Są co najmniej 4 biblioteki, o których wiem, że dostarczają soczewki.
Pojęcie soczewki polega na tym, że zapewnia ona coś izomorficznego
data Lens a b = Lens (a -> b) (b -> a -> a)
zapewniając dwie funkcje: pobierającą i ustawiającą
get (Lens g _) = g
put (Lens _ s) = s
podlega trzem prawom:
Po pierwsze, jeśli coś włożysz, możesz to z powrotem wyciągnąć
get l (put l b a) = b
Po drugie, otrzymanie, a następnie ustawienie nie zmienia odpowiedzi
put l (get l a) a = a
Po trzecie, postawienie dwa razy jest tym samym, co postawienie raz, a raczej drugie postawienie wygrywa.
put l b1 (put l b2 a) = put l b1 a
Zwróć uwagę, że system typów nie jest wystarczający, aby sprawdzić te przepisy za Ciebie, więc musisz się upewnić, że niezależnie od zastosowanej implementacji obiektywu.
Wiele z tych bibliotek zapewnia również kilka dodatkowych kombinatorów na górze i zwykle jakąś formę szablonów maszyn haskell do automatycznego generowania soczewek dla pól prostych typów rekordów.
Mając to na uwadze, możemy przejść do różnych implementacji:
Wdrożenia
fclabels
fclabels jest prawdopodobnie najłatwiejszym argumentem na temat bibliotek soczewek, ponieważ a :-> b
można go bezpośrednio przełożyć na powyższy typ. Udostępnia instancję Category,(:->)
która jest przydatna, ponieważ umożliwia komponowanie soczewek. Zapewnia również Point
typ bezprawia, który uogólnia pojęcie zastosowanej tutaj soczewki i pewną hydraulikę do radzenia sobie z izomorfizmami.
Jedną z przeszkód w przyjęciu programu fclabels
jest to, że główny pakiet zawiera instalację wodno-kanalizacyjną template-haskell, więc pakiet nie jest Haskell 98, a także wymaga (raczej niekontrowersyjnego) TypeOperators
rozszerzenia.
akcesor danych
[Edytuj: data-accessor
nie używa już tej reprezentacji, ale przeszedł do formy podobnej do tej z data-lens
. Ale zachowuję ten komentarz.]
Data-accessor jest nieco bardziej popularny niż fclabels
, po części, dlatego, że jest to Haskell 98. Jednak jego wybór wewnętrznej reprezentacji sprawia, że wymiotuję trochę w ustach.
Typ T
używany do reprezentowania soczewki jest wewnętrznie definiowany jako
newtype T r a = Cons { decons :: a -> r -> (a, r) }
W związku z tym, aby get
określić wartość soczewki, musisz podać nieokreśloną wartość dla argumentu „a”! Wydaje mi się to niesamowicie brzydką implementacją ad hoc.
To powiedziawszy, Henning włączył hydraulikę template-haskell, aby automatycznie generować akcesory w osobnym pakiecie „ data-accessor-template ”.
Ma tę zaletę, że jest to całkiem duży zestaw pakietów, które już go wykorzystują, jest to Haskell 98 i zapewnia najważniejszą Category
instancję, więc jeśli nie zwracasz uwagi na to, jak powstaje kiełbasa, ten pakiet jest w rzeczywistości całkiem rozsądnym wyborem .
soczewki
Następnie jest pakiet soczewek , który zauważa, że soczewka może zapewnić homomorfizm monady stanu między dwiema monadami stanu, poprzez bezpośrednie definiowanie soczewek jako takich homomorfizmów monad.
Gdyby rzeczywiście zadał sobie trud dostarczenia typu dla swoich soczewek, miałyby typ drugiego stopnia, taki jak:
newtype Lens s t = Lens (forall a. State t a -> State s a)
W rezultacie raczej nie podoba mi się to podejście, ponieważ niepotrzebnie wyciąga cię z Haskell 98 (jeśli chcesz, aby typ dostarczał twoim soczewkom w abstrakcji) i pozbawia cię Category
instancji soczewek, które pozwolą ci skomponuj je z .
. Implementacja wymaga również klas typu wieloparametrowego.
Należy pamiętać, że wszystkie inne biblioteki obiektywów wymienione tutaj zapewniają jakiś kombinator lub mogą być użyte do zapewnienia tego samego efektu fokalizacji stanu, więc nic nie jest zyskiwane przez bezpośrednie kodowanie obiektywu w ten sposób.
Ponadto warunki poboczne podane na początku nie mają ładnego wyrażenia w tej formie. Podobnie jak w przypadku „fclabels”, zapewnia to metodę template-haskell do automatycznego generowania soczewek dla typu rekordu bezpośrednio w głównym pakiecie.
Ze względu na brak Category
instancji, barokowe kodowanie i wymóg template-haskell w głównym pakiecie, jest to moja najmniej ulubiona implementacja.
soczewka danych
[Edycja: od 1.8.0 te zostały przeniesione z pakietu comonad-transformers do data-lens]
W moim data-lens
pakiecie są soczewki w ramach comonad Sklepu .
newtype Lens a b = Lens (a -> Store b a)
gdzie
data Store b a = Store (b -> a) b
Rozszerzony jest to odpowiednik
newtype Lens a b = Lens (a -> (b, b -> a))
Można to postrzegać jako uwzględnienie wspólnego argumentu pobierającego i ustawiającego w celu zwrócenia pary składającej się z wyniku pobrania elementu i metody ustawiającej, która wstawia nową wartość. Daje to obliczeniową korzyść, jaką „ustawiacz” tutaj można ponownie wykorzystać część pracy użytej do uzyskania wartości, co zapewnia bardziej wydajną operację „modyfikowania” niż w fclabels
definicji, zwłaszcza gdy akcesoria są połączone łańcuchami.
Istnieje również dobre teoretyczne uzasadnienie tego przedstawienia, ponieważ podzbiór wartości „soczewki”, które spełniają 3 prawa określone na początku tej odpowiedzi, to dokładnie te soczewki, dla których funkcja opakowana jest „komonadą węgla” dla comonady sklepowej . To przekształca 3 włochate prawa dla obiektywu l
do 2 ładnie pozbawionych punktów odpowiedników:
extract . l = id
duplicate . l = fmap l . l
Podejście to pierwszy zauważył i opisał w Russell O'Connora Functor
jest Lens
jak Applicative
jest Biplate
: Przedstawiamy wielotarczowe i został blogu o oparciu o preprintu przez Jeremy Gibbons.
Zawiera również szereg kombinatorów do ścisłej pracy z soczewkami i niektóre standardowe soczewki do pojemników, takie jak Data.Map
.
Tak więc soczewki w data-lens
formie a Category
(w przeciwieństwie do lenses
opakowania) są Haskell 98 (w przeciwieństwie do fclabels
/ lenses
), są rozsądne (w przeciwieństwie do tylnej części data-accessor
) i zapewniają nieco bardziej wydajną implementację, data-lens-fd
zapewniają funkcjonalność do pracy z MonadState dla tych, którzy chcą wyjść na zewnątrz Haskell 98, a maszyneria template-haskell jest teraz dostępna za pośrednictwem data-lens-template
.
Aktualizacja 28.06.2012: Inne strategie wdrażania obiektywów
Soczewki izomorficzne
Warto wziąć pod uwagę dwa inne kodowania obiektywów. Pierwsza daje dobry teoretyczny sposób spojrzenia na soczewkę jako sposób na rozbicie struktury na wartość pola i „wszystko inne”.
Podano typ izomorfizmów
data Iso a b = Iso { hither :: a -> b, yon :: b -> a }
takie, które spełniają ważni członkowie hither . yon = id
, iyon . hither = id
Możemy przedstawić soczewkę z:
data Lens a b = forall c. Lens (Iso a (b,c))
Są one przede wszystkim przydatne jako sposób myślenia o znaczeniu soczewek i możemy ich użyć jako narzędzia do rozumowania, aby wyjaśnić inne soczewki.
Soczewki van Laarhoven
Możemy modelować soczewki w taki sposób, aby można je było komponować z, (.)
a id
nawet bez Category
instancji za pomocą
type Lens a b = forall f. Functor f => (b -> f b) -> a -> f a
jako typ naszych soczewek.
W takim razie zdefiniowanie soczewki jest tak proste, jak:
_2 f (a,b) = (,) a <$> f b
i możesz samodzielnie przekonać się, że skład funkcji to skład soczewki.
Niedawno pisałem o tym, jak można dalej uogólniać soczewki van Laarhovena, aby uzyskać rodziny soczewek, które mogą zmieniać typy pól, po prostu uogólniając ten podpis na
type LensFamily a b c d = forall f. Functor f => (c -> f d) -> a -> f b
Ma to niefortunną konsekwencję, że najlepszym sposobem mówienia o soczewkach jest użycie polimorfizmu rangi 2, ale nie musisz używać tego podpisu bezpośrednio podczas definiowania soczewek.
Lens
Zdefiniowałem powyżej _2
jest faktycznie LensFamily
.
_2 :: Functor f => (a -> f b) -> (c,a) -> f (c, b)
Napisałem bibliotekę zawierającą soczewki, rodziny soczewek i inne uogólnienia, w tym gettery, setery, fałdy i przejścia. Jest dostępny na hackage jako lens
pakiet.
Ponownie, wielką zaletą tego podejścia jest to, że opiekunowie bibliotek mogą faktycznie tworzyć soczewki w tym stylu w twoich bibliotekach bez ponoszenia jakiejkolwiek zależności od biblioteki soczewek, po prostu dostarczając funkcjom typ Functor f => (b -> f b) -> a -> f a
, dla ich poszczególnych typów „a” i „b”. To znacznie obniża koszt adopcji.
Ponieważ nie musisz faktycznie używać pakietu do definiowania nowych obiektywów, znacznie odciąża to moje wcześniejsze obawy dotyczące zachowania biblioteki Haskell 98.
lens
pakiet ma najbogatszą funkcjonalność i dokumentację, więc jeśli nie masz nic przeciwko jego złożoności i zależnościom, to jest droga do zrobienia.