lentilles, fclabels, data-accessor - quelle bibliothèque pour l'accès à la structure et la mutation est la meilleure

173

Il existe au moins trois bibliothèques populaires pour accéder et manipuler des champs d'enregistrements. Ceux que je connais sont: un accesseur de données, des fclabels et des lentilles.

Personnellement, j'ai commencé avec data-accessor et je les utilise maintenant. Cependant, récemment, sur haskell-cafe, il y avait une opinion selon laquelle les étiquettes étaient supérieures.

Par conséquent, je suis intéressé par la comparaison de ces trois bibliothèques (et peut-être plus).

Tener
la source
3
À partir d'aujourd'hui, le lenspackage 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.
modulaire

Réponses:

200

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 :-> bpeut ê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 fclabelsest que le package principal inclut la plomberie template-haskell, donc le package n'est pas Haskell 98, et il nécessite également l' TypeOperatorsextension (assez non controversée) .

accesseur de données

[Modifier: data-accessorn'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 Tqu'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 getla 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' Categoryinstance 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' Categoryinstance, 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-lensforfait fournit des lentilles en termes de magasin comonad.

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

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 len 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 Functoris to Lensas Applicativeis 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-lensforme 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-fdfournit 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 Categoryinstance, 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 Lensj'ai défini ci-dessus pour _2est 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 lenspackage.

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.

Edward KMETT
la source
28
J'aime les fclabels pour leur approche optimiste:->
Tener
3
Les articles Inessential Guide to data-accessor et Inessential guide to fclabels pourraient être remarquables
hvr
10
Est-il important d'être compatible avec Haskell 1998? Parce que cela facilite le développement du compilateur? Et ne devrions-nous pas plutôt parler de Haskell 2010?
yairchu
55
Oh non! J'étais l'auteur original de data-accessor, puis je l'ai passé à Henning et j'ai arrêté d'y prêter attention. La a -> r -> (a,r)représentation me met également mal à l'aise, et mon implémentation d'origine était exactement comme votre Lenstype. Heeennnninngg !!
luqui
5
Yairchu: c'est surtout pour que votre bibliothèque puisse avoir une chance de travailler avec un compilateur autre que ghc. Personne d'autre n'a le modèle Haskell. 2010 n'ajoute rien de pertinent ici.
Edward KMETT