我想提一个重要的区别:
cyclic' = [0,1] ++ cyclic'
cyclic'' = [0,1] ++ x
where x = cyclic''
这两个函数在函数的定义引用自身的意义上是递归的。但
cyclic = let x = 0 : y
y = 1 : x
in x
不是!它在x
内部使用,这是递归的,但整体cyclic
不是 - 在它的定义中没有对自身的引用。这也是为什么它们在编译成核心语言时会有所不同的原因。
这有一些重要的实际意义,即递归函数不能被内联,但非递归函数可以。这就是为什么你经常看到类似的定义
fix :: (a -> a) -> a
fix f = let x = f x in x
(来自 的来源)Data.Function
而不是更直接
fix f = f (fix f)
(我不太确定为什么 GHC 不会自动执行此操作。)
为了完整起见,还有其他简短的方法来定义cyclic
:
-- recursive:
cyclic = 0 : 1 : cyclic
-- non-recursive:
cyclic = let x = 0 : 1 : x in x
cyclic = cycle [0,1]
cyclic = fix ([0,1] ++)
更新:举个例子:让我们定义
-- The `const` combinator, defined explicitly so that
-- it gets inlined.
k :: a -> b -> a
k x y = x
fix, fix' :: (a -> a) -> a
fix f = let x = f x in x
fix' f = f (fix' f)
main = do
print $ fix (k 1)
print $ fix' (k 2)
fix'
递归也是如此,而fix
不是(的定义fix
是从 复制的Data.Function
)。当我们使用时会发生什么fix'
?编译器不能内联它,因为内联后,它会再次得到一个包含的表达式fix'
。它应该第二次内联吗?然后第三次?因此,递归函数永远不会被设计内联。另一方面,fix
它不是递归的,因此fix (k 1)
被内联到let x = k 1 x in x
. 然后编译器内联k
,这导致let x = 1 in x
,它被简单地替换为1
.
我们可以通过将编译后的代码转储到核心语言中来验证上述说法:
$ ghc -ddump-simpl -dsuppress-all Main.hs
[1 of 1] Compiling Main ( Main.hs, Main.o )
==================== Tidy Core ====================
Result size of Tidy Core = {terms: 24, types: 27, coercions: 0}
Rec {
fix'_reJ
fix'_reJ = \ @ a_c f_aeR -> f_aeR (fix'_reJ f_aeR)
end Rec }
main
main =
>>
$fMonadIO
($ (print $fShowInteger) (__integer 1))
($ (print $fShowInteger)
(fix'_reJ
(let {
x_aeN
x_aeN = __integer 2 } in
\ _ -> x_aeN)))
main
main = runMainIO main