2012-03-18 94 views
19

Haskell平台中的许多函数的实现中有一个非常常见的模式困扰我,但我无法找到解释。这是关于使用嵌套函数进行优化。Haskell平台:嵌套函数和优化

原因在where子句中嵌套函数时,他们的目标是使尾递归是很清楚,我(在length),但目的是什么,当内部函数具有完全相同的类型作为最高一级?它发生,例如,在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:我增加了INLINABLEINLINE部从所述一组模块。我没有想到,他们乍一看可能会有什么用处。

+1

我怀疑这与严格分析有关:编译器应该推断出'member'的第一个参数必须被评估,但'go'的第一个参数总是被评估过。但我不确定。 – dave4420 2012-03-18 10:23:46

+0

@ dave4420:谢谢你的建议。我用一些关于函数严格性的更多信息更新了我的问题,我希望这会有所帮助。 – 2012-03-18 10:51:45

+1

我认为这纯粹是一个风格问题。但是你可以通过'go'中的'x'释放一些微小的增益。我不喜欢这样写蜜蜂的风格,因为它似乎暗示x在每次迭代中都会被seq:ed。另外,当它不一定是(但那很小)时,它在'x'中使成员严格。 – augustss 2012-03-18 11:24:16

回答

16

一个直接的好处就是专业化。 member在调用站点上使用固定元素类型定义的方式,Ord字典可以被丢弃,并且合适的compare函数内联或至少作为静态参数传递。

有了直接递归定义,member成为环开关和断路器不内联,所以叫现场的专业化不这样做 - 这是,至少,在INLINABLE编译之前的故事。

使用INLINABLE编译指示,呼叫站点专门化确实发生,字典被删除,​​但我有一些尝试没有设法编写一个与当前一样高效的直接递归member。但有了INLINE编译指示,法律仍然有效,不会内联断路器;对于member这意味着没有呼叫站点专业化,以消除字典是可能的。

因此,可能不再需要以该风格编写函数,但它是为了获得呼叫站点专业化。

13

GHC不能内联递归函数。定义方式member,递归限于gomember本身不递归并且可以内联。

GHC user guide:

GHC保证了内联不能永远持续下去:每次 相互递归组由被 从不内联(见GHC衬里,JFP的秘密一个或多个回路断路器切12(4)2002年7月)。 GHC尝试不选择带有INLINE杂注的函数作为循环 断路器,但是如果没有选择,即使可以选择INLINE函数 ,在这种情况下,INLINE杂注将被忽略。例如,对于 自递归函数,环路断路器本身只能是功能 ,所以始终忽略INLINE杂注。周围有当地工人的INLINABLE(或INLINE)包装的

+1

谢谢。有了这个,我更近了一步,但我仍然不明白。首先声明它是“INLINE”,如果它只是调用另一个非内联函数'go',那么有什么意义呢? – 2012-03-18 11:32:52

+0

所以,通过定义一个嵌套函数,我们以某种方式允许甚至为递归函数'member'提供内联,让GHC有机会选择'go'作为循环断路器,对吧? – 2012-03-18 12:01:25

+1

@Riccardo,好点。在http://hackage.haskell.org/trac/ghc/wiki/Inlining 有一些相关的信息也许这可以澄清一点。 – 2012-03-18 12:27:58