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 :-> b
es 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 Point
Typ, der die Vorstellung einer hier verwendeten Linse verallgemeinert, und einige Leitungen für den Umgang mit Isomorphismen.
Ein Hindernis für die Annahme von fclabels
ist, dass das Hauptpaket die Schablonen-Haskell-Installation enthält, sodass das Paket nicht Haskell 98 ist und auch die (ziemlich unumstrittene) TypeOperators
Erweiterung erfordert .
Datenzugriff
[Bearbeiten: data-accessor
verwendet 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 get
den 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 Category
Instanz 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 Category
Instanz 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 Category
Instanz, 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-lens
Paket 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 fclabels
Definition, 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 l
in 2 schön punktfreie Äquivalente:
extract . l = id
duplicate . l = fmap l . l
Dieser Ansatz wurde erstmals in Russell O'Connors "to Functor
is Lens
as Applicative
to 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-lens
Form a Category
(im Gegensatz zum lenses
Paket) sind also Haskell 98 (im Gegensatz zu fclabels
/ lenses
) und sind vernünftig (im Gegensatz zum hinteren Ende von)data-accessor
data-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 id
auch ohne Category
Instanz 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 Lens
ich oben für definiert habe, _2
ist 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 lens
Paket 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.
lens
verfü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.