我试图了解有关镜头库的更多信息。我已经了解了lens-family包中的镜头及其推导,并且还掌握了Store,Pretext和Bazaar的两个类型参数版本,但我无法理解Control.Lens.Traversal
' s partsOf
,holesOf
和singular
函数,它们由复杂类型和许多辅助函数定义。这些功能是否也可以用更简单的方式表达?
答案 0 :(得分:5)
这是一个相当大而棘手的问题。我自称我自己并不完全理解holesOf
和partsOf
是如何工作的,而我在几分钟之前并不理解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
源代码时尝试应用的规则:
光学通常遵循整个库中的s-t-a-b
形式,这允许您修改&#34;目标&#34;的类型。 (一个重载的词,充其量)。但是,只有s
和a
才能实现许多光学器件,当您尝试使用t
和b
时,通常无需跟踪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 (例如dimap
和rmap
)及其伴随辅助函数(lens
,p ~ (->)
)。
在阅读Representable
代码时,每当我看到一个profunctor变量时,我发现几乎总是假设Conjoined
(函数类型)很有帮助。这样,我就可以从上面代码段中的签名中删除Bizarre
,Over
,singular
和singular :: 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。我觉得这种表现方式对于直观地发生了什么更有帮助。
这里的宝石是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:xs
与x
执行此操作。如果你内联这个定义,你应该得到相同的结果。
在liftSmall x
案例中,f small
是我们想要采取行动的价值。它是我们给出的遍历所针对的第一个值,现在成为我们返回的镜头的第一个值。我们致电f
为xs
获取一个仿函数f [small]
;然后我们在仿函数中附加unsafeOuts bazaar
以获得f [small]
;然后我们在仿函数中调用f 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) -> 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
纯猜想如下:据我所知,b
与f (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,我不完全确定它们是如何挂在一起的。值得进一步探索!