给出二进制自然数,零例为“两次”,而“两次加一”。
如何使用原始递归(仅使用函数foldBNat
)来表达加法?
-- zero | n * 2 | n * 2 + 1
data BNat = Z | T BNat | TI BNat
deriving (Show)
foldBNat :: BNat -> t -> (BNat -> t -> t) -> (BNat -> t -> t) -> t
foldBNat n z t ti =
case n of
Z -> z
T m -> t m (foldBNat m z t ti)
TI m -> ti m (foldBNat m z t ti)
div2 :: BNat -> BNat
div2 n = foldBNat n Z (\m _ -> m) (\m _ -> m)
pred :: BNat -> BNat
pred n = foldBNat n Z (\_ r -> TI r) (\m _ -> T m)
succ :: BNat -> BNat
succ n = foldBNat n (TI Z) (\m _ -> TI m) (\_ r -> T r)
答案 0 :(得分:3)
想法:要计算a + b
,我们需要增加b
a
次。所以:
0 + b = b
1 + b = succ b
2 + b = succ (succ b)
3 + b = succ (succ (succ b))
...
我们可能会从写作开始
plus a b = foldBNat a b (\m r -> ...
但是在这里我们陷入困境:m
代表a
的一半(因为a = T m
在这里,即a = 2 * m
),而r
是递增{的结果{1}} b
次(即m
)。我们对此无能为力。我们想要的是m + b
,我们无法直接从a + b = 2*m + b
获得。应用m + b
只会给我们T
,它太大了,根据规则,我们不能直接在2 * (m + b) = 2*m + 2*b
上递归来计算plus
。
我们需要的是一种更直接的方法来操纵m + (m + b) = 2*m + b
个操作。
想法:不要直接计算数字;而是计算一个函数(将其参数增加一定次数)。所以:
succ
我们可以直接实现:
incBy 0 = id
incBy 1 = succ
incBy 2 = succ . succ
incBy 3 = succ . succ . succ
...
这里incBy :: BNat -> (BNat -> BNat)
incBy n = foldBNat n id (\_ r -> r . r) (\_ r -> succ . r . r)
给我们提供了一个函数,该函数使数字递增的次数是r . r
的两倍(通过应用r
两次)。
现在我们可以简单地将加法定义为:
r
(由于plus :: BNat -> BNat -> BNat
plus n m = (incBy n) m
,这是多余的)。