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 :-> b
có 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 Point
loạ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 fclabels
là 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 TypeOperators
phần mở rộng (không gây tranh cãi) .
truy cập dữ liệu
[Chỉnh sửa: data-accessor
khô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ó là 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 T
nó 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 đó, để get
giá 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 Category
ví 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ỏ Category
ví 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 Category
ví 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-lens
Gó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 l
thà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 Functor
là Lens
như Applicative
là Biplate
: 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-lens
dạng Category
(không giống như lenses
gó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-fd
cung 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 (.)
và id
thậm chí không có Category
ví 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 Lens
tôi định nghĩa ở trên để _2
thự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ư lens
gó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.
lens
gó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.