2016-06-22 10 views
19

Supponiamo ho voluto creare un "ottica" per i contenuti dei MaybeT m a:Generico LensLike come mappato e attraversare

maybeTContents = _Wrapped .something. _Just

v'è una tale something?

maybeTContents sarebbe ad esempio tramite un Traversal quando m è [], ma solo quando Setterm è (->) Int. utilizzo

Esempio:

+0

@haoformayor per quanto riguarda la modifica del titolo - 'MaybeT' è solo un esempio di quello che sto cercando. speriamo che il nuovo titolo spieghi meglio cosa sto cercando – yairchu

+2

Vedi anche https://github.com/ekmett/lens/wiki/Varying-lens-properties-by-instance – phadej

risposta

5

Un modo per farlo è quello di rendere la propria classe che dà l'ottica giusta per il tipo di vostro usando:

{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE RankNTypes    #-} 

class Ocular f t where 
    optic :: LensLike f (t a) (t b) a b 

instance Settable f => Ocular f ((->) a) where 
    optic = mapped 

instance Functor f => Ocular f Identity where 
    optic = _Wrapped 

instance Applicative f => Ocular f [] where 
    optic = traversed 

instance Applicative f => Ocular f Maybe where 
    optic = _Just 

Questo darà un setter per (->) s e un attraversamento per [] ecc

> let maybeTContents = _Wrapped . optic . _Just 
> MaybeT [Just 1, Nothing, Just 2] ^.. maybeTContents 
[1,2] 
> runMaybeT (MaybeT (Just . ('e':)) & maybeTContents %~ ('H':)) "llo" 
Just "Hello" 

Si può anche scrivere un'istanza per MaybeT e ReaderT:

instance (Applicative f, Ocular f m) => Ocular f (MaybeT m) where 
    optic = _Wrapped . optic . _Just 


instance (Ocular f m, Settable f) => Ocular f (ReaderT r m) where 
    optic = _Wrapped . mapped . optic 

> MaybeT [Just 1, Nothing, Just 2] ^.. optic 
[1,2] 
> runReaderT (ReaderT (\r -> [r,r+1]) & optic *~ 2) 1 
[2,4] 

Si noti che il caso Identity è solo uno Lens, non uno Iso. Per questo è necessario includere lo Profuctor nella classe Ocular. È anche possibile scrivere una versione che consenta l'uso di lenti e attraversamenti indicizzati in questo modo.

+1

Una versione migliore di questo è usare un tipo associato alla classe per il vincolo, con un membro che dimostra che l'ottica è almeno un minimo livello di ottica. Difficile da fare in un commento di 400 caratteri in cui non riesco a formattare il codice, ma 'classe HasFoo e dove {type T e :: * -> Constraint; foo ':: T e f => LensLike' f e Foo; fooIsALens :: T e f: - Functor f} 'Quindi puoi definire' foo :: HasFoo e => Lens 'e Foo' manipolando il vincolo. Nei casi monomorfi si procede come prima w/'foo'', ma ora si può ragionevolmente usare' pippo' parametricamente senza fuoriuscire ogni intermedio 'f' nella propria firma. –

+0

@EdwardKMETT - Ecco cosa ho ottenuto finora: https://github.com/lamdu/lamdu/blob/master/bottlelib/Data/Traversable/Generalized.hs quasi come hai descritto ma con 'Functor f' nei limiti dell'obiettivo invece di 'fooIsALens' – yairchu

+1

@EdwardKMETT ho avuto un tentativo, è questo che intendi? http://lpaste.net/168558 Non puoi usare i sinonimi di tipo per ottenere un effetto simile (ad esempio: SetFoo = HasFoo Identity')? – cchalmers

8

Sì! La prima cosa da notare è che something deve avere il tipo Setter (e, senza perdita di generalità, Setter'). Per quanto riguarda il tipo usiamo i buchi.

maybeTContents :: Setter' (MaybeT m a) a 
maybeTContents = 
    _Wrapped . _ . _Just 

GHC ci dice che vuole digitare Settable f => (Maybe a -> f (Maybe a)) -> (m (Maybe a) -> f (m (Maybe a)) per il buco.

Con un viaggio su Hackage riconosciamo questo tipo come Setter' (m (Maybe a)) (Maybe a). Quindi, risolvendo u ~ Maybe a, possiamo riformulare la domanda più in generale: esiste un setter che si unifica sia con Setter' [u] u sia con Setter' (Reader u) u?

Ma, sia come [] e Reader hanno casi functor possiamo rivolgerci a un assoluto classico di un setter mapped, the setter heard around the world. mapped ha il tipo mapped :: Functor f => Setter (f a) (f b) a b - si scopre quando è disponibile un'istanza di functor che mapped = sets fmap è il valore che obbedisce a tutte le leggi setter.

Possiamo vedere in azione qui:

% stack ghci 
GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help 
Ok, modules loaded: none. 
λ> import Control.Lens 
λ> import Control.Monad.Trans.Maybe 
λ> import Control.Monad.Trans.Reader 
λ> MaybeT [Just 1, Nothing, Just 2, Nothing, Just 3] & _Wrapped . mapped . _Just .~ 100 
MaybeT [Just 100,Nothing,Just 100,Nothing,Just 100] 
λ> data A = A 
λ> data B = B 
λ> :t MaybeT (ReaderT (\r -> Identity (Just A))) 
MaybeT (ReaderT (\r -> Identity (Just A))) 
    :: MaybeT (ReaderT r Identity) A 
λ> :t MaybeT (ReaderT (\r -> Identity (Just A))) & _Wrapped . mapped . _Just .~ B 
MaybeT (ReaderT (\r -> Identity (Just A))) & _Wrapped . mapped . _Just .~ B 
    :: MaybeT (ReaderT r Identity) B 

Poiché non vi era alcuna Show istanza per ReaderT il meglio che potessi fare per illustrare che il setter ha lavorato è stato quello di generare due tipi di marca-spankin'-nuovi A e B.

Questa domanda è fantastica, perché è al centro della motivazione alla base del pacchetto lens. Dato fmapDefault dal mondo Traversable, è possibile correggere il traversable per essere Identity per scrivere over. È quindi possibile scrivere l'inverso di over, sets, ad esempio over . sets = id e sets . over = id. Siamo quindi costretti a concludere che mapped = sets fmap è un setter naturale che obbedisce al tipo di leggi che vogliamo per i setter, uno dei più importanti è che mapped . mapped . mapped compone con (.). Il resto di lens segue presto.

+1

Con 'something = mapped', potresti fare? Forse [Solo 1, Niente, Solo 2]^.. forse Contenuti'? (Ho appena aggiornato la mia domanda per avere questo esempio) – yairchu

+1

@yairchu Sì. Tutti i setter sono anche traversali. (Vedi: il gigantesco diagramma UML sulla pagina Hackage dell'obiettivo.) – hao

+1

non sembra funzionare. ': t MaybeT [Just 'a', Nothing, Just 'b']^.. _Wrapped. mappato _Just' restituisce un errore 'Nessuna istanza per (Impostabile (Const (Data.Monoid.Endo [Char]))) derivante da un uso di 'mappato'' ... – yairchu

1

Pochi esempi basati sulle risposte precedenti:

> MaybeT [Just 1, Nothing, Just 2] ^.. _Wrapped . traverse . _Just 
[1,2] 
> runMaybeT (MaybeT (Just . ('e':)) & _Wrapped . collect . _Just %~ ('H':)) "llo" 
Just "Hello" 

vale a dire per Traversal/Fold usiamo traverse, per Setter: collect (o mapped).

Purtroppo Traversable e Distributive non hanno tutte le istanze: (->) r non è Traversable e Const non è Distributive (e non può essere, AFAICS).

Se pensi a questo, vedi che ha senso. Traversal e Distributive sono doppi, se per "andare nell'altra direzione" di traverse usiamo collect.