Haskell中的原始递归函数执行

时间:2020-05-30 21:49:32

标签: function haskell recursion primitive

对于函数式编程专业,我需要在haskell中应用原始递归函数。但是,我还不太了解这类函数的定义(和应用程序)。

我们看到要使用的数据类型为Nat,其构造函数为: 数据Nat =零| Succ Nat

据我所知,这意味着“ Nat”类型可以是零或自然后代。

然后我们有一个递归:

recNat :: a -> (Nat -> a -> a) -> Nat -> a
recNat a _ Zero = a
recNat a h (Succ n) = h n (recNat a h n)

我理解这是将递归应用于函数?

我还给出了使用递归的加法函数示例:

addR :: Nat -> Nat -> Nat
addR m n = recNat n (\ _ y -> Succ y) m

但是我不知道它是如何工作的,它使用带有给定两个Nat的recNat函数,还使用了一个匿名函数作为recNat的输入(那部分我不确定它的作用!)< / p>

所以我的主要问题是,该函数到底在> \ _ y-> Succ y

我应该将相同的递归(RecNat)应用于 Nat ,但是我仍然试图理解该示例!

2 个答案:

答案 0 :(得分:3)

您说对了,data Nat = Zero | Succ Nat意味着Nat可以是Zero或另一个Succ的{​​{1}}的元素;这将自然数表示为链接列表,即:

Nat

zero, one, two, three, four, five :: Nat zero = Zero one = Succ Zero -- or: Succ zero two = Succ (Succ Zero) -- Succ one three = Succ (Succ (Succ Zero)) -- Succ two four = Succ (Succ (Succ (Succ Zero))) -- Succ three five = Succ (Succ (Succ (Succ (Succ Zero)))) -- Succ four -- … 的功能是将{em> fold 折叠到recNat上:NatrecNat z k并逐个“倒数”到最后Nat,在每个中间Zero上调用k,然后将Succ替换为Zero

z

lambda recNat z k three recNat z k (Succ (Succ (Succ Zero))) -- by second equation of ‘recNat’: k two (recNat z k two) k (Succ (Succ Zero)) (recNat z k (Succ (Succ Zero))) -- by second equation of ‘recNat’: k two (k one (recNat z k one)) k (Succ (Succ Zero)) (k (Succ Zero) (recNat z k (Succ Zero))) -- by second equation of ‘recNat’: k two (k one (k zero (recNat z k zero))) k (Succ (Succ Zero)) (k (Succ Zero) (k Zero (recNat z k Zero))) -- by first equation of ‘recNat’: k two (k one (k zero z)) k (Succ (Succ Zero)) (k (Succ Zero) (k Zero z)) 的类型为\ _ y -> Succ y;它只是忽略其第一个参数,并返回其第二个参数的后继。以下是a -> Nat -> Nat如何计算两个addR之和的说明:

Nat

如您所见,这里发生的实际上是我们从一个数字中取出每个addR two three addR (Succ (Succ Zero)) (Succ (Succ (Succ Zero))) -- by definition of ‘addR’: recNat three (\ _ y -> Succ y) two recNat (Succ (Succ (Succ Zero))) (\ _ y -> Succ y) (Succ (Succ Zero)) -- by second equation of ‘recNat’: (\ _ y -> Succ y) one (recNat three (\ _ y -> Succ y) one) (\ _ y -> Succ y) (Succ Zero) (recNat (Succ (Succ (Succ Zero))) (\ _ y -> Succ y) (Succ Zero)) -- by application of the lambda: Succ (recNat three (\ _ y -> Succ y) one) Succ (recNat (Succ (Succ (Succ Zero))) (\ _ y -> Succ y) (Succ Zero)) -- by second equation of ‘recNat’: Succ ((\ _ y -> Succ y) zero (recNat three (\ _ y -> Succ y) zero)) Succ ((\ _ y -> Succ y) zero (recNat (Succ (Succ (Succ Zero))) (\ _ y -> Succ y) zero)) -- by application of the lambda: Succ (Succ (recNat three (\ _ y -> Succ y) zero)) Succ (Succ (recNat (Succ (Succ (Succ Zero))) (\ _ y -> Succ y) zero)) -- by first equation of ‘recNat’: Succ (Succ three) Succ (Succ (Succ (Succ (Succ Zero)))) -- by definition of ‘five’: five Succ (Succ (Succ (Succ (Succ Zero)))) 并将其放在另一个数字的末尾,或者等效地,将Succ替换为一个数字加上另一个数字,即步骤如下:

Zero

内部lambda始终会忽略1+1+0 + 1+1+1+0 2 + 3 1+(1+0 + 1+1+1+0) 1+(1 + 3) 1+1+(0 + 1+1+1+0) 1+1+(0 + 3) 1+1+(1+1+1+0) 1+1+(3) 1+1+1+1+1+0 5 的第一个参数,因此使用简单的_定义将recNat替换为值{ {1}}和Zero与功能z

Succ

然后稍微简化一下添加内容:

s

字面意思是“要计算recNat' :: a -> (a -> a) -> Nat -> a recNat' z _ Zero = z recNat' z s (Succ n) = s (recNat z s n) addR' m n = recNat' n Succ m 的总和,请向mn一次。”

如果您为它们创建一个m实例和一个n实例,您可能会发现更容易使用这些数字:

Num

然后,您可以编写Show并将其显示为{-# LANGUAGE InstanceSigs #-} -- for explicitness instance Num Nat where fromInteger :: Integer -> Nat fromInteger n | n <= 0 = Zero | otherwise = Succ (fromInteger (n - 1)) (+) :: Nat -> Nat -> Nat (+) = addR (*) :: Nat -> Nat -> Nat (*) = … -- left as an exercise (-) :: Nat -> Nat -> Nat (-) = … -- left as an exercise abs :: Nat -> Nat abs n = n signum :: Nat -> Nat signum Zero = Zero signum Succ{} = Succ Zero negate :: Nat -> Nat negate n = n -- somewhat hackish instance Show Nat where show n = show (recNat' (+ 1) 0 n :: Int)

答案 1 :(得分:2)

大约recNat x f n用于计算

f (n-1) (f (n-2) (f (n-3) (... (f 0 x))))

因此,它将f应用于xn次,每次也传递一个“计数器”作为f的第一个参数。

在您的情况下,\_ y -> ...忽略“计数器”参数。因此

addR m n = recNat n (\ _ y -> Succ y) m

可以理解为“计算m+n,将m乘以函数Succn。这样可以有效地计算出总和中有((n+1)+1)+1...个的m

您可以尝试以类似方式计算两个自然数的乘积。使用\_ y -> ...并将乘法表示为重复加法。为此,您需要使用已经定义的addR

其他提示:乘法后,如果要计算前身n-1,则“ counter”参数将非常方便,因此请不要丢弃它,而应使用\x y -> ...。之后,您可以得出(截断的)减法作为重复的前任。