在Haskell中改变每个函数调用的表达式评估顺序

时间:2015-02-07 17:16:25

标签: haskell infinite-loop lazy-evaluation ghc expression-evaluation

假设我有以下程序:

foo x y = let l1 = foo 0 x
              l2 = foo 0 y
          in l1 + l2

这只是一个简单的例子,但我认为足以用于演示目的。对于 foo 的每次新(递归)调用,我怎样才能更改 l1 l2 ?我知道它们在表达式中被评估为懒惰(在本例中是在运算符中的表达式)而不是在声明它们时,但是我需要一种如所述的方式,因为可能存在程序进入无限循环的情况。如果在第二个评估的参数 l2 上发生此无限递归,则没有问题,因为 l1 始终会获得在 * l2 之前评估,但如果是相反的方式,即无限评估 l1 表达式, * l2 ***没有机会评估。因此,如果我可以在每个新的 foo l2 表达式评估>函数调用,问题就解决了。寻找一个好的/一般的解决方案。

编辑:忘记提及 x y 或两者都可能是无限结构(列表) ,那就是问题所在。

1 个答案:

答案 0 :(得分:1)

问题

为了得到一个好的答案,我们首先需要一个具体的问题。考虑自然数为零Z或另一个自然数S n的后继n

data Nat = Z | S Nat

zero = Z
one  = S Z
two  = S $ S Z

在Haskell中,我们还可以编写无限的重复数据结构,如

infinity = S infinity

只要一个Nat号码不是infinity,我们就可以确定它是偶数还是奇数。

even :: Nat -> Bool
even  Z    = True
even (S n) = odd n

odd :: Nat -> Bool
odd  Z    = False
odd (S n) = even n

对于有限NatevenTrueFalseeven infinityundefined。这没关系,但如果我们想检查两个数字中的任何一个是even怎么办?我们可以编写一个天真的实现:

eitherEven :: Nat -> Nat -> Bool
eitherEven x y = even x || even y

只要第一个参数是有限的,这就很好。在下文中,even是任意偶数,odd是任何奇数。

eitherEven even even     == True
eitherEven even odd      == True
eitherEven even infinity == True
eitherEven odd  even     == True
eitherEven odd  odd      == False
eitherEven odd  infinity == undefined

但是当第一个参数是无限的时,即使第二个参数是True

,它也不会返回Even
eitherEven infinity even     == undefined -- this should be True
eitherEven infinity odd      == undefined
eitherEven infinity infinity == undefined

一个简单的解决方案

该问题的一个简单解决方案是在测试第一个参数和测试第二个参数之间交替。当我们以递归方式调用函数时,我们将参数交换为替换正在测试的两个参数中的哪一个。

eitherEven :: Nat -> Nat -> Bool
eitherEven Z         _ = True
eitherEven (S Z)     y = even y 
eitherEven (S (S x)) y = eitherEven y x

即使第一个参数不是有限的,它也具有所需的输出。

> eitherEven infinity two
True

对于不对称处理参数的更复杂问题,您可以传递Bool标志并在每一步上翻转它。通常,您可以使用任何状态机来跟踪您的工作地点。

这个解决方案不是很令人满意,因为当我们想要测试三个数字中的任何一个是否均匀时,它不能立即解决该怎么做。为此,我们需要编写一个新函数。

anyEven3 :: Nat -> Nat -> Nat -> Bool
anyEven3 Z         _ _ = True
anyEven3 (S Z)     y z = eitherEven y z
anyEven3 (S (S x)) y z = anyEven3 y z x

我们将x放在最后,因为在再次尝试y之前,我们想要同时尝试zx。我们正在排队。如果我们能够证明队列中的第一件事产生了结果True,那么我们就完成了。如果我们可以证明队列中的第一件事没有产生结果,我们将其从队列中删除并使用适用于较小输入集的版本。如果我们无法确定队列中第一件事的结果,我们就把它放在最后。在eitherEven中可以看到相同的模式,它带有两个参数,甚至在even中只有一个参数。

anyEven :: [Nat] -> Bool
anyEven = go []
    where
        go [] [] = False
        go r  [] = go [] (reverse r)
        go r (      Z  :_ ) = True
        go r (    S Z  :ns) = go r     ns
        go r ((S (S x)):ns) = go (x:r) ns