haskell - Lens genérico como mapeado y transversal
(3)
Supongamos que quisiera crear una "óptica" para los contenidos de MaybeT ma
:
maybeTContents = _Wrapped .
something
. _Just
¿Hay something
?
maybeTContents
vez los contenidos maybeTContents
, por ejemplo, un Traversal
cuando m
es []
, pero solo un Setter
cuando m
es (->) Int
.
Ejemplo de uso:
> MaybeT [Just 1, Nothing, Just 2] ^.. maybeTContents
[1, 2]
> runMaybeT (MaybeT (Just . (''e'':)) & maybeTContents %~ (''H'':)) "llo"
Just "Hello"
¡Sí! Lo primero a tener en cuenta es que something
debe tener el tipo Setter
(y, sin pérdida de generalidad, Setter''
). En cuanto a qué tipo vamos a utilizar agujeros.
maybeTContents :: Setter'' (MaybeT m a) a
maybeTContents =
_Wrapped . _ . _Just
GHC nos dice que quiere el tipo Settable f => (Maybe a -> f (Maybe a)) -> (m (Maybe a) -> f (m (Maybe a))
para el agujero.
Con un viaje a Hackage, reconocemos este tipo como Setter'' (m (Maybe a)) (Maybe a)
. Entonces, arreglando u ~ Maybe a
, podemos reformular la pregunta de manera más general: ¿existe un setter que se unifique con Setter'' [u] u
existe y Setter'' (Reader u) u
?
Pero, dado que tanto []
como Reader
tienen instancias de funtor, podemos recurrir a un clásico absoluto de un setter mapped
, el setter escuchado en todo el mundo . mapped
tiene el tipo mapped :: Functor f => Setter (fa) (fb) ab
- resulta que cuando tiene una instancia de functor disponible que mapped = sets fmap
es el valor que obedece a todas las leyes de los mapped = sets fmap
.
Podemos ver esto en acción aquí:
% 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
Como no había una instancia de Show
para ReaderT
lo mejor que pude hacer para ilustrar que el setter funcionó fue generar dos tipos A
y B
nuevos y flamantes.
Creo que esta pregunta es excelente porque se encuentra en el centro de la motivación detrás del paquete de lens
. Dado fmapDefault
del mundo Traversable
, puede corregir lo transable que sea Identity
para fmapDefault
. A continuación, puede escribir el inverso de over
, sets
, tal que over . sets = id
over . sets = id
y sets . over = id
sets . over = id
. Luego nos vemos obligados a concluir que mapped = sets fmap
es un setter natural que obedece el tipo de leyes que queremos para los setters, uno de los más importantes es el mapped . mapped . mapped
mapped . mapped . mapped
mapped . mapped . mapped
compone con (.)
. El resto de la lens
pronto sigue.
Pocos ejemplos basados en respuestas anteriores:
> MaybeT [Just 1, Nothing, Just 2] ^.. _Wrapped . traverse . _Just
[1,2]
> runMaybeT (MaybeT (Just . (''e'':)) & _Wrapped . collect . _Just %~ (''H'':)) "llo"
Just "Hello"
Por ejemplo, para Traversal
/ Fold
usamos el traverse
, para Setter
: collect
(o mapped
).
Desafortunadamente, Traversable
y Distributive
no tienen todas las instancias: (->) r
no es Traversable
y Const
no es Distributive
(y no pueden ser, AFAICS).
Si piensas en esto, ves que tiene sentido. Traversal
y Distributive
son duales, para "ir en otra dirección" del traverse
que usamos por collect
.
Una forma de hacer esto es crear su propia clase que ofrezca la óptica adecuada para el tipo que usa:
{-# 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
Esto le dará un setter para (->) s
un recorrido para []
etc.
> let maybeTContents = _Wrapped . optic . _Just
> MaybeT [Just 1, Nothing, Just 2] ^.. maybeTContents
[1,2]
> runMaybeT (MaybeT (Just . (''e'':)) & maybeTContents %~ (''H'':)) "llo"
Just "Hello"
También puedes escribir una instancia para MaybeT
y 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]
Tenga en cuenta que el caso de Identity
es solo un Lens
, no un Iso
. Para eso necesitas incluir el Profuctor
en la clase Ocular
. También puede escribir una versión que permita lentes indexadas y transversales de esta manera.