我读了一篇关于Haskell递归的文章说:
但是lambda演算并没有出现在表面上 由于表达式的匿名性,递归方式。怎么做 你打电话给没有名字的东西?能够写递归 但是,函数对于图灵完整性至关重要。我们使用了 组合器 - 称为Y组合器或定点组合器 - 在lambda演算中编写递归函数。 Haskell有本地人 基于与Y组合子相同的原理的递归能力。
原生递归是什么意思?
请考虑以下代码段:
applyTimes :: (Eq a, Num a) => a -> (b -> b) -> b -> b
applyTimes 0 f b = b
applyTimes n f b = f (applyTimes (n-1) f b)
上述代码不符合Y combinator
原则,因为applyTimes
在函数体本身中被调用,并且之前没有定义。
如果我错了,请证明我的答案。
答案 0 :(得分:4)
"原生递归"意味着语言本身支持递归定义。与简单的lambda演算不同,所有术语都是匿名的,Haskell确实有一种命名表达式的方法;而且,在给出名称的定义时,您可以使用该名称本身。您自己观察到了这一点:在定义applyTimes
时,您使用了名称applyTimes
,因此利用了Haskell对递归的原生支持。
您还可以想象一种支持命名表达式而不是递归的不同语言;事实上,许多功能语言已经区分了" letrec"和"让"分别为递归且不递归的定义形式。在这种语言中,applyTimes
的定义如果使用了" let"形式,如果使用" letrec"则接受形式。
答案 1 :(得分:2)
“Native recursion”只是意味着Haskell以递归定义和let
绑定的形式将递归作为“本机”(内置)语言特性:
-- Recursive definition
map f (x : xs) = f x : map f xs
---
map _ [] = []
-- Recursive “let” binding
main = let ones = 1 : ones in print (take 10 ones)
----
在内部,编译器可以使用定点组合器(fix
)重写这些,例如在类型检查之前进行简化,但我不知道它是否确实这样做了
map f xs = let
map' k (x : xs) = f x : k xs
map' _ [] = []
in fix map' xs
main = let
ones = fix ones'
ones' k = 1 : k
in print (take 10 ones)
在Haskell中,定点组合器是使用对递归绑定的本机支持实现的:
-- Naïve implementation
fix f = f (fix f)
-- Optimisation to improve sharing
fix f = let x = f x in x
任何递归函数都可以用fix
表示:
applyTimes :: (Eq a, Num a) => a -> (b -> b) -> b -> b
applyTimes 0 f b = b
applyTimes n f b = f (applyTimes (n-1) f b)
applyTimes n f b = let
applyTimes' _ 0 = b
applyTimes' k n = f (k (n - 1))
in fix applyTimes' n
注意没有原生递归,Y组合子\ f -> (\ x -> f (x x)) (\ x -> f (x x))
不能用简单类型的lambda演算表示,因为x
没有允许它的类型是一个以x
为参数的函数。具体来说,它失败了“发生检查”:如果x
是一个函数,那么它必须具有a -> b
形式的类型,但如果x
作为参数传递给{{1}那么它必须具有类型x
。显然,a
不能等于a
,因为a -> b
中出现了a
,因此会无限扩展:a -> b
,a -> b
,(a -> b) -> b
等等。
虽然Haskell的类型系统比STLC更强大,但是对于类型化的函数式语言来说,它通常提供递归作为语言的原始部分而不是定点组合器。
答案 2 :(得分:1)
上面的代码不符合
fix :: (a -> a) -> a fix f = f (fix f) applyTimes :: (Eq a, Num a) => a -> (b -> b) -> b -> b applyTimes n f b = fix go n where go rec n = if n == 0 then b else f (rec (n - 1))
原则,因为applyTimes在函数体本身中调用,之前没有定义过。
这样的事情怎么样?
{{1}}