Control.Lens.Traversal的partsOf,holesOf和singular的简单定义是什么?

时间:2017-06-01 14:35:51

标签: haskell lens

我试图了解有关镜头库的更多信息。我已经了解了lens-family包中的镜头及其推导,并且还掌握了Store,Pretext和Bazaar的两个类型参数版本,但我无法理解Control.Lens.Traversal' s partsOfholesOfsingular函数,它们由复杂类型和许多辅助函数定义。这些功能是否也可以用更简单的方式表达?

1 个答案:

答案 0 :(得分:5)

这是一个相当大而棘手的问题。我自称我自己并不完全理解holesOfpartsOf是如何工作的,而我在几分钟之前并不理解singular是如何工作的,但我想写给你一个可能有帮助的答案。

我想解决一个更普遍的问题:如何阅读lens源代码。因为如果你记住几个简化的假设,你通常可以简化疯狂的定义,如

singular :: (Conjoined p, Functor f)
         => Traversing p f s t a a
         -> Over p f s t a a
singular l = conjoined
  (\afb s -> let b = l sell s in case ins b of
    (w:ws) -> unsafeOuts b . (:ws) <$> afb w
    []     -> unsafeOuts b . return <$> afb (error "singular: empty traversal"))
  (\pafb s -> let b = l sell s in case pins b of
    (w:ws) -> unsafeOuts b . (:Prelude.map extract ws) <$> cosieve pafb w
    []     -> unsafeOuts b . return                    <$> cosieve pafb (error "singular: empty traversal"))

unsafeOuts :: (Bizarre p w, Corepresentable p) => w a b t -> [b] -> t
unsafeOuts = evalState `rmap` bazaar (cotabulate (\_ -> state (unconsWithDefault fakeVal)))
  where fakeVal = error "unsafePartsOf': not enough elements were supplied"

ins :: Bizarre (->) w => w a b t -> [a]
ins = toListOf (getting bazaar)

unconsWithDefault :: a -> [a] -> (a,[a])
unconsWithDefault d []     = (d,[])
unconsWithDefault _ (x:xs) = (x,xs)

但我自己走在了前面

这些是我在阅读lens源代码时尝试应用的规则:

Dumber Optics

光学通常遵循整个库中的s-t-a-b形式,这允许您修改&#34;目标&#34;的类型。 (一个重载的词,充其量)。但是,只有sa才能实现许多光学器件,当您尝试使用tb时,通常无需跟踪singular{-# LANGUAGE RankNTypes #-} {-# LANGUAGE NoImplicitPrelude #-} import BasePrelude hiding (fold) type Lens big small = forall f. (Functor f) => (small -> f small) -> (big -> f big) type Traversal big small = forall ap. (Applicative ap) => (small -> ap small) -> (big -> ap big) makeLens :: (big -> small) -> (big -> small -> big) -> Lens big small makeLens getter setter = \liftSmall big -> setter big <$> liftSmall (getter big) s阅读定义。

例如,当我尝试逆向工程set :: ((small -> Identity small) -> big -> Identity big) -> small -> big -> big set setter new big = runIdentity (setter (\_ -> Identity new) big) view :: ((small -> Const small small) -> big -> Const small big) -> big -> small view getter big = getConst (getter Const big) 时,我在我的临时文件中使用了这些类型:

lens

组合器看起来像这样:

Choice

离开这里,索引和棱镜

作为镜头的消费者,棱镜和索引光学器件非常有用,但是它们对一些更令人眼前一亮的代码负责。为了统一棱镜和索引光学,Conjoined开发人员使用 profunctors (例如dimaprmap)及其伴随辅助函数(lensp ~ (->))。

在阅读Representable代码时,每当我看到一个profunctor变量时,我发现几乎总是假设Conjoined(函数类型)很有帮助。这样,我就可以从上面代码段中的签名中删除BizarreOversingularsingular :: Traversal big small -> Lens big small singular = _ 类型类。

许多类型的洞

有了这个以及GHC类型漏洞的帮助,我们可以开始尝试在我们更简单的数字类型上构建我们自己的big

[small]

作为alluded to briefly on this comonad.com's blog post的一般策略是使用Const遍历State值以获取小符号列表(toListOf),然后将它们放置返回我们使用toListOf :: Traversal big small -> big -> [small] toListOf traversal = foldrOf traversal (:) [] -- | foldMapOf with mappend/mzero inlined foldrOf :: Traversal big small -> (small -> r -> r) -> r -> big -> r foldrOf traversal fold zero = \big -> appEndo (foldMapOf traversal (Endo . fold) big) zero -- | Traverses a value of type big, accumulating the result in monoid mon foldMapOf :: Monoid mon => Traversal big small -> (small -> mon) -> big -> mon foldMapOf traversal fold = getConst . traversal (Const . fold) 获取它们。

遍历获取列表可以通过我们重新实现Endo

来完成
Const

这里有一个monoid的嵌套玩偶:来自singular :: Traversal big small -> Lens big small singular traversal liftSmall big = do case toListOf traversal big of (x:xs) -> _ [] -> _ s的unsafeOuts :: (Bizarre p w, Corepresentable p) => w a b t -> [b] -> t unsafeOuts = evalState `rmap` bazaar (cotabulate (\_ -> state (unconsWithDefault fakeVal))) where fakeVal = error "unsafePartsOf': not enough elements were supplied" 的列表。

现在我们有:

newtype Bazaar' small small' big =
  Bazaar { unBazaar :: forall ap. Applicative ap => (small -> ap small') -> ap big }
  deriving Functor

instance Applicative (Bazaar' small small') where
  pure big =
    Bazaar (\_ -> pure big)
  Bazaar lhs <*> Bazaar rhs =
    Bazaar (\liftSmall -> lhs liftSmall <*> rhs liftSmall)

type Bazaar small big = Bazaar' small small big

gobble :: StateT Identity [a] a
gobble = state (unconsWithDefault (error "empty!"))

unsafeOuts :: Bazaar small big -> [small] -> big
unsafeOuts (Bazaar bazaar) smalls =
  evalState (bazaar (\_ -> gobble)) smalls

将值放回是有点大脑弯曲。我们一直在避免谈论这种疯狂的功能:

rmap = (.)

在我们的简化世界中,成为

cotabulate f = f . Identity

我们在此处列出了p ~ (->)lens,并且我们能够这样做,因为我们假设Traversal

一个半心半意的尝试困扰集市

Bazaars很奇怪,似乎很少有关于它们的文章。 big文档提到它类似于已经应用于结构的遍历。事实上,如果您使用data FunList a b t = Done t | More a (FunList a b (b -> t)) instance Functor (FunList a b) where ... instance Applicative (FunList a b) where ... instance Profunctor (FunList a) where ... -- example values: -- * Done (x :: t) -- * More (a1 :: a) (Done (x :: a -> t)) -- * More (a1 :: a) (More (a2 :: a) (Done (x :: a -> a -> t)) 类型并将其应用于您已有的lens值,则会获得一个集市。

它也像fancy free applicative,但我不知道这是否有帮助或伤害。

last comment of this blog post about a seeming unrelated FunList datatype上,用户Zemyla计算出

之间的等价关系
gobble

bazaar Bazaar。我觉得这种表现方式对于直观地发生了什么更有帮助。

Dat State Monad

这里的宝石是gobble :: StateT Identity [small] small,它每次运行时从状态弹出列表的头部。我们的bazaar (\_ -> gobble) :: StateT Identity [small] big可以将bazaarOf :: Traversal big small -> big -> Bazaar small big bazaarOf traversal = traversal (\small -> Bazaar (\liftSmall -> liftSmall small)) -- See below for `ix`. λ> unBazaar (bazaarOf (ix 3) [1,2,3,4]) Right Right [1,2,3,4] λ> unBazaar (bazaarOf (ix 3) [1,2,3,4]) (\_ -> Right 10) Right [1,2,3,100] λ> unBazaar (bazaarOf (ix 1) [1,2,3,4]) Left Left 2 值升级为traverse。非常像遍历,我们能够采取有效的行动作用于一个小值的一部分,并将其升级为一个作用于整个价值的行动。这一切都很快发生,似乎代码不够;它让我头晕目眩。

(可能有用的东西是使用这个助手功能在GHCi中玩集市:

unsafeOuts

在简单的情况下,它似乎大约是一个延迟的&#34;版本big。)

无论如何

small为我们提供了一种方法,可以根据big值列表和从第一个singular :: Traversal big small -> Lens big small singular traversal liftSmall big = do let bazaar = traversal (\small -> Bazaar ($ small)) big case toListOf traversal big of (x:xs) -> _ [] -> _ 值构建的集市来检索第二个Bazaar small small值。现在我们需要从我们传入的原始遍历中构建一个集市:

big

我们在这里做两件事:

  • 首先我们给自己涂上x :: small。由于我们计划遍历Bazaar (\f -> f x) :: Bazaar small small,因此我们可以获取每个Bazaar small small值,并构建bazaar :: Bazaar small big。这就够了!

  • 然后,遍历类型会将lens平滑升级为b = traversal sell big

原始sell代码使用来自Sellable (->) (Bazaar (->))实例的x:xsx执行此操作。如果你内联这个定义,你应该得到相同的结果。

liftSmall x案例中,f small是我们想要采取行动的价值。它是我们给出的遍历所针对的第一个值,现在成为我们返回的镜头的第一个值。我们致电fxs获取一个仿函数f [small];然后我们在仿函数中附加unsafeOuts bazaar以获得f [small];然后我们在仿函数中调用f bigsingular :: Traversal big small -> Lens big small singular traversal liftSmall big = do let bazaar = traversal (\small -> Bazaar ($ small)) big case toListOf traversal big of (x:xs) -> fmap (\y -> unsafeOuts bazaar (y:xs)) <$> liftSmall x [] -> _ 转回singular :: Traversal big small -> Lens big small singular traversal liftSmall big = do let bazaar = traversal (\small -> Bazaar ($ small)) big case toListOf traversal big of (x:xs) -> fmap (\y -> unsafeOuts bazaar (y:xs)) <$> liftSmall x [] -> fmap (\y -> unsafeOuts bazaar [y]) <$> liftSmall (error "singularity")

-- | Constructs a Traversal that targets zero or one
makePrism :: (small -> big) -> (big -> Either big small) -> Traversal big small
makePrism constructor getter =
  \liftSmall big -> case (fmap liftSmall . getter) big of
    Left big' -> pure big'
    Right fsmall -> fmap constructor fsmall

_Cons :: Traversal [a] (a, [a])
_Cons = makePrism (uncurry (:)) (\case (x:xs) -> Right (x, xs); [] -> Left [])

_1 :: Lens (a, b) a
_1 = makeLens fst (\(_, b) a' -> (a', b))

_head :: Traversal [a] a
_head = _Cons . _1

ix :: Int -> Traversal [a] a
ix k liftSmall big =
  if k < 0 then pure big else go big k
  where
    go [] _ = pure []
    go (x:xs) 0 = (:xs) <$> liftSmall x
    go (x:xs) i = (x:) <$> go xs (i - 1)

如果列表为空,我们的行为方式相同,只是我们填写了一个底值:

lens

让我们定义一些基本光学元件,以便我们可以使用我们的定义:

Monoid

这些都是从λ> :t view _head view _head :: Monoid a => [a] -> a λ> :t view (singular _head) view (singular _head) :: [small] -> small λ> view _head [1,2,3,4] [snip] • Ambiguous type variable ‘a0’ arising from a use of ‘print’ prevents the constraint ‘(Show a0)’ from being solved. [snip] λ> view (singular _head) [1,2,3,4] 1 库中窃取的。

正如预期的那样,它可以帮助我们消除恼人的λ> set (ix 100) 50 [1,2,3] [1,2,3] λ> set (singular (ix 100)) 50 [1,2,3] [1,2,3] λ> set _head 50 [1,2,3,4] [50,2,3,4] λ> set (singular _head) 50 [1,2,3,4] [50,2,3,4] 类型类:

partsOf

它没有像预期的那样对setter做任何事情(因为遍历已经是setter):

holesOf

-- | A type-restricted version of 'partsOf' that can only be used with a 'Traversal'. partsOf' :: ATraversal s t a a -> Lens s t [a] [a] partsOf' l f s = outs b <$> f (ins b) where b = l sell s partsOf

singular

纯猜想如下:据我所知,bf (ins b)非常相似,因为它首先构建了一个集市holesOf :: forall p s t a. Conjoined p => Over p (Bazaar p a a) s t a a -> s -> [Pretext p a a t] holesOf l s = unTagged ( conjoined (Tagged $ let f [] _ = [] f (x:xs) g = Pretext (\xfy -> g . (:xs) <$> xfy x) : f xs (g . (x:)) in f (ins b) (unsafeOuts b)) (Tagged $ let f [] _ = [] f (wx:xs) g = Pretext (\wxfy -> g . (:Prelude.map extract xs) <$> cosieve wxfy wx) : f xs (g . (extract wx:)) in f (pins b) (unsafeOuts b)) :: Tagged (p a b) [Pretext p a a t] ) where b = l sell s ,在集市上调用holesOf ,然后&#34;将值放回到找到它的位置。&#34;

l sell s

p ~ (->)也是第三次进行集市(conjoined!)并且再次患有结膜炎:假设Pretext你可以删除{{1}}的第二个分支1}}。但是你留下了一堆{{1}}和comonads,我不完全确定它们是如何挂在一起的。值得进一步探索!

Here is a gist of all the code I had in my scratch file at the time I hit Submit on this wall of text.