obiettivi, etichette, accesso ai dati - quale libreria per l'accesso alla struttura e la mutazione è migliore


173

Esistono almeno tre librerie popolari per l'accesso e la manipolazione dei campi dei record. Quelli che conosco sono: accesso ai dati, etichette e obiettivi.

Personalmente ho iniziato con l'accesso ai dati e li sto usando ora. Tuttavia, di recente sul haskell-cafe c'era un'opinione che fclabels fosse superiore.

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


3
Ad oggi, il lenspacchetto ha la più ricca funzionalità e documentazione, quindi se non ti dispiace la sua complessità e dipendenze, è la strada da percorrere.
modulare

Risposte:


200

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 :-> bpuò essere tradotto direttamente nel tipo sopra. Fornisce un'istanza di categoria per la (:->)quale è utile in quanto consente di comporre obiettivi. Fornisce anche un Pointtipo 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' TypeOperatorsestensione (abbastanza non controversa) .

i dati di accesso-

[Modifica: data-accessornon 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 Tche utilizza per rappresentare un obiettivo è internamente definito come

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

Di conseguenza, per getil 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' Categoryistanza 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 Categorydell'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 Categoryistanza, 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-lenspacchetto 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 fclabelsdefinizione, 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 lfino 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è Lenscome 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-lensforma a Category(a differenza del lensespacchetto), 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-fdforniscono 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 = ideyon . 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 Categoryistanza, 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 Lenssopra definito per in _2realtà è 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 lenspacchetto.

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.


28
Mi piacciono le etichette per il suo approccio ottimista:->
Tener

3
Gli articoli Inessential Guide to data accessor e Inessential guide to fclabels potrebbero essere degni di nota
hvr

10
Essere compatibili con Haskell 1998 è importante? Perché facilita lo sviluppo del compilatore? E non dovremmo invece passare a parlare di Haskell 2010?
yairchu,

55
Oh no! Ero l'autore originale di data-accessor, e poi l'ho passato a Henning e ho smesso di prestare attenzione. Anche la a -> r -> (a,r)rappresentazione mi mette a disagio e la mia implementazione originale era proprio come il tuo Lenstipo. Heeennnninngg !!
luqui,

5
Yairchu: è principalmente così che la tua libreria potrebbe avere la possibilità di lavorare con un compilatore diverso da ghc. Nessun altro ha il modello Haskell. Il 2010 non aggiunge nulla di rilevante qui.
Edward KMETT,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.