[英]Does liftA2 preserve associativity?
Given an operation (??)
such that给定一个操作
(??)
这样
(a ?? b) ?? c = a ?? (b ?? c)
(that is to say (??)
is associative) (也就是说
(??)
是关联的)
must it be the case that一定是这样吗
liftA2 (??) (liftA2 (??) a b) c = liftA2 (??) a (liftA2 (??) b c)
(that is to say that liftA2 (??)
is associative) (也就是说
liftA2 (??)
是关联的)
If we would prefere we can rewrite this as:如果我们愿意,我们可以将其重写为:
fmap (??) (fmap (??) a <*> b) <*> c = fmap (??) a <*> (fmap (??) b <*> c)
I spent a little while staring at the applicative laws but I couldn't come up with a proof that this would be the case.我花了一点时间盯着适用的法律,但我无法拿出证据证明情况确实如此。 So I set out to disprove it.
所以我开始反驳它。 All the out-of-the-box applicatives (
Maybe
, []
, Either
, etc.) that I have tried, follow the law, so I thought I would create my own.我尝试过的所有开箱即用的应用程序(
Maybe
, []
, Either
等)都遵循法律,所以我想我会创建自己的。
My best idea was to make a vacuous applicative with an extra piece of information attached.我最好的想法是制作一个空的应用程序,并附加一条额外的信息。
data Vacuous a = Vac Alg
Where Alg
would be some algebra I would define at my own convenience later as to make the property fail but the applicative laws succeed. Alg
将是一些代数,我稍后会在自己方便时定义为使属性失败但应用定律成功。
Now we define our instances as such:现在我们这样定义我们的实例:
instance Functor Vacuous where
fmap f = id
instance Applicative Vacuous where
pure x = Vac i
liftA2 f (Vac a) (Vac b) = Vac (comb a b)
(Vac a) <*> (Vac b) = Vac (comb a b)
Where i
is some element of Alg
to be determined and comb
is a binary combinator on Alg
also to be determined.其中
i
是待确定的Alg
的某个元素, comb
是待确定的Alg
上的二进制组合子。 There is not really another way we can go about defining this.我们真的没有其他方法可以定义这个。
If we want to fulfill the Identiy law this forces i
to be an idenity over comb
.如果我们想满足同一性定律,这将迫使
i
成为comb
上的同一性。 We then get Homomorphism and Interchange for free.然后我们免费获得同态和交换。 But now Composition forces
comb
to be associative over Alg
但是现在, Composition强制
comb
与Alg
关联
((pure (.) <*> Vac u) <*> Vac v) <*> Vac w = Vac u <*> (Vac v <*> Vac w)
((Vac i <*> Vac u) <*> Vac v) <*> Vac w = Vac u <*> (Vac v <*> Vac w)
(Vac u <*> Vac v) <*> Vac w = Vac u <*> (Vac v <*> Vac w)
(Vac (comb u v)) <*> Vac w = Vac u <*> (Vac (comb v w))
Vac (comb (comb u v) w) = Vac (comb u (comb v w))
comb (comb u v) w = comb u (comb v w)
Forcing us to satisfy the property.强迫我们满足财产。
Is there a counter example?有反例吗? If not how can we prove this property?
如果不是,我们如何证明这个属性?
We start by rewriting the left hand side, using the applicative laws.我们首先使用应用定律重写左侧。 Recall that both
<$>
and <*>
are left-associative, so that we have, eg, x <*> y <*> z = (x <*> y) <*> z
and x <$> y <*> z = (x <$> y) <*> z
.回想一下
<$>
和<*>
都是左结合的,因此我们有,例如x <*> y <*> z = (x <*> y) <*> z
和x <$> y <*> z = (x <$> y) <*> z
。
(??) <$> ((??) <$> a <*> b) <*> c
= fmap/pure law
pure (??) <*> (pure (??) <*> a <*> b) <*> c
= composition law
pure (.) <*> pure (??) <*> (pure (??) <*> a) <*> b <*> c
= homomorphism law
pure ((.) (??)) <*> (pure (??) <*> a) <*> b <*> c
= composition law
pure (.) <*> pure ((.) (??)) <*> pure (??) <*> a <*> b <*> c
= homomorphism law
pure ((.) ((.) (??)) (??)) <*> a <*> b <*> c
= definition (.)
pure (\x -> (.) (??) ((??) x)) <*> a <*> b <*> c
= definition (.), eta expansion
pure (\x y z -> (??) ((??) x y) z) <*> a <*> b <*> c
= associativity (??)
pure (\x y z -> x ?? y ?? z) <*> a <*> b <*> c
The last form reveals that, essentially, the original expression "runs" the actions a
, b
, and c
in that order, sequencing their effects in that way, and then uses (??)
to purely combine the three results.最后一种形式表明,本质上,原始表达式按顺序“运行”动作
a
、 b
和c
,以这种方式对它们的效果进行排序,然后使用(??)
纯粹组合三个结果。
We can then prove that the right hand side is equivalent to the above form.然后我们可以证明右手边等价于上面的形式。
(??) <$> a <*> ((??) <$> b <*> c)
= fmap/pure law
pure (??) <*> a <*> (pure (??) <*> b <*> c)
= composition law
pure (.) <*> (pure (??) <*> a) <*> (pure (??) <*> b) <*> c
= composition law
pure (.) <*> pure (.) <*> pure (??) <*> a <*> (pure (??) <*> b) <*> c
= homomorphism law
pure ((.) (.) (??)) <*> a <*> (pure (??) <*> b) <*> c
= composition law
pure (.) <*> (pure ((.) (.) (??)) <*> a) <*> pure (??) <*> b <*> c
= composition law
pure (.) <*> pure (.) <*> pure ((.) (.) (??)) <*> a <*> pure (??) <*> b <*> c
= homomorphism law
pure ((.) (.) ((.) (.) (??))) <*> a <*> pure (??) <*> b <*> c
= interchange law
pure ($ (??)) <*> (pure ((.) (.) ((.) (.) (??))) <*> a) <*> b <*> c
= composition law
pure (.) <*> pure ($ (??)) <*> pure ((.) (.) ((.) (.) (??))) <*> a <*> b <*> c
= homomorphism law
pure ((.) ($ (??)) ((.) (.) ((.) (.) (??)))) <*> a <*> b <*> c
Now, we only have to rewrite the point-free term ((.) ($ (??)) ((.) (.) ((.) (.) (??))))
in a more readable point-ful form, so that we can make it equal to the term we got in the first half of the proof.现在,我们只需将无点术语
((.) ($ (??)) ((.) (.) ((.) (.) (??))))
改写成更易读的点—— ful 形式,这样我们就可以使它等于我们在证明的前半部分得到的项。 This is just a matter of applying (.)
and ($)
as needed.这只是根据需要应用
(.)
和($)
的问题。
((.) ($ (??)) ((.) (.) ((.) (.) (??))))
= \x -> (.) ($ (??)) ((.) (.) ((.) (.) (??))) x
= \x -> ($ (??)) ((.) (.) ((.) (.) (??)) x)
= \x -> (.) (.) ((.) (.) (??)) x (??)
= \x y -> (.) ((.) (.) (??) x) (??) y
= \x y -> (.) (.) (??) x ((??) y)
= \x y z -> (.) ((??) x) ((??) y) z
= \x y z -> (??) x ((??) y z)
= \x y z -> x ?? y ?? z
where in the last step we exploited the associativity of (??)
.在最后一步中,我们利用了
(??)
的关联性。
(Whew.) (哇。)
Not only does it preserve associativity, I would say that's perhaps the main idea behind the applicative laws in the first place!它不仅保留了关联性,我想说这可能是应用法则背后的主要思想!
Recall the maths-style form of the class:回想一下 class 的数学形式:
class Functor f => Monoidal f where
funit :: () -> f ()
fzip :: (f a, f b) -> f (a,b)
with laws有法律的
zAssc: fzip (fzip (x,y), z) ≅ fzip (x, fzip (y,z)) -- modulo tuple re-bracketing
fComm: fzip (fmap fx x, fmap fy y) ≡ fmap (fx***fy) (fzip (x,y))
fIdnt: fmap id ≡ id -- ─╮
fCmpo: fmap f . fmap g ≡ fmap (f . g) -- ─┴ functor laws
In this approach, liftA2
factors into fmapping a tuple-valued function over an already ready-zipped pair:在这种方法中,
liftA2
将一个元组值 function 映射到一个已经准备好的压缩对上:
liftZ2 :: ((a,b)->c) -> (f a,f b) -> f c
liftZ2 f = fmap f . fzip
ie IE
liftZ2 f (a,b) = f <$> fzip (a,b)
Now say we have given现在说我们给了
g :: (G,G) -> G
gAssc: g (g (α,β), γ) ≡ g (α, g (β,γ))
or point-free (again ignoring tuple-bracket interchange)或无点(再次忽略元组括号交换)
gAssc: g . (g***id) ≅ g . (id***g)
If we write everything in this style, it's easy to see that associativity-preservation is basically just zAssc
, with everything about g
happening in a separate fmap
step:如果我们以这种风格编写所有内容,很容易看出关联性保留基本上只是
zAssc
,有关g
的所有内容都发生在单独的fmap
步骤中:
liftZ2 g (liftZ2 g (a,b), c)
{-liftA2'-} ≡ g <$> fzip (g <$> fzip (a,b), c)
{-fIdnt,fComm-} ≡
g . (g***id) <$>
fzip (fzip (a,b), c) {-gAssc,zAssc-} ≡ g . (id***g) <$>
fzip (a, fzip (b,c))
{-fComm,fIdnt-} ≡ g <$> fzip (a, g <$> fzip (b,c))
{-liftA2'-} ≡ liftZ2 g (a, liftZ2 g (b,c))
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.