Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell platform: nested functions and optimization

Tags:

There's a very common pattern in the implementation of many functions in the haskell platform that bothers me, but I wasn't able to find an explanation. It's about the use of nested functions for optimization.

The reason for nested functions in where clauses when they aim to make tail recursion is very clear to me (as in length), but what is the purpose when the inner function has exactly the same type as the top-level one? It happens, in example, in many functions of the Data.Set module, like the following one:

-- | /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

I suspect it may have something to do with memoization, but I'm not sure.


edit: Since dave4420 suggests strictness, here is the definition for the STRICT_1_OF_2 macro that can be found in the same module:

-- 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

I understand how this macro works, however I still do not see why go together with STRICT_1_OF_2(go) shouldn't be moved at top-level in place of member.

Maybe it's not because of an optimization, but simply because of a stylistic choice?


edit 2: I added the INLINABLE and INLINE part from the set module. I did not thought that they could have something to do with it at first glance.

like image 296
Riccardo T. Avatar asked Mar 18 '12 10:03

Riccardo T.


1 Answers

One immediate benefit of having an INLINABLE (or INLINE) wrapper around the local worker is specialisation. The way that member is defined, at the call site, with a fixed element type, the Ord dictionary can be discarded and the appropriate compare function inlined or at least passed as a static argument.

With a directly recursive definition, member becomes a loop-breaker and isn't inlined, so call-site specialisation isn't done - that was, at least, the story before the INLINABLE pragma.

With an INLINABLE pragma, call site specialisation does take place, the dictionary is eliminated, but I have in a few attempts not managed to write a directly recursive member that is as efficient as the current. But with an INLINE pragma, the law still stands, a loop-breaker is not inlined; for member that means no call-site specialisation to eliminate the dictionary is possible.

So it may not be necessary anymore to write the functions in that style, but it was, to get call-site specialisation.

like image 138
Daniel Fischer Avatar answered Sep 17 '22 12:09

Daniel Fischer