Objektive, Fclabels, Datenzugriff - welche Bibliothek für Strukturzugriff und Mutation ist besser

173

Es gibt mindestens drei beliebte Bibliotheken für den Zugriff auf und die Bearbeitung von Datensatzfeldern. Diejenigen, die ich kenne, sind: Datenzugriff, Fclabels und Objektive.

Persönlich habe ich mit Data Accessor angefangen und benutze sie jetzt. Vor kurzem gab es jedoch im Haskell-Café die Meinung, dass Fclabels überlegen seien.

Daher bin ich am Vergleich dieser drei (und vielleicht mehr) Bibliotheken interessiert.

Teneriffa
quelle
3
Ab heute lensverfügt das Paket über die umfangreichsten Funktionen und Dokumentationen. Wenn Sie sich also nicht um die Komplexität und die Abhängigkeiten kümmern, ist dies der richtige Weg.
Modular

Antworten:

200

Es gibt mindestens 4 Bibliotheken, von denen ich weiß, dass sie Objektive bereitstellen.

Die Vorstellung einer Linse ist, dass sie etwas Isomorphes liefert

data Lens a b = Lens (a -> b) (b -> a -> a)

Bereitstellung von zwei Funktionen: ein Getter und ein Setter

get (Lens g _) = g
put (Lens _ s) = s

unterliegt drei Gesetzen:

Erstens, wenn Sie etwas setzen, können Sie es wieder herausholen

get l (put l b a) = b 

Zweitens ändert das Abrufen und anschließende Einstellen nichts an der Antwort

put l (get l a) a = a

Und drittens ist das zweimalige Putten dasselbe wie das einmalige Putten oder vielmehr, dass der zweite Put gewinnt.

put l b1 (put l b2 a) = put l b1 a

Beachten Sie, dass das Typsystem nicht ausreicht, um diese Gesetze für Sie zu überprüfen. Sie müssen sie daher selbst sicherstellen, unabhängig davon, welche Objektivimplementierung Sie verwenden.

Viele dieser Bibliotheken bieten darüber hinaus eine Reihe zusätzlicher Kombinatoren und normalerweise eine Form von Vorlagen-Haskell-Maschinen, um automatisch Linsen für die Felder einfacher Datensatztypen zu generieren.

In diesem Sinne können wir uns den verschiedenen Implementierungen zuwenden:

Implementierungen

fclabels

fclabels ist vielleicht die am einfachsten zu begründende Linsenbibliothek, da a :-> bes direkt in den obigen Typ übersetzt werden kann. Es bietet eine Kategorie- Instanz, für (:->)die es nützlich ist, Objektive zusammenzustellen. Es bietet auch einen gesetzlosen PointTyp, der die Vorstellung einer hier verwendeten Linse verallgemeinert, und einige Leitungen für den Umgang mit Isomorphismen.

Ein Hindernis für die Annahme von fclabelsist, dass das Hauptpaket die Schablonen-Haskell-Installation enthält, sodass das Paket nicht Haskell 98 ist und auch die (ziemlich unumstrittene) TypeOperatorsErweiterung erfordert .

Datenzugriff

[Bearbeiten: data-accessorverwendet diese Darstellung nicht mehr, wurde jedoch in eine ähnliche Form wie die von verschoben data-lens. Ich behalte diesen Kommentar jedoch.]

Daten-Accessor ist etwas beliebter als fclabels, zum Teil , weil es ist Haskell 98. Allerdings ich in meinem Mund ein wenig erbrechen die Wahl der internen Repräsentation macht.

Der Typ T, der zur Darstellung eines Objektivs verwendet wird, ist intern definiert als

newtype T r a = Cons { decons :: a -> r -> (a, r) }

Folglich müssen Sie für getden Wert eines Objektivs einen undefinierten Wert für das Argument 'a' angeben! Dies scheint mir eine unglaublich hässliche und Ad-hoc-Implementierung zu sein.

Trotzdem hat Henning das Template-Haskell-Sanitär integriert, um die Accessoren automatisch für Sie in einem separaten ' Data-Accessor-Template' -Paket zu generieren .

Es hat den Vorteil einer anständig großen Anzahl von Paketen, die es bereits verwenden, nämlich Haskell 98, und die alles entscheidende CategoryInstanz bereitstellen. Wenn Sie also nicht darauf achten, wie die Wurst hergestellt wird, ist dieses Paket tatsächlich eine ziemlich vernünftige Wahl .

Linsen

Als nächstes gibt es das Linsenpaket , das beobachtet, dass eine Linse einen Zustandsmonadenhomomorphismus zwischen zwei Zustandsmonaden bereitstellen kann, indem Linsen direkt als solche Monadenhomomorphismen definiert werden .

Wenn es sich tatsächlich die Mühe machen würde, einen Typ für seine Objektive bereitzustellen, hätten sie einen Typ vom Rang 2 wie:

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

Aus diesem Grund mag ich diesen Ansatz eher nicht, da er Sie unnötig aus Haskell 98 herauszieht (wenn Sie möchten, dass ein Typ Ihren Objektiven abstrakt zur Verfügung gestellt wird) und Ihnen die CategoryInstanz für Objektive entzieht , die Sie zulassen würden komponiere sie mit .. Die Implementierung erfordert auch Typklassen mit mehreren Parametern.

Beachten Sie, dass alle anderen hier erwähnten Objektivbibliotheken einen Kombinator bieten oder verwendet werden können, um denselben Zustandsfokalisierungseffekt zu erzielen. Sie können also nichts gewinnen, wenn Sie Ihr Objektiv direkt auf diese Weise codieren.

Darüber hinaus haben die zu Beginn angegebenen Nebenbedingungen in dieser Form keinen wirklich schönen Ausdruck. Wie bei 'fclabels' bietet dies eine Template-Haskell-Methode zum automatischen Generieren von Objektiven für einen Datensatztyp direkt im Hauptpaket.

Aufgrund der fehlenden CategoryInstanz, der Barockcodierung und der Anforderung von Template-Haskell im Hauptpaket ist dies meine am wenigsten bevorzugte Implementierung.

Datenlinse

[Edit: Ab 1.8.0 sind diese vom Comonad-Transformers-Paket auf Data-Lens umgestiegen]

Mein data-lensPaket enthält Objektive im Sinne der Store Comonad.

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

wo

data Store b a = Store (b -> a) b

Erweitert entspricht dies

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

Sie können dies als Ausklammern des allgemeinen Arguments aus dem Getter und dem Setter betrachten, um ein Paar zurückzugeben, das aus dem Ergebnis des Abrufens des Elements besteht, und einem Setter, um einen neuen Wert wieder einzugeben. Dies bietet den Rechenvorteil, den der 'Setter' bietet. Hier kann ein Teil der Arbeit recycelt werden, die verwendet wird, um den Wert herauszuholen, was zu einem effizienteren Änderungsvorgang führt als in der fclabelsDefinition, insbesondere wenn Accessoren verkettet sind.

Es gibt auch eine gute theoretische Begründung für diese Darstellung, da die Teilmenge der 'Linsen'-Werte, die die drei zu Beginn dieser Antwort angegebenen Gesetze erfüllen, genau jene Linsen sind, für die die umhüllte Funktion eine' Comonad Coalgebra 'für die Store Comonad ist . Dies transformiert 3 haarige Gesetze für eine Linse lin 2 schön punktfreie Äquivalente:

extract . l = id
duplicate . l = fmap l . l

Dieser Ansatz wurde erstmals in Russell O'Connors "to Functoris Lensas Applicativeto Biplate: Introducing Multiplate" erwähnt und beschrieben und wurde basierend auf einem Preprint von Jeremy Gibbons gebloggt.

Es enthält auch eine Reihe von Kombinatoren für die strikte Arbeit mit Linsen und einige Standardlinsen für Behälter, wie z Data.Map.

Die Linsen in data-lensForm a Category(im Gegensatz zum lensesPaket) sind also Haskell 98 (im Gegensatz zu fclabels/ lenses) und sind vernünftig (im Gegensatz zum hinteren Ende von)data-accessordata-lens-fd Backend ) und bieten eine etwas effizientere Implementierung. Sie bieten die Funktionalität für die Arbeit mit MonadState für diejenigen, die bereit sind, nach draußen zu gehen von Haskell 98, und die Template-Haskell-Maschinerie ist jetzt über verfügbar data-lens-template.

Update 28.06.2012: Andere Implementierungsstrategien für Objektive

Isomorphismuslinsen

Es gibt zwei weitere erwägenswerte Objektivcodierungen. Die erste bietet eine schöne theoretische Möglichkeit, eine Linse als eine Möglichkeit zu betrachten, eine Struktur in den Wert des Feldes und "alles andere" zu zerlegen.

Gegeben ein Typ für Isomorphismen

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

so dass gültige Mitglieder erfüllen hither . yon = id, undyon . hither = id

Wir können eine Linse darstellen mit:

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

Diese sind in erster Linie nützlich, um über die Bedeutung von Objektiven nachzudenken, und wir können sie als Argumentationswerkzeug verwenden, um andere Objektive zu erklären.

van Laarhoven Linsen

Wir können Objektive so modellieren, dass sie mit (.)und idauch ohne CategoryInstanz zusammengesetzt werden können

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

als Typ für unsere Objektive.

Dann ist das Definieren eines Objektivs so einfach wie:

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

und Sie können selbst überprüfen, ob die Funktionszusammensetzung die Linsenzusammensetzung ist.

Ich habe kürzlich darüber geschrieben, wie Sie van Laarhoven-Objektive weiter verallgemeinern können , um Linsenfamilien zu erhalten, die die Feldtypen ändern können, indem Sie einfach diese Signatur auf verallgemeinern

type LensFamily a b c d = forall f. Functor f => (c -> f d) -> a -> f b

Dies hat die unglückliche Konsequenz, dass der beste Weg, über Linsen zu sprechen, die Verwendung von Polymorphismus vom Rang 2 ist, aber Sie müssen diese Signatur nicht direkt verwenden, wenn Sie Linsen definieren.

Das, was Lensich oben für definiert habe, _2ist eigentlich ein LensFamily.

_2 :: Functor f => (a -> f b) -> (c,a) -> f (c, b)

Ich habe eine Bibliothek geschrieben, die Linsen, Linsenfamilien und andere Verallgemeinerungen enthält, einschließlich Getter, Setter, Falten und Durchquerungen. Es ist auf Hackage als lensPaket verfügbar .

Ein großer Vorteil dieses Ansatzes besteht wiederum darin, dass Bibliotheksverwalter tatsächlich Objektive in diesem Stil in Ihren Bibliotheken erstellen können, ohne dass eine Abhängigkeit von der Objektivbibliothek entsteht, indem sie lediglich Funktionen mit Typ bereitstellen Functor f => (b -> f b) -> a -> f a für ihre jeweiligen Typen 'a' und 'b' bereitstellen. Dies senkt die Adoptionskosten erheblich.

Da Sie das Paket nicht zum Definieren neuer Objektive verwenden müssen, entlastet dies meine früheren Bedenken hinsichtlich der Aufbewahrung der Bibliothek Haskell 98 erheblich.

Edward KMETT
quelle
28
Ich mag die fclabels für ihren optimistischen Ansatz:->
Tener
3
Die Artikel Inessential Guide to Data-Accessor und Inessential Guide to fclabels könnten bemerkenswert sein
hvr
10
Ist es wichtig, Haskell 1998 kompatibel zu sein? Weil es die Compilerentwicklung erleichtert? Und sollten wir nicht stattdessen über Haskell 2010 sprechen?
Yairchu
55
Ach nein! Ich war der ursprüngliche Autor von data-accessor, und dann gab ich es an Henning weiter und hörte auf, darauf zu achten. Die a -> r -> (a,r)Darstellung macht mich auch unbehaglich und meine ursprüngliche Implementierung war genau wie Ihr LensTyp. Heeennnninngg !!
Luqui
5
Yairchu: Meistens hat Ihre Bibliothek die Möglichkeit, mit einem anderen Compiler als ghc zu arbeiten. Niemand sonst hat Vorlage Haskell. 2010 fügt hier nichts Relevantes hinzu.
Edward KMETT