Haskell确实是一种惰性的语言。惰性意味着表达式只有在需要时才会被计算。但是,惰性并不意味着两个表达式可以以任意顺序计算。在Haskell中,表达式的计算顺序很重要。例如,考虑你的und
函数:
und :: Bool -> Bool -> Bool
und False y = False
und y False = False
首先,我想指出这个函数是不完整的。完整的函数如下:
und :: Bool -> Bool -> Bool
und False y = False
und y False = False
und y True = True
实际上,und
函数可以更加简洁(和懒惰)地编写如下:
und :: Bool -> Bool -> Bool
und False _ = False
und _ x = x
无论如何,在Haskell中的模式匹配语法只是
case
表达式的语法糖。例如,你原始(不完整)的函数将被转换为(最多到α等价):
und :: Bool -> Bool -> Bool
und x y = case x of False -> False
True -> case y of False -> False
True -> undefined
我们可以看到:
- 你的函数不完整,因为最后一个情况是
undefined
。
- 即使第一个参数不需要,如果第一个参数是
True
,你的函数也会评估第二个参数。请记住,case
表达式总是强制评估正在检查的表达式。
- 你的函数首先检查
x
,然后检查y
,如果x
评估为True
。因此,在这里确实有明确的评估顺序。请注意,如果x
评估为False
,则永远不会评估y
(证明und
确实是惰性的)。
正是由于这种评估顺序,你的表达式
und (non_term 1) False
会发散:
und (non_term 1) False
= case non_term 1 of False -> False
True -> case False of False -> False
True -> undefined
= case non_term 2 of False -> False
True -> case False of False -> False
True -> undefined
= case non_term 3 of False -> False
True -> case False of False -> False
True -> undefined
.
.
.
.
如果您愿意,可以创建一个具有不同评估顺序的函数:
und :: Bool -> Bool -> Bool
und x y = case y of False -> False
True -> x
现在,表达式
und (non_term 1) False
的评估结果为
False
。然而,表达式
und False (non_term 1)
仍会发散。所以,你的主要问题是:
Is there a way to implement und
(i.e. and
in German) correctly (not just partially as above) so that both
und (non_term 1) False
and
und False (non_term 1)
return False?
短答案是不行。您总是需要特定的评估顺序;并且根据评估顺序,
und (non_term 1) False
或
und False (non_term 1)
将会出现分歧。
这是否意味着Haskell是错误/有问题的?不。Haskell做了正确的事情,并且只是没有产生任何答案。对于人类(可以同时评估两个表达式的人),
und (non_term 1) False
的结果必须是
False
。然而,计算机必须始终具有评估顺序。
那么,实际问题是什么?在我看来,实际问题是二者之一:
Parallel evaluation. Haskell should evaluate the expression both ways in parallel and choose the one that terminates first:
import Data.Unamb (unamb)
type Endo a = a -> a
bidirectional :: Endo (a -> a -> b)
bidirectional = unamb <*> flip
und :: Bool -> Bool -> Bool
und = bidirectional (&&)
General recursion. In my humble opinion, general recursion is too powerful for most use cases: it allows you to write absurd functions like non_term x = non_term (x + 1)
. Such functions are quite useless. If we don't consider such useless functions as inputs then your original und
function is a perfectly good function to use (just implement the last case or use &&
).
希望这能有所帮助。
False
,就必须打开盒子,也就是对这些参数进行求值。Haskell 不可能“猜测”应该先处理哪个盒子! - Lynnunamb
,它是一个并行计算运算符,可以执行两个计算并使用第一个计算的结果终止而不出错。但基本上,惰性求值是一种特定的求值策略-还有其他可能更适合你目的的策略。 - dfeuer