【问题标题】:Why doesn't the list Applicative instance perform a one-to-one application?为什么列表 Applicative 实例不执行一对一的应用程序?
【发布时间】:2018-06-05 13:51:27
【问题描述】:

我从 Hutton's Programming in Haskell 中阅读了有关 Haskell 中 Applicative 的信息。为了更好地理解它,我为列表的Applicative 提出了以下定义:

-- Named as pure' and "app" to avoid confusion with builtin versions 
class Applicative' f where
 pure' :: a -> f a
 app :: f (a->b) -> f a -> f b

instance Applicative' [] where
 pure' x = [x]
 app _ [] = []
 app [g] (x:xs) = [(g x)] ++ app [g] xs
 app (g:gs) (x:xs) = [(g x)] ++ app gs xs

-- fmap functions could be defined as:
fmap1' :: (Applicative' f)=>(a->b) -> f a -> f b
fmap1' g x = app (pure' g) x

fmap2' :: (Applicative' f)=>(a->b->c) -> f a -> f b -> f c
fmap2' g x y = app (app (pure' g) x) y


fmap3' :: (Applicative' f)=>(a->b->c->d) -> f a -> f b -> f c -> f d
fmap3' g x y z = app (app (app (pure' g) x) y) z

fmap2'的使用示例如下:

Ok, one module loaded.
*Main> g = \x y -> x*y
*Main> arr1 = [1,2,3]
*Main> arr2 = [4,5,6]
*Main> fmap2' g arr1 arr2
[4,10,18]
*Main>

Applicative函数<*>的标准定义为:

gs <*> xs = [g x | g <- gs, x <- xs]

因此导致

pure (*) <*> [1,2], [3,4]
[3,4,6,8]

我想知道为什么它以for all arr1, for all arr2, apply function 而不是take corresponding elements arr1, arr2 apply function 的方式定义。 我想第一个定义可能更有用?这种选择有什么具体原因吗?

【问题讨论】:

标签: haskell applicative


【解决方案1】:

这基本上是ZipList 应用实例。主要区别是

pure x = repeat x

而不是你的pure x = [x]

这是履行适用法律所必需的。也就是说,您的实现违反了交换法:

[id, id] <*> pure 1 ≡ [id,id] <*> [1]
                    ≡ [id 1] ++ ([id] <*> [])
                    ≡ [id 1] ++ []
                    ≡ [1]
‡ pure ($ 1) <*> [id,id] ≡ [1,1]

无限pure 的要求使ZipList 在实践中有些有趣。标准实现基本上是最自然的finite-only 实现。可以说,如果在前奏中有有限数组和可能无限列表的单独类型,并且列表具有 ZipList 实例,那会更好。

按照 cmets,您的实现实际上也很好,只要您在需要时填写两个列表即可。不错!

【讨论】:

  • 另一个修复可能是使app 在处理列表结尾时对称。例如app [] [] = []; app [f] [x] = [f x]; app [f] xs = map f xs; app fs [x] = map ($x) fs; app (f:fs) (x:xs) = f x : app fs xs。保持pure x = [x] 与问题相同。这似乎是一个真正不同于标准实例或ZipListApplicative,对于finite-only 列表可以正常工作,而且我的直觉认为它不应该违反任何法律(尽管我实际上并没有检查)。
  • @DanielWagner 我的直觉说它可能会违反组合法则......无论是修剪较长的列表还是扩展较短的列表,它似乎都表现得不一致。但我不确定,它实际上可能与MaybeT (WriterT (Min Word) ZipList) 或类似的东西同构。
  • 它不会比类型要求的不一致:空列表传播(因为它们必须在任何总实例中),否则总是扩展较短的列表。
  • QuickCheck 说它满足所有的法律,只做了一个小调整:它需要[] __ [] 的两个基本情况,而不仅仅是[] [] 基本情况。 Here's some code.
  • (调整后的)实例确实是合法的——(&lt;*&gt;) 归结为扩展较短的列表,然后使用ZipList 实例;这样做不可能使关联性失败。这很整洁。
【解决方案2】:

Applicative [] 具有 generate-all-possible-combinations 行为而不是任何类型的 zippy 行为的基本原因是 ApplicativeMonad 的超类,旨在按照Monad 存在时的实例。 Monad [] 将列表视为失败和优先选择,因此 Applicative [] 实例也是如此。人们经常使用应用接口重构一元代码,以减少值所需的中间名称的数量,并增加并行性的机会。如果这会导致函数语义发生重大转变,那将是相当可怕的。

除此之外,事实是,您对Applicative [] 实例的选择一无所知,如果您考虑空/非空和有限/共归纳/无限变化,则更是如此。这是为什么呢?

好吧,正如我在this answer 中提到的,每个Applicative f 都以Monoid (f ()) 开始其生命,结合数据的形状,然后我们开始担心价值观。列表就是一个很好的例子。

[()] 基本上是数字的类型。数字在很多方面都是幺半群。

Monad [] 中取Applicative [] 等于选择1* 生成的幺半群。

同时,Applicative ZipList 利用了 Haskell 的共归纳合并,相当于选择了由无穷大和最小值生成的幺半群。

问题提出了一个不合法的例子,但与合法的例子很接近。您会注意到 &lt;*&gt; 没有为空函数列表定义,但对于非空函数列表,它会填充以匹配参数列表。不对称地,当参数用完时它会截断。有点不对劲。

接下来有两个候选修复。

一种是在两边截断为空,然后你必须取pure = repeat,你有ZipList

另一种是排除空列表并在两侧填充。然后你会得到由Monoid 生成的Applicative,它是由 1 和 maximum 生成的正数。所以它根本不是ZipList。这就是我在this answer 中称为PadMe 的东西。您需要排除 0 的原因是,对于 &lt;*&gt; 的输出中的每个位置,您需要指向两个输入中函数及其参数(分别)来自的位置。如果你没有什么可以垫的,你就不能垫。

这是一个有趣的游戏。在数字上选择一个Monoid,看看你是否可以将它变成Applicative 用于列表!

【讨论】:

  • 虽然每个instance Applicative [] 都会在[()] 上产生一个幺半群,但我完全不清楚其他方式是否总是可行的。特别是,这些类型要求在任何(全部)实例中都有[] &lt;*&gt; _ = []_ &lt;*&gt; [] = [];但是有很多关于数字的幺半群,0 不是歼灭者。事实上,在Product(您提出的第一个示例)之后,显然下一个选择是Sum,它没有这个属性。所以“数字在很多方面都是幺半群”并不意味着“[] 在很多方面都是 Applicative”(尽管后者被证明是正确的)。
  • 另一方面,您观察到正数上的任何幺半群都可以变成自然数上的幺半群(虽然 确实0 作为歼灭者你没有提到它)非常有趣,进一步思考Sum 的变体让我想到了以下奇怪的Applicative 实例:pure x = [x]; fs@(f:ft) &lt;*&gt; xs@(x:_) = map f xs ++ map ($x) ft(+ 基本案例)。法律似乎确实成立(根据 QuickCheck,我懒得提供完整的证明......),但我发现它的行为很奇怪,你是对的:这是一个有趣的游戏!
  • @DanielWagner 类似于 this reddit post 中讨论的这个 monad 的应用实例。
猜你喜欢
  • 2021-04-06
  • 2020-10-19
  • 1970-01-01
  • 1970-01-01
  • 2021-10-02
  • 2015-01-09
  • 2020-07-25
  • 2010-12-02
  • 1970-01-01
相关资源
最近更新 更多