对于函数式编程专业,我需要在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 ,但是我仍然试图理解该示例!
答案 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
上:Nat
取recNat 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
的总和,请向m
加n
一次。”
如果您为它们创建一个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
应用于x
达n
次,每次也传递一个“计数器”作为f
的第一个参数。
在您的情况下,\_ y -> ...
忽略“计数器”参数。因此
addR m n = recNat n (\ _ y -> Succ y) m
可以理解为“计算m+n
,将m
乘以函数Succ
到n
。这样可以有效地计算出总和中有((n+1)+1)+1...
个的m
。
您可以尝试以类似方式计算两个自然数的乘积。使用\_ y -> ...
并将乘法表示为重复加法。为此,您需要使用已经定义的addR
。
其他提示:乘法后,如果要计算前身n-1
,则“ counter”参数将非常方便,因此请不要丢弃它,而应使用\x y -> ...
。之后,您可以得出(截断的)减法作为重复的前任。