【问题标题】:Why right folding can process infinite list in Haskell?为什么右折叠可以在 Haskell 中处理无限列表?
【发布时间】:2016-05-31 14:39:24
【问题描述】:

对于无限列表,它没有“最后一个”元素。那么foldr 怎么处理呢?

我有来自 Haskell 书中的这段代码 sn-p:

(&&)::Bool->Bool->Bool
True && x = x
False && _ = False

and' :: [Bool]->Bool
and' xs=foldr (Main.&&) True xs

然后在 Prelude 中加载这个 hs 文件并运行:

*Main> and' (repeat False)
False

它按预期工作,但我不明白:

  1. (&&) 的定义在其左侧接收布尔值, 但是我们应用True && x = x 而变量x 在它的右侧。 是不是很奇怪?
  2. 为什么foldr(&&) 返回False 时停止? 据我了解,foldr 将从尾到头循环遍历列表。 是否有任何内部“中断”机制?
  3. foldr 从列表的最后一个元素开始,但无限列表没有结束。 foldr 如何开始工作?

【问题讨论】:

  • 或者这个:stackoverflow.com/questions/833186/…。无论哪种方式,每个帖子一个问题。您的第一个与您的第二个和第三个不同。第二个和第三个是其他的重复。另外,foldr 不是从右边开始的;它关联到右侧:f a (f b (f c( ... ))))。清理你的问题。
  • 您能详细说明您的第一个问题吗?

标签: list haskell infinite fold


【解决方案1】:

您需要稍微熟悉一下语法。 在 Haskell 中,函数定义是这样的:

 this   =   that

而且,基本上,它的意思是:当你看到 this 时,它的意思是 that。其实

True && x 

x一样,

False && x 

False,这就是为什么我们可以按照您的示例编写定义。

对于您的第二个问题:foldr“停止”并非如此。当左边的操作数是False 时,它是&& 运算符不计算其右操作数。

对于您的第三个问题:不,foldr 不是从列表的最后一个元素开始。请看一下foldr的定义:

foldr f z [] = z
foldr f z (x:xs) = x `f`  foldr f z xs

现在假设

foldr (&&) True (False:xs)

计算结果为

False && foldr (&&) True xs

因为

False && _

False,结果是False

【讨论】:

  • “foldr f z [] = z”不应该是“foldr f z [] = f z”吗?我想应该应用 f 吗?
  • f 是什么意思?
  • f 的中缀应用(你至少应该通过 LYAH,因为在大多数基本语法未知的情况下几乎不可能讨论事情)。这也意味着 f 是一个有两个参数的函数,所以不,将它应用于第一个方程中的 z 是没有意义的。
【解决方案2】:

让我们重复你的定义并添加更多:

(&&)::Bool->Bool->Bool
True && x = x
False && _ = False

and :: [Bool]->Bool
and xs = foldr (&&) True xs

repeat :: a -> [a]
repeat a = a : repeat a

foldr :: (a -> r -> r) -> r -> [a] -> r
foldr _ z [] = z
foldr f z (a:as) = f a (foldr f z as)

现在,我们可以通过手动评估来证明这一点,注意“懒惰”地进行(首先是最外层的应用程序,并且只评估到足以解析最外层的数据构造函数):

and (repeat False)
  = foldr (&&) True (repeat False)           -- definition of `and`
  = foldr (&&) True (False : repeat False)   -- definition of `repeat`
  = False && foldr (&&) True (repeat False)  -- `foldr`, second equation
  = False                                    -- `&&`, second equation

关键是&&定义的第二个方程舍弃了它的第二个参数:

False && _ = False

这意味着,就运行时而言,我们永远不会在遇到False 的步骤强制执行foldr 的递归调用。

另一种看待它的方式是考虑foldr 的第二个等式,以及当我们进行惰性求值时它的含义:

foldr f z (a:as) = f a (foldr f z as)

由于对foldr 的递归调用作为f 的参数发生,这意味着,在运行时,函数f 决定其第二个参数的值是否必要,因此在每个折叠步骤是否向下递归列表。而这个“决策”过程是从左到右进行的。


据我了解,foldr 将从尾到头循环遍历列表。是否有任何内部“中断”机制?

严格来说,在纯函数式语言中,没有评估顺序的内在概念。表达式可以按照与其数据相关性一致的任何顺序进行评估。

你在这里所说的是一个常见的误解,即那些从不纯、热切的语言中学习 foldr 的人会继承到 Haskell。在急切的语言中,这是一条有用的经验法则,但在 Haskell 中,使用纯惰性求值,该规则只会让您感到困惑。在编程 Haskell 时,相反 的经验法则通常很有用:foldr 将从左到右访问列表元素,并且在每一步,它的 f 参数函数决定是否其余的清单是必要的。

这方面的极端例子是使用foldr实现一个获取列表头部的函数:

-- | Return `Just` the first element of the list, or `Nothing` if the
-- list is empty.
safeHead :: [a] -> Maybe a
safeHead = foldr (\a _ -> Just a) Nothing

例如:

safeHead [1..]
  = foldr (\a _ -> Just a) Nothing [1..]
  = foldr (\a _ -> Just a) Nothing (1:[2..])
  = (\a _ -> Just a) 1 (foldr (\a _ -> Just a) Nothing [2..])
  = Just 1

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-15
    • 2010-10-24
    • 1970-01-01
    • 1970-01-01
    • 2010-10-25
    • 2015-02-25
    • 2012-02-25
    • 1970-01-01
    相关资源
    最近更新 更多