问题
为了得到一个好的答案,我们首先需要一个具体的问题。考虑为零或另一个自然数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
对于有限Nat
的 s ,even
isTrue
或is 。这没关系,但是如果我们想检查两个数字中的任何一个是否是?我们可以编写一个简单的实现:False
even infinity
undefined
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
,z
然后x
再试一次。我们正在排队。如果我们可以证明队列中的第一件事产生了结果,那么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