在haskell平台的许多功能的实现中有一个非常常见的模式困扰我,但我无法找到解释。它是关于使用嵌套函数进行优化的。
嵌套函数在where子句用于进行尾递归时的原因对我来说非常清楚(如在length中),但内部函数与top的类型完全相同的目的是什么-一级?例如,它发生在Data.Set
module的许多函数中,如下所示:
-- | /O(log n)/. Is the element in the set?
member :: Ord a => a -> Set a -> Bool
member = go
where
STRICT_1_OF_2(go)
go _ Tip = False
go x (Bin _ y l r) = case compare x y of
LT -> go x l
GT -> go x r
EQ -> True
#if __GLASGOW_HASKELL__ >= 700
{-# INLINABLE member #-}
#else
{-# INLINE member #-}
#endif
我怀疑它可能与记忆有关,但我不确定。
编辑:由于dave4420建议严格,以下是可以在同一模块中找到的STRICT_1_OF_2
宏的定义:
-- Use macros to define strictness of functions.
-- STRICT_x_OF_y denotes an y-ary function strict in the x-th parameter.
-- We do not use BangPatterns, because they are not in any standard and we
-- want the compilers to be compiled by as many compilers as possible.
#define STRICT_1_OF_2(fn) fn arg _ | arg `seq` False = undefined
我理解这个宏是如何工作的,但是我仍然不明白为什么go
和STRICT_1_OF_2(go)
一起不应该移到顶层代替member
。
也许这不是因为优化,而是因为风格选择?
编辑2 :我在设置模块中添加了INLINABLE
和INLINE
部分。我没想到乍一看他们可以和它有关。
答案 0 :(得分:16)
在本地工作者周围拥有INLINABLE
(或INLINE
)包装器的一个直接好处是专业化。在调用站点上使用固定元素类型定义member
的方式,Ord
字典可以被丢弃,并且相应的compare
函数内联或至少作为静态参数传递
使用直接递归定义,member
成为一个循环断路器并且没有内联,因此呼叫站点专业化没有完成 - 至少是INLINABLE
之前的故事附注
使用INLINABLE
编译指示,调用站点专业化确实发生,字典被删除,但我有几次尝试没有设法编写一个与当前一样高效的直接递归member
。但是对于INLINE
pragma,法律仍然存在,没有内联的断路器;对于member
,这意味着没有可以消除字典的呼叫站点专业化。
因此,可能没有必要再以该样式编写函数,但是,获得调用站点的专业化。
答案 1 :(得分:13)
GHC无法内联递归函数。定义member
的方式,递归仅限于go
,而member
本身不是递归的,可以内联。
GHC确保内联不能永远持续下去:每一个 相互递归组由一个或多个环路断路器切断 从未内联(参见GHC内联机构的秘密,JFP 12(4)2002年7月)。 GHC尝试不选择具有INLINE编译指示的函数作为循环 断路器,但是当没有选择时,即使是INLINE功能也可以 选中,在这种情况下,INLINE编译指示将被忽略。例如,对于 一个自递归函数,环路断路器只能是函数 本身,所以INLINE编译指示总是被忽略。