lenti, fclabels, data-accessor – quale libreria per l’accesso alla struttura e la mutazione è migliore

Esistono almeno tre librerie popolari per accedere e manipolare i campi dei record. Quelle che conosco sono: accessor ai dati, fclabels e lenti.

Personalmente ho iniziato con l’accesso ai dati e ora li sto utilizzando. Tuttavia, di recente su haskell-cafe c’era un’opinione che le fclabel fossero superiori.

Quindi sono interessato al confronto di quelle tre (e forse più) librerie.

Ci sono almeno 4 librerie che sono a conoscenza della fornitura di obiettivi.

La nozione di objective è che fornisce qualcosa di isomorfo

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

fornendo due funzioni: un getter e un setter

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

sobject a tre leggi:

Innanzitutto, se metti qualcosa, puoi recuperarlo

 get l (put lba) = b 

In secondo luogo, ottenere e quindi impostare non cambia la risposta

 put l (get la) a = a 

E terzo, mettere due volte è come mettere una volta, o meglio, che la seconda mossa vince.

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

Nota che il sistema di tipi non è sufficiente per controllare queste leggi per te, quindi devi assicurartelo indipendentemente dall’implementazione dell’objective che usi.

Molte di queste librerie offrono anche una serie di combinatori aggiuntivi in ​​cima, e di solito una qualche forma di macchine 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 obiettivi, perché a :-> b può essere tradotto direttamente nel tipo precedente. Fornisce un’istanza di categoria per (:->) che è utile in quanto consente di comporre obiettivi. Fornisce anche un tipo Point senza legge che generalizza la nozione di un objective usato qui, e alcuni impianti idraulici per affrontare gli isomorfismi.

Un ostacolo all’adozione di fclabels è che il pacchetto principale include il plumbing template-haskell, quindi il pacchetto non è Haskell 98, e richiede anche l’estensione (abbastanza controversa) di TypeOperators .

i dati di accesso-

[Modifica: data-accessor non utilizza più questa rappresentazione, ma è stata spostata in una forma simile a quella data-lens dei data-lens . Sto mantenendo questo commento, però.]

data-accessor è un po ‘più popolare di fclabels , in parte perché è Haskell 98. Tuttavia, la sua scelta di rappresentazione interna mi fa vomitare un po’ nella mia bocca.

Il tipo T che usa per rappresentare un objective è definito internamente come

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

Di conseguenza, per get il valore di un objective, devi presentare un valore non definito per l’argomento ‘a’! Questo mi sembra un’implementazione incredibilmente brutta e ad hoc.

Detto questo, Henning ha incluso l’impianto idraulico di template-haskell per generare automaticamente gli accessor per te in un pacchetto separato ” data-accessor-template “.

Ha il vantaggio di una serie decentemente ampia di pacchetti che già la impiegano, essendo Haskell 98 e fornendo l’importantissima istanza di Category , quindi se non si presta attenzione a come viene fatta la salsiccia, questo pacchetto è in realtà abbastanza ragionevole scelta.

lenti

Successivamente, vi è il pacchetto di lenti , che osserva che una lente può fornire un monomomodo di stato tra due monade di stato, definendo direttamente le lenti come tali omomorfismi di monade.

Se in realtà si prendesse la briga di fornire un tipo per i suoi obiettivi, avrebbero un tipo rank-2 come:

 newtype Lens st = Lens (forall a. State ta -> State sa) 

Di conseguenza, preferisco che questo approccio non mi piaccia, poiché ti porta fuori inutilmente da Haskell 98 (se vuoi che un tipo fornisca le tue lenti in astratto) e ti privi dell’istanza Category per gli obiettivi, che permetterebbe li componi con . . L’implementazione richiede anche classi di tipi a più parametri.

Nota, tutte le altre librerie di obiettivi menzionate qui forniscono un qualche combinatore o possono essere utilizzate per fornire questo stesso effetto di focalizzazione dello stato, quindi non si ottiene nulla codificando l’objective direttamente in questo modo.

Inoltre, le condizioni laterali dichiarate all’inizio non hanno una bella espressione in questa forma. Come con ‘fclabels’ questo fornisce il metodo template-haskell per generare automaticamente obiettivi per un tipo di record direttamente nel pacchetto principale.

A causa della mancanza dell’istanza di Category , della codifica barocca e del requisito di template-haskell nel pacchetto principale, questa è la mia implementazione preferita.

Dati-lens

[Modifica: dalla versione 1.8.0, questi sono passati dal pacchetto di trasformatori di comonad all’objective di dati]

Il mio pacchetto di lenti per data-lens fornisce obiettivi in ​​termini di comonad Store .

 newtype Lens ab = Lens (a -> Store ba) 

dove

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

Espanso questo è equivalente a

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

Puoi vedere questo come considerare l’argomento comune dal getter e dal setter per restituire una coppia consistente nel risultato del recupero dell’elemento, e un setter in cui inserire un nuovo valore. Ciò offre il vantaggio computazionale che il ‘setter’ qui è ansible riciclare parte del lavoro utilizzato per ottenere il valore, rendendo necessaria un’operazione di modifica più efficiente rispetto alla definizione di fclabels , in particolare quando gli accessor sono concatenati.

C’è anche una buona giustificazione teorica per questa rappresentazione, perché il sottoinsieme dei valori di “Lente” che soddisfano le 3 leggi dichiarate all’inizio di questa risposta sono precisamente quegli obiettivi per i quali la funzione avvolta è una “comonad coalgebra” per il negozio comonad . Questo trasforma 3 leggi pelose per una lente l fino a 2 equivalenti piacevolmente senza punta:

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

Questo approccio è stato notato e descritto per la prima volta in Russell O’Connor. Functor è per Lens come Applicative è per Biplate : Introducendo Multiplate ed è stato Biplate come un Biplate basato su un preprint di Jeremy Gibbons.

Include anche un numero di combinatori per lavorare con obiettivi rigorosamente e alcune lenti per contenitori, come Data.Map .

Quindi le lenti in objective data-lens formano una Category (a differenza del pacchetto lenses ), sono Haskell 98 (a differenza di fclabels / lenses ), sono sensate (a differenza del back-end dell’accessorio data-accessor ) e forniscono un’implementazione leggermente più efficiente, data-lens-fd fornisce le funzionalità per lavorare con MonadState per coloro che desiderano uscire da Haskell 98, e le macchine template-haskell sono ora disponibili tramite data-lens-template .

Aggiornamento del 28/06/2012: Altre strategie di implementazione della lente

Lenti isomorfismo

Ci sono altre due codifiche della lente che vale la pena considerare. Il primo offre un buon modo teorico per visualizzare un objective come un modo per rompere una struttura nel valore del campo e “tutto il resto”.

Dato un tipo per isomorfismi

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

tale che i membri validi soddisfino hither . yon = id hither . yon = id e yon . hither = id yon . hither = id

Possiamo rappresentare un objective con:

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

Questi sono principalmente utili come un modo per pensare al significato degli obiettivi, e possiamo usarli come uno strumento di ragionamento per spiegare altri obiettivi.

Lenti van Laarhoven

Possiamo modellare gli obiettivi in ​​modo tale che possano essere composti con (.) E id , anche senza un’istanza di Category usando

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

come il tipo per i nostri obiettivi.

Quindi definire un objective è facile come:

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

e puoi verificare da solo che la composizione della funzione è la composizione dell’objective.

Ho recentemente scritto su come è ansible generalizzare ulteriormente gli obiettivi di van Laarhoven per ottenere famiglie di obiettivi che possono cambiare il tipo di campi, semplicemente generalizzando questa firma a

 type LensFamily abcd = forall f. Functor f => (c -> fd) -> a -> fb 

Questo ha la sfortunata conseguenza che il modo migliore per parlare di obiettivi è usare il polimorfismo di grado 2, ma non è necessario usare quella firma direttamente quando si definiscono gli obiettivi.

L’ Lens I definito sopra per _2 è in realtà una LensFamily .

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

Ho scritto una libreria che include obiettivi, famiglie di lenti e altre generalizzazioni tra cui getter, setter, pieghe e attraversamenti. È disponibile su Hackage come pacchetto di lens .

Ancora una volta, un grande vantaggio di questo approccio è che i manutentori di librerie possono effettivamente creare obiettivi in ​​questo stile nelle librerie senza incorrere in alcuna dipendenza della libreria di obiettivi, fornendo semplicemente funzioni con tipo Functor f => (b -> fb) -> a -> fa , 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, ci vuole molta pressione sulle mie precedenti preoccupazioni riguardo al mantenimento della libreria Haskell 98.