Общий объектив, похожий на отображение и траверс
Предположим, я хотел создать "опцию" для содержимого MaybeT m a
:
maybeTContents = _Wrapped .
something
. _Just
Есть ли такой something
?
maybeTContents
будет, например, быть Traversal
, когда m
есть []
,
но только a Setter
, когда m
есть (->) Int
.
Пример использования:
> MaybeT [Just 1, Nothing, Just 2] ^.. maybeTContents
[1, 2]
> runMaybeT (MaybeT (Just . ('e':)) & maybeTContents %~ ('H':)) "llo"
Just "Hello"
Ответы
Ответ 1
Один из способов сделать это - сделать свой собственный класс, который дает правильную оптику для используемого вами типа:
{-# 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
Это даст сеттер для (->) s
и обход для []
и т.д.
> let maybeTContents = _Wrapped . optic . _Just
> MaybeT [Just 1, Nothing, Just 2] ^.. maybeTContents
[1,2]
> runMaybeT (MaybeT (Just . ('e':)) & maybeTContents %~ ('H':)) "llo"
Just "Hello"
Вы также можете записать экземпляр для MaybeT
и 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]
Обратите внимание, что случай Identity
- это только Lens
, а не Iso
. Для этого вам нужно включить Profuctor
в класс Ocular
. Вы также можете написать версию, которая позволяет индексировать объектив и обходы таким образом.
Ответ 2
Да! Прежде всего следует отметить, что something
должен иметь тип Setter
(и, не ограничивая общности, Setter'
). Что касается того, какой тип использовать отверстия.
maybeTContents :: Setter' (MaybeT m a) a
maybeTContents =
_Wrapped . _ . _Just
GHC говорит нам, что хочет тип Settable f => (Maybe a -> f (Maybe a)) -> (m (Maybe a) -> f (m (Maybe a))
для отверстия.
С поездкой в Hackage мы распознаем этот тип как Setter' (m (Maybe a)) (Maybe a)
. Итак, фиксируя u ~ Maybe a
, мы можем более подробно перефразировать вопрос: существует ли сеттер, который объединяется с Setter' [u] u
и Setter' (Reader u) u
?
Но, поскольку оба []
и Reader
имеют экземпляры functor, мы можем обратиться к абсолютному классику setter mapped
, сеттер, услышанный по всему миру. mapped
имеет тип mapped :: Functor f => Setter (f a) (f b) a b
- получается, когда у вас есть экземпляр-функтор, который mapped = sets fmap
является значением, которое подчиняется всем законам setter.
Мы видим это в действии здесь:
% 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
Поскольку не было экземпляра Show
для ReaderT
, лучшее, что я мог бы сделать, чтобы проиллюстрировать, что работающий сеттер работал, состоял в том, чтобы создать два типа brand-spankin'-new A
и B
.
Этот вопрос велик, я думаю, потому что он лежит в основе мотивации пакета lens
. Учитывая fmapDefault
из мира Traversable
, вы можете зафиксировать пройденный путь Identity
для записи over
. Затем вы можете записать инверсию over
, sets
, такую, что over . sets = id
и sets . over = id
. Затем мы вынуждены заключить, что mapped = sets fmap
является естественным сеттером, который подчиняется законам, которые мы хотим для сеттеров, одним из наиболее важных из которых является mapped . mapped . mapped
с (.)
. Остальная часть lens
скоро следует.
Ответ 3
Несколько примеров, основанных на предыдущих ответах:
> MaybeT [Just 1, Nothing, Just 2] ^.. _Wrapped . traverse . _Just
[1,2]
> runMaybeT (MaybeT (Just . ('e':)) & _Wrapped . collect . _Just %~ ('H':)) "llo"
Just "Hello"
т.е. для Traversal
/Fold
мы используем traverse
для Setter
: collect
(или mapped
).
К сожалению, Traversable
и Distributive
не имеют всех экземпляров:
(->) r
не Traversable
и Const
не Distributive
(и они не могут быть AFAICS).
Если вы подумаете об этом, вы увидите, что это имеет смысл. Traversal
и Distributive
являются дуальными, se, чтобы "идти в другом направлении" traverse
, мы используем collect
.