isOdd, isEven :: Int -> Bool
isOdd n
| n<=0 = False
| otherwise = isEven (n-1)
isEven n
| n<0 = False
| n==0 = True
| otherwise = isOdd (n-1)
我不太理解这段代码,在isOdd函数中,只定义了什么是False,然后转到isEven函数,那么Haskell如何知道n是奇数呢?
isOdd, isEven :: Int -> Bool
isOdd n
| n<=0 = False
| otherwise = isEven (n-1)
isEven n
| n<0 = False
| n==0 = True
| otherwise = isOdd (n-1)
我不太理解这段代码,在isOdd函数中,只定义了什么是False,然后转到isEven函数,那么Haskell如何知道n是奇数呢?
isOdd
和isEven
。它们是相互定义的:如果一个数字为负或零,则它是“非奇数”,如果比该数字少一,那么它就是“偶数”。如果一个数字为负,则它是“非偶数”,如果它为零或比该数字少一,则它是“奇数”。even, odd :: (Integral a) => a -> Bool
even n = n `rem` 2 == 0
odd = not . even
INLINEABLE
pragma只是一种优化方式,可以忽略不计。even
特化为机器 Word 或机器 Int,以使其在这些类型上变得稍微快一些。如果在编译时知道某个值的值,它甚至可以修剪掉不可达情况。let x = True in if x then 5 else 10
会被优化成 5
,因为实际检查 True 是否为 True 是浪费时间。在评估 f x y
时,它可以先评估 x 或 y,结果是相同的。 - Lazersmoke_|_
周围的行为是指称语义,而不是操作语义。 严格性/惰性是“结果”的一部分,因此必须通过优化来保留。 执行是操作性的,评估是指称性的。 执行由编译器决定,评估由Haskell的语义定义。 - Lazersmoke这些函数是相互递归的(每个函数都可以调用另一个函数),有基本情况。让我们通过使用isOdd
来进行示例评估。首先,我将把警卫条件改为等效的if
语句,以便在此答案中更加清晰(尽管我通常建议使用警卫条件)。
isOdd, isEven :: Int -> Bool
isOdd n =
if n <= 0
then False
else isEven (n-1)
isEven n =
if n < 0
then False
else if n == 0
then True
else isOdd (n-1)
现在,我们可以尝试通过一个示例评估[1]来进行步骤:
isOdd 3
==> if 3 <= 0 (Applying isOdd to 5 and expanding the body)
then False
else isEven (3-1)
==> isEven (3-1) (3 > 0)
==> if 2 < 0
then False
else if 2 == 0
then True
else isOdd (2-1)
==> isOdd (2-1) (4 > 0, so the first two branches aren't taken)
==> if 1 <= 0 (Applying isOdd to 1 and expanding the body)
then False
else isEven (1-1)
==> isEven 0
==> if 0 < 0
then False
else if 0 == 0
then True
else isOdd (0-1)
==> True (0 == 0, so the second branch is taken)
n
大于0
,则如果它的前一个数(n-1
)是偶数,则n
是奇数,反之亦然。这是因为偶数和奇数交替出现。当你遇到不理解的函数(或者在本例中是一对函数)时,我建议使用像这样的小例子逐步进行评估。请注意,以下注释[1]不影响此问题,我已经稍微简化了形状为x-1
的表达式被简化为相应的数字。isEven
被调用并传入参数0
(因为它可以被2
整除,所以是偶数)。第二个分支是说“如果0 == 0
那么就是True
...”。True
是评估isOdd 3
的最终结果。中间的内容是从isOdd 3
到最终结果True
所经过的步骤。从更高层次的角度来看,整个逻辑链类似于“要知道3是否为奇数,请查看2是否为偶数。要查看2是否为偶数,请查看1是否为奇数。要查看1是否为奇数,请查看0是否为偶数。0被“定义”为偶数(可以这么说)”。 - David Young0
是偶数,所以所有前面的语句都是正确的(1
是奇数,2
是偶数,最后,3
是奇数)。 - David Young这被称为“相互递归”或“相互递归函数”,就像您需要定义终止状态(或退出条件)的递归函数一样。不过,您的定义并不是最好的,这里有一个更好的替代方案
isEven,isOdd :: Int -> Bool
isEven 0 = True
isEven n = isOdd (n - 1)
isOdd 0 = False
isOdd n = isEven (n - 1)
这里设置了终止条件为0
(对称地),并且相互递归最终会在其中一个上结束。
请注意,这仅定义为非负整数,但没有通过类型Int
加以强制。
您的定义也不正确,但至少对于负数将会终止。
isOdd n | n < 0 = isOdd (-n)
(对于isEven
同样如此)是完全合理的。 - chepner