Ci sono almeno 4 librerie di cui sono a conoscenza per fornire obiettivi.
L'idea di una lente è che fornisce qualcosa di isomorfo
data Lens a b = Lens (a -> b) (b -> a -> a)
fornendo due funzioni: un getter e un setter
get (Lens g _) = g
put (Lens _ s) = s
soggetto a tre leggi:
Innanzitutto, se metti qualcosa, puoi recuperarlo
get l (put l b a) = b
Secondo che ottenere e quindi impostare non cambia la risposta
put l (get l a) a = a
E terzo, mettere due volte è lo stesso di mettere una volta, o meglio, che vince il secondo.
put l b1 (put l b2 a) = put l b1 a
Tieni presente che il sistema di tipi non è sufficiente per verificare queste leggi per te, quindi devi assicurarle tu stesso indipendentemente dall'implementazione dell'obiettivo che usi.
Molte di queste librerie forniscono anche un sacco di combinatori extra in cima, e di solito qualche forma di macchinario haskell modello per generare automaticamente obiettivi per i campi di tipi di record semplici.
Con questo in mente, possiamo rivolgerci alle diverse implementazioni:
implementazioni
fclabels
fclabels è forse il più facilmente ragionato delle librerie di lenti, perché a :-> b
può essere tradotto direttamente nel tipo sopra. Fornisce un'istanza di categoria per la (:->)
quale è utile in quanto consente di comporre obiettivi. Fornisce anche un Point
tipo senza legge che generalizza la nozione di una lente usata qui, e alcuni impianti idraulici per trattare gli isomorfismi.
Un ostacolo all'adozione di fclabels
è che il pacchetto principale include l'idraulica template-haskell, quindi il pacchetto non è Haskell 98 e richiede anche l' TypeOperators
estensione (abbastanza non controversa) .
i dati di accesso-
[Modifica: data-accessor
non utilizza più questa rappresentazione, ma si è spostato in una forma simile a quella di data-lens
. Sto mantenendo questo commento, però.]
l'accesso ai dati è un po 'più popolare di fclabels
, in parte perché è Haskell 98. Tuttavia, la sua scelta di rappresentazione interna mi fa vomitare un po' la bocca.
Il tipo T
che utilizza per rappresentare un obiettivo è internamente definito come
newtype T r a = Cons { decons :: a -> r -> (a, r) }
Di conseguenza, per get
il valore di un obiettivo, è necessario inviare un valore indefinito per l'argomento "a"! Questo mi sembra un'implementazione incredibilmente brutta e ad hoc.
Detto questo, Henning ha incluso l'idraulico template-haskell per generare automaticamente gli accessor per te in un pacchetto separato ' data-accessor-template '.
Ha il vantaggio di un set decentemente ampio di pacchetti che già lo utilizzano, essendo Haskell 98 e fornendo l' Category
istanza fondamentale, quindi se non presti attenzione a come viene prodotta la salsiccia, questo pacchetto è in realtà una scelta abbastanza ragionevole .
lenti a contatto
Successivamente, c'è il pacchetto di lenti , che osserva che una lente può fornire un omomorfismo di monade di stato tra due monadi di stato, definendo le lenti direttamente come tali omomorfismi di monade.
Se si preoccupasse effettivamente di fornire un tipo per i suoi obiettivi, avrebbero un tipo di grado 2 come:
newtype Lens s t = Lens (forall a. State t a -> State s a)
Di conseguenza, preferisco non mi piace questo approccio, poiché ti strappa inutilmente da Haskell 98 (se vuoi un tipo da fornire ai tuoi obiettivi in astratto) e ti priva Category
dell'istanza per gli obiettivi, che ti permetterebbe comporrli con .
. L'implementazione richiede anche classi di tipo multiparametro.
Nota, tutte le altre librerie di obiettivi menzionate qui forniscono alcuni combinatori o possono essere utilizzate per fornire lo stesso effetto di focalizzazione dello stato, quindi non si ottiene nulla codificando l'obiettivo direttamente in questo modo.
Inoltre, le condizioni secondarie dichiarate all'inizio non hanno davvero una bella espressione in questa forma. Come per "fclabels", questo fornisce il metodo template-haskell per generare automaticamente obiettivi per un tipo di record direttamente nel pacchetto principale.
A causa della mancanza di Category
istanza, della codifica barocca e del requisito di template-haskell nel pacchetto principale, questa è la mia implementazione meno preferita.
Dati-lens
[Modifica: a partire dalla 1.8.0, questi sono passati dal pacchetto trasformatori comonad a data-lens]
Il mio data-lens
pacchetto fornisce obiettivi in termini di comonad dello Store .
newtype Lens a b = Lens (a -> Store b a)
dove
data Store b a = Store (b -> a) b
Espanso questo equivale a
newtype Lens a b = Lens (a -> (b, b -> a))
Puoi vederlo come fattorizzare l'argomento comune dal getter e dal setter per restituire una coppia consistente nel risultato del recupero dell'elemento e un setter per riportare un nuovo valore. Questo offre il vantaggio computazionale che il 'setter' qui è possibile riciclare parte del lavoro utilizzato per ottenere il valore, rendendo l'operazione di modifica più efficiente rispetto alla fclabels
definizione, in particolare quando gli accessor sono concatenati.
Esiste anche una bella giustificazione teorica per questa rappresentazione, perché il sottoinsieme dei valori di "Lens" che soddisfano le 3 leggi dichiarate all'inizio di questa risposta sono proprio quegli obiettivi per i quali la funzione wrapped è una "coalgebra comune" per il comune negozio . Questo trasforma 3 leggi pelose per un obiettivo l
fino a 2 equivalenti ben privi di punti:
extract . l = id
duplicate . l = fmap l . l
Questo approccio è stato notato e descritto per la prima volta in Russell O'Connor Functor
è Lens
come Applicative
è Biplate
: Introduzione a Multiplate ed è stato scritto sul blog sulla base di una prestampa di Jeremy Gibbons.
Include anche una serie di combinatori per lavorare rigorosamente con obiettivi e alcuni obiettivi stock per contenitori, come ad esempio Data.Map
.
Quindi gli obiettivi in data-lens
forma a Category
(a differenza del lenses
pacchetto), sono Haskell 98 (a differenza di fclabels
/ lenses
), sono sani (a differenza del back-end di data-accessor
) e forniscono un'implementazione leggermente più efficiente, data-lens-fd
forniscono la funzionalità per lavorare con MonadState per coloro che vogliono uscire di Haskell 98 e il macchinario template-haskell è ora disponibile tramite data-lens-template
.
Aggiornamento 28/06/2012: altre strategie di implementazione dell'obiettivo
Lenti di isomorfismo
Ci sono altre due codifiche dell'obiettivo che vale la pena considerare. Il primo offre un buon modo teorico di vedere un obiettivo come un modo per spezzare una struttura nel valore del campo e "tutto il resto".
Dato un tipo di isomorfismi
data Iso a b = Iso { hither :: a -> b, yon :: b -> a }
tale che i membri validi soddisfino hither . yon = id
eyon . hither = id
Possiamo rappresentare un obiettivo con:
data Lens a b = forall c. Lens (Iso a (b,c))
Questi sono principalmente utili come modo di pensare al significato degli obiettivi e possiamo usarli come uno strumento di ragionamento per spiegare altri obiettivi.
van Laarhoven Lenti
Possiamo modellare obiettivi in modo tale che possano essere composti con (.)
e id
, anche senza Category
istanza, usando
type Lens a b = forall f. Functor f => (b -> f b) -> a -> f a
come il tipo per i nostri obiettivi.
Quindi definire un obiettivo è facile come:
_2 f (a,b) = (,) a <$> f b
e puoi confermare che la composizione della funzione è la composizione dell'obiettivo.
Di recente ho scritto su come generalizzare ulteriormente le lenti di Van Laarhoven per ottenere famiglie di lenti in grado di cambiare i tipi di campi, semplicemente generalizzando questa firma a
type LensFamily a b c d = forall f. Functor f => (c -> f d) -> a -> f b
Ciò ha la sfortunata conseguenza che il modo migliore per parlare delle lenti è usare il polimorfismo di grado 2, ma non è necessario usare quella firma direttamente per definire le lenti.
Il Lens
sopra definito per in _2
realtà è a LensFamily
.
_2 :: Functor f => (a -> f b) -> (c,a) -> f (c, b)
Ho scritto una biblioteca che include obiettivi, famiglie di obiettivi e altre generalizzazioni tra cui getter, setter, pieghe e traversali. È disponibile su hackage come lens
pacchetto.
Ancora una volta, un grande vantaggio di questo approccio è che i manutentori delle librerie possono effettivamente creare obiettivi con questo stile nelle vostre librerie senza incorrere in alcuna dipendenza dalle librerie di obiettivi, fornendo semplicemente funzioni con type Functor f => (b -> f b) -> a -> f a
, per i loro particolari tipi 'a' e 'b'. Ciò riduce notevolmente i costi di adozione.
Dal momento che non è necessario utilizzare effettivamente il pacchetto per definire nuovi obiettivi, toglie molta pressione alle mie precedenti preoccupazioni sulla conservazione della libreria Haskell 98.
lens
pacchetto ha la più ricca funzionalità e documentazione, quindi se non ti dispiace la sua complessità e dipendenze, è la strada da percorrere.