我注意到 GHC manual说“对于自递归函数,循环断路器只能是函数本身,因此总是忽略 INLINE 杂注。”
这不是说像 map
这样的常见递归函数构造的每个应用程序吗? , zip
, scan*
, fold*
, sum
等不能内联?
当你使用它们时,你总是可以重写所有这些函数,添加适当的严格标签,或者使用像推荐的“流融合”这样的花哨技术here .
然而,这一切难道不是极大地限制了我们编写既快速又优雅的代码的能力?
最佳答案
事实上,GHC 目前不能内联递归函数。然而:
fac :: (Eq a, Num a) => a -> a
fac 0 = 1
fac n = n * fac (n-1)
f :: Int -> Int
f x = 1 + fac x
GHC 会发现
fac
用于类型 Int -> Int
并生成 fac
的专用版本对于该类型,它使用快速整数运算。这种特化在一个模块内自动发生(例如,如果
fac
和 f
在同一个模块中定义)。对于跨模块特化(例如,如果 f
和 fac
定义在不同的模块中),用 an INLINABLE pragma 标记要特化的功能:{-# INLINABLE fac #-}
fac :: (Eq a, Num a) => a -> a
...
map
、 filter
、 fold*
)。这种转变转map f [] = []
map f (x:xs) = f x : map f xs
进入
map f xs0 = go xs0
where
go [] = []
go (x:xs) = f x : go xs
这样一个电话,如
g :: [Int] -> [Int]
g xs = map (2*) xs
将有
map
内联并成为 g [] = []
g (x:xs) = 2*x : g xs
此转换已应用于 Prelude 函数,例如
foldr
和 foldl
. foldr
的非递归函数一样。和/或 build
;那么所有的递归都被捕获到 foldr
, 并且有处理 foldr
的特殊规则.利用这种融合原则上很容易:避免手动递归,首选库函数,如
foldr
, map
, filter
, 以及 this list 中的任何函数.特别是,以这种风格编写代码会产生“同时又快又优雅”的代码。 与快捷方式融合一样,在文本和矢量中利用流融合原则上很容易:避免手动递归,首选已标记为“受融合”的库函数。
关于optimization - GHC真的可以永远inline map、scanl、foldr等吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9658438/