ống kính, fclabels, truy cập dữ liệu - thư viện để truy cập cấu trúc và đột biến là tốt hơn


173

Có ít nhất ba thư viện phổ biến để truy cập và thao tác các lĩnh vực hồ sơ. Những cái tôi biết là: truy cập dữ liệu, fclabels và ống kính.

Cá nhân tôi đã bắt đầu với trình truy cập dữ liệu và tôi đang sử dụng chúng ngay bây giờ. Tuy nhiên gần đây trên haskell-cafe đã có ý kiến ​​về fclabels là vượt trội.

Do đó, tôi quan tâm đến việc so sánh ba thư viện (và có thể nhiều hơn).


3
Cho đến ngày nay, lensgói có chức năng và tài liệu phong phú nhất, vì vậy nếu bạn không quan tâm đến sự phức tạp và phụ thuộc của nó, thì đó là cách để đi.
mô-đun

Câu trả lời:


200

Có ít nhất 4 thư viện mà tôi biết về việc cung cấp ống kính.

Khái niệm về một thấu kính là nó cung cấp một cái gì đó đồng hình với

data Lens a b = Lens (a -> b) (b -> a -> a)

cung cấp hai chức năng: getter và setter

get (Lens g _) = g
put (Lens _ s) = s

theo ba luật:

Đầu tiên, nếu bạn đặt một cái gì đó, bạn có thể lấy nó ra

get l (put l b a) = b 

Thứ hai là nhận và sau đó thiết lập không thay đổi câu trả lời

put l (get l a) a = a

Và thứ ba, đặt hai lần cũng giống như đặt một lần, hay đúng hơn là đặt thứ hai thắng.

put l b1 (put l b2 a) = put l b1 a

Lưu ý rằng hệ thống loại không đủ để kiểm tra các luật này cho bạn, vì vậy bạn cần tự đảm bảo chúng cho dù bạn sử dụng ống kính nào.

Nhiều trong số các thư viện này cũng cung cấp một loạt các tổ hợp bổ sung ở trên và thường là một số dạng máy móc có khuôn mẫu để tự động tạo ống kính cho các trường của các loại bản ghi đơn giản.

Với ý nghĩ đó, chúng ta có thể chuyển sang các triển khai khác nhau:

Triển khai

fclabels

fclabels có lẽ là lý do dễ hiểu nhất về các thư viện ống kính, bởi vì nó a :-> bcó thể được dịch trực tiếp sang loại trên. Nó cung cấp một loại dụ cho (:->)đó là hữu ích vì nó cho phép bạn để ống kính soạn. Nó cũng cung cấp một Pointloại không hợp pháp trong đó khái quát hóa khái niệm về một ống kính được sử dụng ở đây và một số hệ thống ống nước để xử lý các đẳng cấu.

Một trở ngại cho việc áp dụng fclabelslà gói chính bao gồm hệ thống ống nước mẫu-haskell, vì vậy gói không phải là Haskell 98, và nó cũng yêu cầu TypeOperatorsphần mở rộng (không gây tranh cãi) .

truy cập dữ liệu

[Chỉnh sửa: data-accessorkhông còn sử dụng biểu diễn này nữa, nhưng đã chuyển sang dạng tương tự như data-lens. Tôi đang giữ bình luận này, mặc dù.]

truy cập dữ liệu có phần phổ biến hơn fclabels, một phần vì nó Haskell 98. Tuy nhiên, sự lựa chọn đại diện bên trong của nó khiến tôi khó chịu trong miệng một chút.

Loại Tnó sử dụng để thể hiện một ống kính được xác định bên trong là

newtype T r a = Cons { decons :: a -> r -> (a, r) }

Do đó, để getgiá trị của ống kính, bạn phải gửi một giá trị không xác định cho đối số 'a'! Điều này gây ấn tượng với tôi như là một thực hiện cực kỳ xấu xí và ad hoc.

Điều đó nói rằng, Henning đã bao gồm hệ thống ống nước mẫu-haskell để tự động tạo ra các bộ truy cập cho bạn trong một gói ' mẫu truy cập dữ liệu ' riêng biệt .

Nó có lợi ích của một bộ lớn các gói đã sử dụng nó, là Haskell 98, và cung cấp Categoryví dụ quan trọng , vì vậy nếu bạn không chú ý đến cách làm xúc xích, gói này thực sự là lựa chọn khá hợp lý .

ống kính

Tiếp theo, có gói ống kính , quan sát rằng một ống kính có thể cung cấp sự đồng hình đơn sắc trạng thái giữa hai đơn nguyên trạng thái, bằng cách xác định trực tiếp ống kính như các đồng cấu đơn nguyên như vậy.

Nếu nó thực sự bận tâm để cung cấp một loại cho các ống kính của nó, thì chúng sẽ có loại hạng 2 như:

newtype Lens s t = Lens (forall a. State t a -> State s a)

Do đó, tôi không thích cách tiếp cận này, vì nó không cần thiết đưa bạn ra khỏi Haskell 98 (nếu bạn muốn một loại để cung cấp cho ống kính của bạn trong bản tóm tắt) và loại bỏ Categoryví dụ cho ống kính, điều này sẽ cho phép bạn sáng tác chúng với .. Việc thực hiện cũng đòi hỏi các lớp loại đa tham số.

Lưu ý, tất cả các thư viện ống kính khác được đề cập ở đây đều cung cấp một số tổ hợp hoặc có thể được sử dụng để cung cấp hiệu ứng tiêu cự tương tự này, do đó, không có gì đạt được bằng cách mã hóa ống kính của bạn trực tiếp theo cách này.

Hơn nữa, các điều kiện bên được nêu ở đầu không thực sự có một biểu hiện đẹp trong mẫu này. Như với 'fclabels', điều này cung cấp phương pháp mẫu-haskell để tự động tạo ống kính cho loại bản ghi trực tiếp trong gói chính.

Do thiếu Categoryví dụ, mã hóa baroque và yêu cầu của mẫu-haskell trong gói chính, đây là cách triển khai ít được yêu thích nhất của tôi.

ống kính dữ liệu

[Chỉnh sửa: Kể từ 1.8.0, chúng đã được chuyển từ gói comonad-Transformers sang ống kính dữ liệu]

data-lensGói của tôi cung cấp ống kính trong điều khoản của cửa hàng comonad.

newtype Lens a b = Lens (a -> Store b a)

Ở đâu

data Store b a = Store (b -> a) b

Mở rộng này tương đương với

newtype Lens a b = Lens (a -> (b, b -> a))

Bạn có thể xem điều này như bao gồm các đối số phổ biến từ getter và setter để trả về một cặp bao gồm kết quả của việc truy xuất phần tử và một setter để đặt lại một giá trị mới. Điều này mang lại lợi ích tính toán mà 'setter' mang lại. ở đây có thể tái chế một số công việc được sử dụng để lấy giá trị ra, giúp cho hoạt động 'sửa đổi' hiệu quả hơn so với fclabelsđịnh nghĩa, đặc biệt là khi các bộ truy cập bị xiềng xích.

Ngoài ra còn có một lý do biện minh lý thuyết tốt cho đại diện này, bởi vì tập hợp con của các giá trị 'Lens' thỏa mãn 3 định luật được nêu trong phần đầu của phản hồi này chính xác là các thấu kính có chức năng được bọc là 'hàm than comonad' cho comonad lưu trữ . Điều này biến đổi 3 định luật lông cho một ống kính lthành 2 tương đương điểm độc đáo:

extract . l = id
duplicate . l = fmap l . l

Cách tiếp cận này lần đầu tiên được ghi nhận và mô tả trong Russell O'Connor FunctorLensnhư ApplicativeBiplate: Giới thiệu Multiplate và được viết blog về dựa trên bản thảo bởi Jeremy Gibbons.

Nó cũng bao gồm một số tổ hợp để làm việc với ống kính nghiêm ngặt và một số ống kính chứng khoán cho các thùng chứa, chẳng hạn như Data.Map.

Vì vậy, các ống kính ở data-lensdạng Category(không giống như lensesgói), là Haskell 98 (không giống fclabels/ lenses), lành mạnh (không giống như mặt sau của data-accessor) và cung cấp triển khai hiệu quả hơn một chút, data-lens-fdcung cấp chức năng để làm việc với MonadState cho những người sẵn sàng bước ra ngoài của Haskell 98 và máy móc haskell mẫu hiện có sẵn thông qua data-lens-template.

Cập nhật 28/11/2012: Các chiến lược triển khai ống kính khác

Ống kính đẳng hình

Có hai bảng mã ống kính khác đáng xem xét. Cách đầu tiên đưa ra một cách lý thuyết hay để xem ống kính là cách phá vỡ cấu trúc thành giá trị của trường và 'mọi thứ khác'.

Cho một loại cho đẳng cấu

data Iso a b = Iso { hither :: a -> b, yon :: b -> a }

sao cho các thành viên hợp lệ thỏa mãn hither . yon = id, vàyon . hither = id

Chúng ta có thể đại diện cho một ống kính với:

data Lens a b = forall c. Lens (Iso a (b,c))

Chúng chủ yếu hữu ích như một cách để suy nghĩ về ý nghĩa của ống kính và chúng ta có thể sử dụng chúng như một công cụ lý luận để giải thích các ống kính khác.

ống kính van Laarhoven

Chúng ta có thể mô hình hóa các ống kính sao cho chúng có thể được sáng tác (.)idthậm chí không có Categoryví dụ bằng cách sử dụng

type Lens a b = forall f. Functor f => (b -> f b) -> a -> f a

như loại cho ống kính của chúng tôi.

Sau đó, xác định một ống kính dễ dàng như:

_2 f (a,b) = (,) a <$> f b

và bạn có thể tự xác nhận rằng thành phần chức năng là thành phần thấu kính.

Gần đây tôi đã viết về cách bạn có thể khái quát hóa thêm các ống kính van Laarhoven để có được các họ ống kính có thể thay đổi các loại trường, chỉ bằng cách khái quát chữ ký này thành

type LensFamily a b c d = forall f. Functor f => (c -> f d) -> a -> f b

Điều này có một hậu quả đáng tiếc là cách tốt nhất để nói về ống kính là sử dụng đa hình bậc 2, nhưng bạn không cần phải sử dụng chữ ký đó trực tiếp khi xác định ống kính.

Các Lenstôi định nghĩa ở trên để _2thực sự là một LensFamily.

_2 :: Functor f => (a -> f b) -> (c,a) -> f (c, b)

Tôi đã viết một thư viện bao gồm ống kính, họ ống kính và các khái quát khác bao gồm getters, setters, nếp gấp và đường ngang. Nó có sẵn trên hackage như lensgói.

Một lần nữa, một lợi thế lớn của phương pháp này là những người bảo trì thư viện thực sự có thể tạo ra các ống kính theo kiểu này trong thư viện của bạn mà không phải chịu bất kỳ sự phụ thuộc nào của thư viện ống kính, chỉ bằng cách cung cấp các hàm với loại Functor f => (b -> f b) -> a -> f a, cho các loại cụ thể của họ 'a' và 'b'. Điều này làm giảm đáng kể chi phí nhận con nuôi.

Vì bạn không thực sự cần sử dụng gói để xác định ống kính mới, nên sẽ giảm được nhiều áp lực trước những lo ngại trước đây của tôi về việc giữ thư viện Haskell 98.


28
Tôi thích fclabels vì cách tiếp cận lạc quan của nó:->
Tener


10
Là tương thích Haskell 1998 quan trọng? Bởi vì nó làm cho trình biên dịch phát triển dễ dàng hơn? Và chúng ta không nên chuyển sang nói về Haskell 2010 chứ?
yairchu

55
Ôi không! Tôi là tác giả ban đầu data-accessor, và sau đó tôi chuyển nó cho Henning và ngừng chú ý. Việc a -> r -> (a,r)đại diện cũng khiến tôi không thoải mái, và cách triển khai ban đầu của tôi giống như Lenskiểu của bạn . Heeennnninngg !!
luqui

5
Yairchu: chủ yếu là để thư viện của bạn có thể có cơ hội làm việc với trình biên dịch khác ngoài ghc. Không ai khác mẫu Haskell. 2010 không thêm bất cứ điều gì có liên quan ở đây.
Edward KMett
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.