haskell lens

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.