Generic LensLike 像映射和遍历

Generic LensLike like mapped and traverse

假设我想为 MaybeT m a:

的内容创建一个 "optic"

maybeTContents = _Wrapped .something. _Just

有这样的吗something?

maybeTContents 例如 Traversalm[] 时, 但只有 Setterm(->) Int.

用法示例:

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

是的!首先要注意的是 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] uSetter' (Reader u) u 统一的 setter?

但是,由于 []Reader 都有仿函数实例,我们可以转向 绝对经典 setter mappedthe setter heard around the worldmapped 的类型为 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

由于 ReaderT 没有 Show 实例,我能做的最好的事情就是说明 setter 的工作是生成两个全新的类型 AB.

我认为这个问题很好,因为它是 lens 包背后动机的核心。给定来自 Traversable 世界的 fmapDefault,您可以将遍历固定为 Identity 以写入 over。然后,您可以编写 oversets 的反函数,使得 over . sets = idsets . over = id。然后我们被迫得出结论,mapped = sets fmap 是一个自然的 setter,它服从我们想要的 setters 的那种法则,其中最重要的是 mapped . mapped . mapped(.)lens 的其余部分很快就会跟进。

一种方法是制作您自己的 class,为您使用的类型提供正确的光学元件:

{-# 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 提供 setter 并为 [] 等提供遍历

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

你也可以为MaybeTReaderT写一个实例:

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。为此,您需要在 Ocular class 中包含 Profuctor。您还可以编写一个允许索引镜头和遍历这种方式的版本。

基于以前答案的几个例子:

> 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,对于 Settercollect(或 mapped)。

不幸的是 TraversableDistributive 没有所有实例: (->) r 不是 Traversable 并且 Const 不是 Distributive(他们不可能是,AFAICS)。

如果你仔细想想,你就会发现这是有道理的。 TraversalDistributive 是对偶,对于 traverse 的 "go in other direction" 我们使用 collect.