【问题标题】:Laziness of (>>=) in folding(>>=) 在折叠中的惰性
【发布时间】:2015-10-08 04:01:00
【问题描述】:

考虑 Haskell 中的以下 2 个表达式:

foldl' (>>=) Nothing (repeat (\y -> Just (y+1)))
foldM (\x y -> if x==0 then Nothing else Just (x+y)) (-10) (repeat 1)

第一个需要永远,因为它试图评估无限表达式

...(((Nothing >>= f) >>= f) >>=f)...

Haskell 只会尝试从里到外评估它。

然而,第二个表达式立即给出了 Nothing。我一直认为 foldM 只是使用 (>>=) 进行折叠,但它会遇到同样的问题。所以它在这里做了一些更聪明的事情——一旦它击中它就知道停止。 foldM 实际上是如何工作的?

【问题讨论】:

  • 如果你没有看到 - 在这些问题上有一个nice article on the haskell wiki(没有提到foldM,但@dfeuer 已经在那里提供了帮助)
  • 请注意,您可以点击 Hackage 文档中的“源”链接自行阅读源代码。

标签: haskell fold


【解决方案1】:

foldM 无法使用foldl 实现。它需要foldr 的力量才能停下来。在我们到达那里之前,这是一个没有任何花哨的版本。

foldM f b [] = return b
foldM f b (x : xs) = f b x >>= \q -> foldM f q xs

我们可以将其转换为使用foldr 的版本。首先我们把它翻过来:

foldM f b0 xs = foldM' xs b0 where
  foldM' [] b = return b
  foldM' (x : xs) b = f b x >>= foldM' xs

然后把最后一个参数移过去:

  foldM' [] = return
  foldM' (x : xs) = \b -> f b x >>= foldM' xs

然后识别foldr模式:

  foldM' = foldr go return where
    go x r = \b -> f b x >>= r

最后,我们可以内联foldM'并将b移回左边:

foldM f b0 xs = foldr go return xs b0 where
  go x r b = f b x >>= r

同样的通用方法适用于您希望在右折叠中从左到右传递累加器的各种情况。您首先将累加器一直向右移动,这样您就可以使用foldr 来构建一个接受累加器的函数,而不是尝试直接构建最终结果。 Joachim Breitner 做了很多工作来为 GHC 7.10 创建 Call Arity 编译器分析,帮助 GHC 优化以这种方式编写的函数。想要这样做的主要原因是它允许他们参与 GHC 列表库的融合框架。

【讨论】:

    【解决方案2】:

    根据foldr 定义foldl 的一种方法是:

    foldl f z xn = foldr (\ x g y -> g (f y x)) id xn z
    

    可能值得弄清楚为什么这是为你自己。可以使用 >>> from Control.Arrow as 重写它

    foldl f z xn = foldr (>>>) id (map (flip f) xn) z
    

    >>> 的一元等效项是

    f >=> g = \ x -> f x >>= \ y -> g y
    

    这让我们可以猜测foldM 可能是

    foldM f z xn = foldr (>=>) return (map (flip f) xn) z
    

    事实证明这是正确的定义。它可以使用foldr/map 重写为

    foldM f z xn = foldr (\ x g y -> f y x >>= g) return xn z
    

    【讨论】:

      猜你喜欢
      • 2012-01-05
      • 2018-12-08
      • 2020-04-30
      • 2010-10-25
      • 1970-01-01
      • 2012-04-27
      • 2011-12-19
      • 2018-12-12
      相关资源
      最近更新 更多