使用原始递归对二进制自然数进行加法

时间:2019-04-20 09:18:32

标签: haskell

给出二进制自然数,零例为“两次”,而“两次加一”。

如何使用原始递归(仅使用函数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)

1 个答案:

答案 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 ,这是多余的)。