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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With