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.