为什么Data.Vector的Applicative实例不对齐其结果?

时间:2015-05-06 19:33:05

标签: haskell

所以我希望fmap一个函数超过几个相同大小的Vector。好吧,Applicative救援!

λ➔ let f x y z = x * y + z
λ➔
λ➔ let va = V.fromList [10, 20, 30]
λ➔ let vb = V.fromList [2, 5, 3]
λ➔ let vc = V.fromList [1, 2, 3]
λ➔
λ➔ f <$> va <*> vb <*> vc
fromList [21,22,23,51,52,53,31,32,33,41,42,43,101,102,103,61,62,63,61,62,63,151,152,153,91,92,93]

...哎呀。远非预期。

the source开始,显然Applicative实例是根据Monad的定义; (<*>) = ap; (>>=) = flip concatMap。这解释了为什么我的表情爆炸了:

λ➔ (+) <$> V.fromList [10, 20, 30] <*> V.fromList [1, 2, 3]
fromList [11,12,13,21,22,23,31,32,33]

它只列举所有参数组合。这似乎是(<*>)的一个非常糟糕的定义,因为结果是组合起来的。

相反,它可以对角化结果以生成[11, 22, 33],如下所示:

instance Applicative Vector where
    pure = repeat
    fs <*> xs = zipWith ($) fs xs

这仍然是一个有效的Applicative实例。在我看来,它更有用。

除了明显的疏忽之外,是否有理由按照原样编写实例?

此外,对于采取进一步行动的经验丰富的建议表示赞赏。

*编辑:更早更正pure = single

3 个答案:

答案 0 :(得分:11)

这不仅仅是疏忽。为了使Applicative实例正确,我们需要pure的实现,其行为类似于无限长向量,所有实例都具有相同的元素。 (并且因为API提供扫描,所以库也需要能够具有类似于任意无限长向量的值。)当前实现使用数组是出于性能原因;由于显而易见的原因,人们无法拥有无限大小的阵列。

在zippy应用程序变得流行之前,您可以按照我们以前的方式实现您想要的目标。有一些函数专门用于你想要压缩的参数数量,通常有zipWithN个名字。

答案 1 :(得分:5)

我只会使用Vector.zipWith

我从使用Vector中学到的一件事是,您通常希望坚持图书馆的操作并避开Functor / Applicative / Monad个实例。为什么?因为几个库的类型不能支持类型类。例如,如果您使用Data.Vector.Vector的{​​{1}}实例编写了大量代码,并且稍后决定使用Functor,那么您将不得不更改所有Data.Vector.Unboxed.Vector }到fmap,因为未装箱的向量不是Data.Vector.Unboxed.map个实例 - 元素类型带有Functor约束。

这很不幸,但它只是一个长期未解决的Haskell问题的例子(当类型需要对类型参数进行约束时,无法生成Unbox类的实例。)

答案 2 :(得分:0)

您可以输入一个类型:

数据DiagonalisingVector a = DiagVector(a a(向量a))

为此编写适用的实例很容易转发Right的所有内容,并向左应用Right的每个元素或另一个单数Left。左表示纯值,可以认为它占据所有点。

这将很容易地做你想要的,你可以开始根据自己的需要定制和优化它。