【问题标题】:Does liftA2 preserve associativity?liftA2 是否保留关联性?
【发布时间】:2020-09-14 03:09:25
【问题描述】:

给定一个操作(??) 这样

(a ?? b) ?? c = a ?? (b ?? c)

(也就是说(??)是关联的)

一定是这样的

liftA2 (??) (liftA2 (??) a b) c = liftA2 (??) a (liftA2 (??) b c)

(也就是说liftA2 (??)是关联的)

如果我们愿意,我们可以将其重写为:

fmap (??) (fmap (??) a <*> b) <*> c = fmap (??) a <*> (fmap (??) b <*> c)

我花了一点时间盯着适用的法律,但我无法拿出证据证明情况确实如此。所以我开始反驳它。我尝试过的所有开箱即用的应用程序(Maybe[]Either 等)都遵循法律,所以我想我会创建自己的。

我最好的想法是制作一个附有额外信息的空应用程序。

data Vacuous a = Vac Alg

Alg 将是一些代数,我稍后会根据自己的方便定义,以使属性失败但应用定律成功。

现在我们这样定义我们的实例:

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)

其中i 是待确定的Alg 的某个元素,comb 是待确定的Alg 上的二进制组合器。我们真的没有其他方法可以定义它。

如果我们想要满足 身份 法则,这将强制 i 成为 comb 的身份。然后我们免费获得HomomorphismInterchange。但现在 Composition 强制 combAlg 关联

((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)

强迫我们满足属性。

有反例吗?如果不是,我们如何证明这个性质?

【问题讨论】:

标签: haskell applicative semigroup


【解决方案1】:

我们首先使用应用法则重写左侧。回想一下 &lt;$&gt;&lt;*&gt; 都是左关联的,所以我们有,例如,x &lt;*&gt; y &lt;*&gt; z = (x &lt;*&gt; y) &lt;*&gt; zx &lt;$&gt; y &lt;*&gt; z = (x &lt;$&gt; y) &lt;*&gt; 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

最后一种形式表明,本质上,原始表达式“运行”动作 abc,以这种方式排序它们的效果,然后使用 (??) 纯粹结合三个结果。

然后我们可以证明右手边等价于上面的形式。

(??) <$> 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

现在,我们只需将无点术语 ((.) ($ (??)) ((.) (.) ((.) (.) (??)))) 改写成更易读的有指向性形式,这样我们就可以使它与我们在证明的前半部分得到的术语相等。这只是根据需要应用(.)($) 的问题。

((.) ($ (??)) ((.) (.) ((.) (.) (??))))
= \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

在最后一步中,我们利用了(??) 的关联性。

(哇哦)

【讨论】:

  • 真的!我发现 Haskell 的 curried 风格对于这种校对工作确实不太有用。
【解决方案2】:

它不仅保留了关联性,我想说这可能是应用法则背后的主要思想

回想一下该类的数学形式:

class Functor f => Monoidal f where
  funit ::    ()     -> f  ()
  fzip :: (f a, f b) -> f (a,b)

法律

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@987654321@fy) (fzip (x,y))
fIdnt:  fmap id ≡ id                    -- ─╮
fCmpo:  fmap f . fmap g ≡ fmap (f . g)  -- ─┴ functor laws

在这种方法中,liftA2 将一个元组值函数映射到一个已经压缩好的对上:

liftZ2 :: ((a,b)->c) -> (f a,f b) -> f c
liftZ2 f = fmap f . fzip

liftZ2 f (a,b) = f <$> fzip (a,b)

现在说我们已经给了

g :: (G,G) -> G
gAssc:  g (g (α,β), γ) ≡ g (α, g (β,γ))

或无点(再次忽略元组括号交换)

gAssc:  g . (g***id) ≅ g . (id***g)

如果我们以这种风格编写所有内容,很容易看出关联性保留基本上只是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))

【讨论】:

  • 确实——每当必须用应用定律证明某事时,使用幺半群表示是一种明智的默认设置。
猜你喜欢
  • 1970-01-01
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
  • 2011-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多