【问题标题】:Does the Applicative interface provide power beyond the ability to lift multi-argument functions (in curried form) into a Functor?Applicative 接口是否提供了超越将多参数函数(以咖喱形式)提升到 Functor 的能力?
【发布时间】:2020-04-28 04:20:39
【问题描述】:

应用程序通常作为提升多参数函数的一种方式呈现 进入函子并将函子值应用于它。但我想知道是否有一些 微妙的额外力量源于它可以通过提升来做到这一点 返回一个函数并应用函数参数的函数 一次。

想象一下,我们定义一个基于提升函数的接口,其参数是一个参数元组:

# from Functor
fmap :: (a -> b) -> Fa -> Fb
# from Applicative
pure :: a -> Fa

# combine multiple functor values into a functor of a tuple
tuple1 :: Fa -> F(a)
tuple2 :: Fa -> Fb -> F(a,b)
tuple3 :: Fa -> Fb -> Fc -> F(a,b,c)
(etc ...)

# lift multi-argument functions (that take a tuple as input)
ap_tuple1 :: ((a) -> b) -> F(a) -> Fb
ap_tuple2 :: ((a,b) -> c) -> F(a,b) -> Fc
ap_tuple3 :: ((a,b,c) -> d) -> F(a,b,c) -> Fd
(etc ..)

假设我们为我们可能遇到的每个大小的元组定义了相应的元组函数。 这个接口是否和 Applicative 接口一样强大,因为它允许 提升/应用到多参数功能但不允许提升/应用到功能 那返回一个函数?显然,可以对以元组作为参数的函数进行 curry 因此它们可以在应用程序中提升,并且可以取消返回函数的函数 为了将它们提升到上面的假设实现中。但在我看来,有一种微妙的 权力的差异。有什么区别吗? (假设这个问题甚至有意义)

【问题讨论】:

  • 参见。 four types of generalized function application(在帖子底部)
  • “应用程序”会分散注意力。我们已经可以在一个单纯的 Functor 中应用函数。所以mult :: Monoidal f => (f a, f b) -> f (a,b) 首先将两个函数值粉碎为一个,然后我们仅通过 Functor 的力量将函数的非柯里化版本 (uncurry g (x,y) = g x y) 应用到该函数值“内部”。一些法律将确保粉碎不会造成任何损失。与x $ y == uncurry ($) (x,y) 相同,只是“在内部”。

标签: haskell higher-order-functions applicative


【解决方案1】:

您重新发现了Applicative 的单向表示。它看起来像这样:

class Functor f => Monoidal f where
    (>*<) :: f a -> f b -> f (a, b)
    unit :: f ()

它与Applicative 同构:

(>*<) = liftA2 (,)
unit = pure ()

pure x = x <$ unit
f <*> x = fmap (uncurry ($)) (f >*< x)

顺便说一句,您的ap_tuple 函数都只是fmap。具有多个值的“硬”部分是将它们组合在一起。将它们拆分成碎片很“容易”。

【讨论】:

  • 或者,也许更漂亮,(&gt;*&lt;) :: (f a, f b) -&gt; f (a, b)。
  • @WillNess 但是你有一个不能以中缀表示法调用的操作符风格的函数。
  • 啊,那么,必须给它起个别的名字。更多地考虑类型。所以&gt;*&lt; 已经是某种逗号了,好吧。
  • 有趣!我认为我更喜欢 Applicative 的这种观点,而不是文章中通常提到的观点,因为它侧重于 monoidal 质量而不是“应用”值到嵌入函数值的能力(当我第一次遇到 applicative 时,这似乎很迟钝)。类似于使用 Join 而不是 Bind 来展示 Monad 更好地展示了 Monad 比 Functor 强大的本质(即两个 Monad 值的扁平化)。
  • 虽然join 在概念上可能更简洁,但&gt;&gt;= 更有效,因为它将展平和映射融合到一个步骤中。换句话说:join m = m &gt;&gt;= id 遍历 m 一次,而 m &gt;&gt;= f = join (fmap f m) 遍历两次。
【解决方案2】:

是的,这同样强大。请注意pure 和tuple1 是相同的。此外,高于tuple2 的所有内容都从tuple2 和fmap 中恢复:

tuple3 x y z = repair <$> tuple2 (tuple2 x y) z
    where repair ((a, b), c) = (a, b, c)
tuple4 w x y z = repair <$> tuple2 (tuple2 x y) (tuple2 x y)
    where repair ((a, b), (c, d)) = (a, b, c, d)
-- etc.

另外,所有ap_tuples 都只是fmap:

ap_tuple1 = fmap
ap_tuple2 = fmap
ap_tuple3 = fmap
-- ...

重命名prod = tuple2,你的问题归结为

是

class Functor f => Applicative f where
    pure :: a -> f a
    prod :: f a -> f b -> f (a, b)

相当于

class Functor f => Applicative f where
    pure :: a -> f a
    liftA2 :: (a -> b -> c) -> f a -> f b -> f c

?

您可能已经看到答案是肯定的。 prod 只是 liftA2 的一个特化

prod = liftA2 (,)

但是(,) 是“自然的”,因为它不会“删除”任何内容,因此您可以通过解构数据来恢复liftA2:

liftA2 f x y = f' <$> prod x y
    where f' (a, b) = f a b

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-20
    相关资源
    最近更新 更多