有没有一种方法可以在Haskell中使h(f x)(g x)变为无点形式?

8

我想要类似J的fork功能。有没有办法实现这个功能?


1
通过询问“pointfree”程序:“pointfree“m h f g x = h (f x) (g x)”“->”m = liftM2“。@kqr的答案是等效的,而且可能更好,因为Applicative实例通常比Monad实例更高效。 - bheklilr
@bheklilr,pointfree建议使用liftM*的唯一原因不是因为在liftA*出现之前就已经写好了吗?一旦applicative成为monad的超类,我们不是可以把liftM2扔掉了吗? - kqr
@kqr 我想应该是这样,或者只是 pointfree 在查找 Control.Applicative 之前先查找了 Control.Monad。是否可能存在某些 Monad,其 liftA* 的实现与 liftM* 不同?我猜测行为应该是相同的,但由于 Applicatives 有时可以在 Monads 无法添加并行性的情况下添加并行性,因此我不会轻易放弃 liftM* - bheklilr
@bheklilr 这取决于它们是否可以不同。Monoid e => Either e 可以使用一个整洁的 Applicative,但无法形成一个 Monad,但一旦 Applicative-Monad 提案出现,实例之间的差异将变得更加奇怪... - J. Abrahamson
2个回答

15

这是所谓的应用风格(applicative style)。

h <$> f <*> g

使用Control.Applicative中的<$><*>,将h提升到(->) r函子中:

liftA2 h f g

这背后的直觉是,如果

h        ::    a     ->    b     ->   c
-- then --
liftA2 h :: (r -> a) -> (r -> b) -> r -> c

所以提升的版本需要两个函数 r -> something,而不是实际的 something,然后提供一个 r 来从函数中获取 something


liftA* 和相应的 <$><*> 的组合是等价的。


12

虽然@kqr提供了更实用的解决方案,基于((->) a)Applicative实例,但我们也可以用"管道式"的方法来谈论它。

        +----- f ------+
       /                \
<---- h                  +------< x
       \                /
        +----- g ------+

使用Control.Arrow中的工具,可以提供一种非常组合式的无点编程方式。我们将使用一个在Haskell中常用的缺失函数diagdup来创建此程序的最右边部分。

--+
   \
    +----- x        dup :: x -> (x, x)
   /                dup x = (x, x)
--+

然后使用Control.Arrow中的(***)组合器创建中间结果

----- f -----     (***)   :: (a -> b) -> (c -> d) -> (a, c) -> (b, d)
                  f       :: (a -> b)
                  g       ::             (c -> d)
----- g -----     f *** g ::                         (a, c) -> (b, d)

那么左侧正是uncurry为我们所做的事情。

  +--    uncurry   :: (a -> b -> c) -> (a, b) -> c
 /       h         :: (a -> b -> c)
h        uncurry h ::                  (a, b) -> c
 \   
  +--

然后将它们全部连接在一起,我们可以用非常组合的方式删除所有的x点。

m :: (a -> b -> c) -> (x -> a) -> (x -> b) -> x -> c
m h f g = uncurry h . (f *** g) . dup

                  +------ f ----+
                 /               \
         <----- h                 +-----------< x (eta reduced)
                 \               /
                  +------ g ----+

这是一个非常聪明的思考问题的方式。出于好奇,当对应于liftA3 f g1 g2 g3时,它的可扩展性如何? - kqr
1
翻译:做得不是很好。你必须做更多的工作来打包和解包嵌套对。我将介绍标准的 Control.Arrow 组合器 f &&& g = (f *** g) . dup,然后我们有 liftA3 f g1 g2 g3 = uncurry ($) . first (uncurry f) . first (g1 &&& g2) . second g3 . dup。呃。 - J. Abrahamson
为了交叉引用,我经常听到这种编程被称为“对偶演算法”中的编程。 - J. Abrahamson
@J.Abrahamson 出于好奇,是否有类似 Arrow 的库可以优雅地处理 liftA3 这种情况? - bheklilr
1
哇,我有点搞砸了 liftM3 版本。它并不像我说的那么糟糕:liftM3 f g1 g2 g3 = uncurry ($) . (uncurry f . (g1 &&& g2)) &&& g3。我们甚至可以在 Arrow (->) 实例中使用 apply :: uncurry ($) 来编写 apply . ((uncurry f . (g1 &&& g2)) &&& g3) 或者 apply . (liftA2 f g1 g2 &&& g3),这给我们提供了一个很好的递归关系,让我们生成 [liftAn | n <- [1..]] - J. Abrahamson
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接