我刚刚开始学习Haskell,并且我被告知Haskell很懒,即它在评估表达式方面做的工作尽可能少,但我不认为这是真的。
考虑一下:
und :: Bool -> Bool -> Bool
und False y = False
und y False = False
non_term x = non_term (x+1)
und (non_term 1) False
的评估永远不会终止,但很明显,结果如果为假。
有没有办法实现und
(即德语为and
)正确(不仅仅是部分如上),以便
und (non_term 1) False
和
und False (non_term 1)
返回False?
答案 0 :(得分:9)
有没有办法正确实施und(即德语)(不是 只是部分如上所述)这两个
und (non_term 1) False
和
und False (non_term 1)
返回False?
如果你对理论感兴趣,那么经典的理论结果表明上述函数在带有递归的惰性lambda演算中是不可能的(称为PCF)。这是由Plotkin于1977年提出的。您可以在第8章“完全抽象”中的Winskel's notes on denotational demantics中找到讨论。
即使证据涉及更多,这里的关键思想是lambda演算是顺序,确定性语言。因此,一旦惰性二元函数被馈送两个布尔值(可能是底部值),它需要决定在另一个之前评估哪一个,从而修复评估顺序。这将打破or
和and
的对称性,因为如果所选参数发生偏差,则or
/ and
也会发散。
正如其他人提到的,在Haskell中,有一个通过非顺序方式定义unamb
的库,即利用下面的一些并发性,因此超出了PCF的功能。有了它,您可以定义并行or
或and
。
答案 1 :(得分:6)
您可以为und
编写一个完整的定义,该定义适用于非终止表达式......排序
为了使这项工作,您需要自己的Bool
定义,以明确任何计算的延迟:
import Prelude hiding (Bool(..))
data Bool = True | False | Delay Bool
deriving (Show, Eq)
然后,无论何时定义类型为Bool
的值,都必须将自己约束为共同递归,其中延迟是
使用Delay
构造函数显式,而不是通过递归,您必须在其中评估子表达式
找到顶级返回值的构造函数。
在这个世界中,非终止值可能如下所示:
nonTerm :: Bool
nonTerm = Delay nonTerm
然后und
成为:
und :: Bool -> Bool -> Bool
und False y = False
und x False = False
und True y = y
und x True = x
und (Delay x) (Delay y) = Delay $ und x y
效果很好:
λ und True False
False
λ und False nonTerm
False
λ und nonTerm False
False
λ case und nonTerm nonTerm of Delay _ -> "delayed" ; _ -> "not delayed"
"delayed"
跟进dfeuer's comment,您可以使用unamb
λ :m +Data.Unamb
λ let undL False _ = False ; undL _ a = a
λ let undR _ False = False ; undR a _ = a
λ let und a b = undL a b `unamb` undR a b
λ und True False
False
λ und False False
False
λ und False True
False
λ und True True
True
λ und undefined False
False
λ und False undefined
False
答案 2 :(得分:4)
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 -- you forgot this case
事实上,und
函数可以更简洁(更懒惰)写成如下:
-- if the first argument is False then the result is False
-- otherwise the result is the second argument
-- note that the second argument is never inspected
und :: Bool -> Bool -> Bool
und False _ = False
und _ x = x
无论如何,Haskell中的模式匹配语法只是case
表达式的语法糖。例如,您的原始(不完整)函数将被取消(直到alpha等价):
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 -- note that x is never inspected
现在,表达式und (non_term 1) False
的计算结果为False
。但是,表达式und False (non_term 1)
仍然存在分歧。所以,你的主要问题是:
有没有办法实现
und
(即德语为and
)正确(不仅仅是部分如上),以便und (non_term 1) False
和
und False (non_term 1)
返回False?
简短的回答是否定的。您始终需要特定的评估顺序;根据评估顺序,und (non_term 1) False
或und False (non_term 1)
会有所不同。
这是否意味着Haskell错误/错误?没有.Haskell做正确的事情,根本不会产生任何答案。对于人类(可以并行评估两个表达式),und (non_term 1) False
的结果似乎必须是False
。但是,计算机必须始终具有评估顺序。
那么,实际问题是什么?在我看来,实际问题是/或:
并行评估。 Haskell应该并行地评估表达式并选择首先终止的表达式:
import Data.Unamb (unamb)
type Endo a = a -> a
bidirectional :: Endo (a -> a -> b)
bidirectional = unamb <*> flip
und :: Bool -> Bool -> Bool
und = bidirectional (&&)
一般递归。在我看来,对于大多数用例来说,一般递归过于强大:它允许你编写像non_term x = non_term (x + 1)
这样荒谬的函数。这些功能毫无用处。如果我们不将这些无用的函数视为输入,则原始的und
函数是一个非常好的函数(只需实现最后一个案例或使用&&
)。
希望有所帮助。