web-dev-qa-db-fra.com

lentilles, fclabels, accesseur de données - quelle bibliothèque pour l'accès à la structure et la mutation est meilleure

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

Personnellement, j'ai commencé avec un accesseur de données et je les utilise maintenant. Cependant récemment sur haskell-cafe, il y avait une opinion selon laquelle les fclabels étaient supérieurs.

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

170
Tener

Il y a au moins 4 bibliothèques que je connais pour 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:

Tout d'abord, si vous mettez quelque chose, vous pouvez le récupérer

get l (put l b a) = b 

Deuxièmement, obtenir et paramétrer 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 saisie n'est pas suffisant pour vérifier ces lois pour vous, vous devez donc les assurer vous-même, quelle que soit la mise en œuvre de l'objectif que vous utilisez.

Beaucoup de ces bibliothèques fournissent également un tas de combinateurs supplémentaires, et généralement une certaine forme de machinerie haskell de modèle 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 plus facile à raisonner sur les bibliothèques d'objectifs, car son a :-> b peut être directement traduit dans le type ci-dessus. Il fournit une instance Category pour (:->) Qui est utile car elle vous permet de composer des objectifs. Il fournit également un type Point sans loi qui généralise la notion de lentille utilisée ici, et une plomberie pour faire face aux isomorphismes.

Un obstacle à l'adoption de fclabels est que le package principal comprend la plomberie template-haskell, donc le package n'est pas Haskell 98, et il nécessite également l'extension (assez peu controversée) TypeOperators .

accesseur de données

[Modifier: data-accessor N'utilise plus cette représentation, mais a migré vers un formulaire similaire à celui de data-lens. Je garde cependant ce commentaire.]

accesseur de données est un peu plus populaire que fclabels, en partie parce qu'il est Haskell 98. Cependant, son choix de représentation interne me fait vomir dans ma bouche un peu.

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 get la valeur d'une lentille, vous devez soumettre une valeur non dé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 paquets qui l'utilisent déjà, étant Haskell 98, et fournissant l'instance très importante Category, donc si vous ne faites pas attention à la façon dont la saucisse est faite, cette le paquet est en fait un choix assez raisonnable.

lentilles

Ensuite, il y a le package lentilles , qui observe qu'une lentille peut fournir un homomorphisme de monade d'état entre deux monades d'état, en définissant directement les lentilles comme de tels homomorphismes de monade.

Si elle prenait la peine de fournir un type pour ses lentilles, elles auraient un type de rang 2 comme:

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

Par conséquent, je n'aime pas cette approche, car elle vous arrache inutilement à Haskell 98 (si vous voulez qu'un type fournisse à vos lentilles dans l'abstrait) et vous prive de l'instance Category pour lentilles, ce qui vous permettrait de les composer avec .. L'implémentation nécessite également des classes de type multi-paramètres.

Notez que toutes les autres bibliothèques d'objectifs mentionnées ici fournissent un combinateur ou peuvent être utilisées pour fournir ce 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épart n'ont pas vraiment une belle expression sous cette forme. Comme pour les "fclabels", cela fournit une méthode modèle-haskell pour générer automatiquement des lentilles pour un type d'enregistrement directement dans le package principal.

En raison du manque d'instance Category, 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: Depuis la version 1.8.0, ceux-ci sont passés du package comonad-transformers à data-lens]

Mon data-lens package fournit des lentilles en termes de Store 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 une factorisation de l'argument commun du getter et du setter pour renvoyer une paire composé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 extraire la valeur, ce qui permet une opération de "modification" plus efficace que dans la définition fclabels, en particulier lorsque les accesseurs sont chaînés.

Il y a aussi une justification théorique de Nice pour cette représentation, parce que le sous-ensemble des valeurs de `` lentille '' qui satisfont aux 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 `` houppier comonad '' pour le magasin comonad . Cela transforme 3 lois velues pour une lentille l en 2 équivalents 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 est à Lens comme Applicative est à Biplate: Présentation de Multiplate = et était blogué sur la base d'une préimpression par Jeremy Gibbons.

Il comprend également un certain nombre de combinateurs pour travailler avec des lentilles strictement et quelques lentilles de stock pour les conteneurs, tels que Data.Map.

Ainsi, les lentilles dans data-lens Forment un Category (contrairement au package lenses), sont Haskell 98 (contrairement à fclabels/lenses), sont sensés (contrairement à la partie 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 veulent sortir de Haskell 98, et la machinerie 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

Lentilles d'isomorphisme

Il y a deux autres encodages de lentilles à considérer. Le premier donne une belle façon théorique de voir une lentille comme un moyen de briser une structure en la valeur du champ et "tout le reste".

Étant donné un type d'isomorphismes

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

de sorte que les membres valides satisfassent hither . yon = id et yon . hither = id

Nous pouvons représenter une lentille avec:

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

Ceux-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 manière à ce qu'elles puissent être composées avec (.) Et id, même sans instance Category en utilisant

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

comme type pour nos lentilles.

Définir un objectif est alors aussi simple que:

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

et vous pouvez valider par vous-même que la composition de la fonction est la composition de la lentille.

J'ai récemment écrit sur la façon dont vous pouvez continuer généraliser les lentilles van Laarhoven pour obtenir des familles de lentilles qui peuvent changer les types de champs, simplement en généralisant cette signature en

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 Lens que 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 objectifs, des familles d'objectifs et d'autres généralisations, notamment des getters, des setters, des plis et des traversées. Il est disponible sur le hackage en tant que package lens .

Encore une fois, un grand avantage de cette approche est que les responsables de bibliothèque peuvent réellement créer des lentilles dans ce style dans vos bibliothèques sans encourir de dépendance de bibliothèque de lentilles, en fournissant simplement des fonctions de type Functor f => (b -> f b) -> a -> f a, pour leurs types particuliers 'a ' et B'. Cela réduit considérablement le coût d'adoption.

Comme vous n'avez pas besoin d'utiliser réellement le package pour définir de nouveaux objectifs, cela soulage beaucoup mes préoccupations précédentes concernant la conservation de la bibliothèque Haskell 98.

196
Edward KMETT