Il y a au moins 4 bibliothèques que je suis conscient de fournir des lentilles.
La notion de lentille est qu'elle fournit quelque chose d'isomorphe à
data Lens a b = Lens (a -> b) (b -> a -> a)
fournissant deux fonctions: un getter et un setter
get (Lens g _) = g
put (Lens _ s) = s
soumis à trois lois:
Premièrement, si vous mettez quelque chose, vous pouvez le récupérer
get l (put l b a) = b
Deuxièmement, obtenir puis régler ne change pas la réponse
put l (get l a) a = a
Et troisièmement, mettre deux fois équivaut à mettre une fois, ou plutôt, que le deuxième put gagne.
put l b1 (put l b2 a) = put l b1 a
Notez que le système de type n'est pas suffisant pour vérifier ces lois à votre place, vous devez donc vous en assurer vous-même quelle que soit l'implémentation d'objectif que vous utilisez.
Beaucoup de ces bibliothèques fournissent également un tas de combinateurs supplémentaires sur le dessus, et généralement une forme de machinerie de gabarit pour générer automatiquement des lentilles pour les domaines de types d'enregistrement simples.
Dans cet esprit, nous pouvons nous tourner vers les différentes implémentations:
Implémentations
fclabels
fclabels est peut-être la bibliothèque de lentilles la plus facile à raisonner, car elle a :-> b
peut être directement traduite dans le type ci-dessus. Il fournit une instance de catégorie(:->)
qui est utile car elle vous permet de composer des objectifs. Il fournit également unPoint
type qui généralise la notion de lentille utilisée ici, et un peu de plomberie pour traiter les isomorphismes.
Un obstacle à l'adoption de fclabels
est que le package principal inclut la plomberie template-haskell, donc le package n'est pas Haskell 98, et il nécessite également l' TypeOperators
extension (assez non controversée) .
accesseur de données
[Modifier: data-accessor
n'utilise plus cette représentation, mais est passée à une forme similaire à celle dedata-lens
. Je garde ce commentaire, cependant.]
data-accessor est un peu plus populaire que fclabels
, en partie parce qu'il est Haskell 98. Cependant, son choix de représentation interne me fait un peu vomir dans la bouche.
Le type T
qu'il utilise pour représenter une lentille est défini en interne comme
newtype T r a = Cons { decons :: a -> r -> (a, r) }
Par conséquent, pour déterminer get
la valeur d'une lentille, vous devez soumettre une valeur indéfinie pour l'argument «a»! Cela me semble être une mise en œuvre incroyablement laide et ad hoc.
Cela dit, Henning a inclus la plomberie template-haskell pour générer automatiquement les accesseurs pour vous dans un package séparé ' data-accessor-template '.
Il a l'avantage d'un ensemble décemment grand de packages qui l'utilisent déjà, étant Haskell 98, et fournissant le très important Category
instance la plus , donc si vous ne faites pas attention à la façon dont la saucisse est fabriquée, ce paquet est en fait un choix assez raisonnable .
lentilles
Ensuite, il y a le paquet de lentilles , qui observe qu'une lentille peut fournir un homomorphisme de monade d'état entre deux monades d'état, en définissant les lentilles directement comme de tels homomorphismes de monade.
S'il prenait la peine de fournir un type pour ses objectifs, ils auraient un type de rang 2 comme:
newtype Lens s t = Lens (forall a. State t a -> State s a)
En conséquence, je n'aime plutôt pas cette approche, car elle vous arrache inutilement de Haskell 98 (si vous voulez qu'un type fournisse à vos objectifs dans l'abstrait) et vous prive de l' Category
instance pour les objectifs, ce qui vous permettrait composez-les avec .
. L'implémentation nécessite également des classes de types à paramètres multiples.
Notez que toutes les autres bibliothèques d'objectifs mentionnées ici fournissent un combinateur ou peuvent être utilisées pour fournir le même effet de focalisation d'état, donc rien n'est gagné en encodant votre objectif directement de cette manière.
De plus, les conditions secondaires énoncées au début n'ont pas vraiment une belle expression sous cette forme. Comme pour 'fclabels', cela fournit une méthode template-haskell pour générer automatiquement des objectifs pour un type d'enregistrement directement dans le package principal.
En raison du manque d' Category
instance, de l'encodage baroque et de l'exigence de template-haskell dans le package principal, c'est ma mise en œuvre la moins préférée.
lentille de données
[Edit: À partir de la 1.8.0, ceux-ci sont passés du package comonad-transformers à data-lens]
Mon data-lens
forfait fournit des lentilles en termes de magasin comonad.
newtype Lens a b = Lens (a -> Store b a)
où
data Store b a = Store (b -> a) b
Développé, cela équivaut à
newtype Lens a b = Lens (a -> (b, b -> a))
Vous pouvez voir cela comme la prise en compte de l'argument commun du getter et du setter pour renvoyer une paire constituée du résultat de la récupération de l'élément, et un setter pour remettre une nouvelle valeur. Cela offre l'avantage de calcul que le "setter" ici peut recycler une partie du travail utilisé pour obtenir la valeur, ce qui permet une opération de `` modification '' plus efficace que dans le fclabels
définition, en particulier lorsque les accesseurs sont enchaînés.
Il y a aussi une belle justification théorique pour cette représentation, car le sous-ensemble de valeurs de `` Lens '' qui satisfont les 3 lois énoncées au début de cette réponse sont précisément les lentilles pour lesquelles la fonction enveloppée est une `` comonad coalgebra '' pour le magasin comonad . Cela transforme 3 lois poilues pour une lentille l
en 2 équivalents bien sans point:
extract . l = id
duplicate . l = fmap l . l
Cette approche a été notée et décrite pour la première fois dans Russell O'Connor Functor
is to Lens
as Applicative
is to Biplate
: Introducing Multiplate et a fait l'objet d'un blog sur la base d'une pré-impression de Jeremy Gibbons.
Il comprend également un certain nombre de combinateurs pour travailler strictement avec des lentilles et des lentilles de stock pour des conteneurs, tels que Data.Map
.
Donc, les lentilles en data-lens
forme a Category
(contrairement à lalenses
package), sont Haskell 98 (contrairement à fclabels
/ lenses
), sont saines (contrairement à l'arrière de data-accessor
) et fournissent une implémentation légèrement plus efficace, data-lens-fd
fournit la fonctionnalité pour travailler avec MonadState pour ceux qui souhaitent sortir de Haskell 98, et la machine template-haskell est maintenant disponible via data-lens-template
.
Mise à jour du 28/06/2012: Autres stratégies de mise en œuvre de l'objectif
Verres d'isomorphisme
Il existe deux autres encodages d'objectifs à considérer. Le premier donne une belle façon théorique de voir une lentille comme un moyen de décomposer une structure dans la valeur du champ, et «tout le reste».
Étant donné un type pour les isomorphismes
data Iso a b = Iso { hither :: a -> b, yon :: b -> a }
de telle sorte que les membres valides satisfassent hither . yon = id
, etyon . hither = id
On peut représenter une lentille avec:
data Lens a b = forall c. Lens (Iso a (b,c))
Celles-ci sont principalement utiles pour réfléchir à la signification des lentilles, et nous pouvons les utiliser comme outil de raisonnement pour expliquer d'autres lentilles.
Verres van Laarhoven
Nous pouvons modéliser des lentilles de telle sorte qu'elles puissent être composées avec (.)
et id
, même sans Category
instance, en utilisant
type Lens a b = forall f. Functor f => (b -> f b) -> a -> f a
comme le type de nos lentilles.
Ensuite, définir une lentille est aussi simple que:
_2 f (a,b) = (,) a <$> f b
et vous pouvez valider vous-même que la composition des fonctions est la composition des verres.
J'ai récemment écrit sur la façon dont vous pouvez généraliser davantage les objectifs van Laarhoven pour obtenir des familles de lentilles qui peuvent changer les types de champs, simplement en généralisant cette signature à
type LensFamily a b c d = forall f. Functor f => (c -> f d) -> a -> f b
Cela a la conséquence malheureuse que la meilleure façon de parler des lentilles est d'utiliser le polymorphisme de rang 2, mais vous n'avez pas besoin d'utiliser cette signature directement lors de la définition des lentilles.
Le que Lens
j'ai défini ci-dessus pour _2
est en fait un LensFamily
.
_2 :: Functor f => (a -> f b) -> (c,a) -> f (c, b)
J'ai écrit une bibliothèque qui comprend des lentilles, des familles de lentilles et d'autres généralisations, notamment des getters, des setters, des plis et des traversées. Il est disponible sur hackage en tant que lens
package.
Encore une fois, un grand avantage de cette approche est que les responsables de la bibliothèque peuvent créer des lentilles dans ce style dans vos bibliothèques sans encourir aucune dépendance de bibliothèque de lentilles, en fournissant simplement des fonctions avec type Functor f => (b -> f b) -> a -> f a
, pour leurs types particuliers 'a' et 'b'. Cela réduit considérablement le coût de l'adoption.
Comme vous n'avez pas besoin d'utiliser le package pour définir de nouvelles lentilles, cela soulage beaucoup de mes préoccupations précédentes concernant la conservation de la bibliothèque Haskell 98.
lens
package a les fonctionnalités et la documentation les plus riches, donc si vous ne vous souciez pas de sa complexité et de ses dépendances, c'est la voie à suivre.