哈斯克尔和懒惰

时间:2015-06-20 23:10:00

标签: haskell lazy-evaluation termination

我刚刚开始学习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?

3 个答案:

答案 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演算是顺序,确定性语言。因此,一旦惰性二元函数被馈送两个布尔值(可能是底部值),它需要决定在另一个之前评估哪一个,从而修复评估顺序。这将打破orand的对称性,因为如果所选参数发生偏差,则or / and也会发散。

正如其他人提到的,在Haskell中,有一个通过非顺序方式定义unamb的库,即利用下面的一些并发性,因此超出了PCF的功能。有了它,您可以定义并行orand

答案 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

从中我们可以看到:

  1. 您的功能不完整,因为最后一种情况是undefined
  2. 如果第一个参数是True,你的函数会计算第二个参数,即使它不需要。请记住,case表达式总是强制评估要检查的表达式。
  3. 您的函数首先检查x,然后在y评估为x的情况下检查True。因此,这里确实存在明确的评估顺序。请注意,如果x评估为False,则永远不会评估y(证明und确实是懒惰的。)
  4. 正是由于这种评估顺序,你的表达式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) Falseund False (non_term 1)会有所不同。

    这是否意味着Haskell错误/错误?没有.Haskell做正确的事情,根本不会产生任何答案。对于人类(可以并行评估两个表达式),und (non_term 1) False的结果似乎必须是False。但是,计算机必须始终具有评估顺序。

    那么,实际问题是什么?在我看来,实际问题是/或:

    1. 并行评估。 Haskell应该并行地评估表达式并选择首先终止的表达式:

      import Data.Unamb (unamb)
      
      type Endo a = a -> a
      
      bidirectional :: Endo (a -> a -> b)
      bidirectional = unamb <*> flip
      
      und :: Bool -> Bool -> Bool
      und = bidirectional (&&)
      
    2. 一般递归。在我看来,对于大多数用例来说,一般递归过于强大:它允许你编写像non_term x = non_term (x + 1)这样荒谬的函数。这些功能毫无用处。如果我们不将这些无用的函数视为输入,则原始的und函数是一个非常好的函数(只需实现最后一个案例或使用&&)。

    3. 希望有所帮助。