繁体   English   中英

为什么正确折叠可以在Haskell中处理无限列表?

[英]Why right folding can process infinite list in Haskell?

对于无限列表,没有“最后”元素。 那么文件foldr如何使用呢?

我有Haskell书中的以下代码段:

(&&)::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. (&&)的定义在其左侧接收布尔值,但是当变量x在其右侧时,我们应用True && x = x 奇怪吗?
  2. 为什么当(&&)返回False时,文件foldr停止? 以我的理解, foldrfoldr遍历整个列表。 有内部的“中断”机制吗?
  3. foldr从列表的最后一个元素开始,但是无限列表没有结束。 文件foldr如何开始工作?

您需要稍微熟悉一下语法。 在Haskell中,函数定义如下所示:

 this   =   that

从根本上讲,它的意思是:当您看到此内容时,它的含义 事实上

True && x 

x相同,并且

False && x 

False ,这就是为什么我们可以像您的示例中那样编写定义。

对于您的第二个问题:文件foldr “停止”的情况并非如此。 当左操作数为False时,是&&运算符不评估其右操作数。

对于您的第三个问题:不,文件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

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

(&&)::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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM