The GHC docs on the INLINE pragma say the following:
Moreover, GHC will only inline the function if it is fully applied, where “fully applied” means applied to as many arguments as appear (syntactically) on the LHS of the function definition. For example:
comp1 :: (b -> c) -> (a -> b) -> a -> c {-# INLINE comp1 #-} comp1 f g = \x -> f (g x) comp2 :: (b -> c) -> (a -> b) -> a -> c {-# INLINE comp2 #-} comp2 f g x = f (g x)
The two functions
comp1
andcomp2
have the same semantics, butcomp1
will be inlined when applied to two arguments, whilecomp2
requires three. This might make a big difference if you saymap (not `comp1` not) xs
which will optimise better than the corresponding use of
comp2
.
Does this mean if I’ve got a function in the form of:
{-# INLINE f #-}
f x = {- something small -}
I should be changing it to be like this?
{-# INLINE f #-}
f = \x -> {- something small -}
if there’s a significant chance of it being partially applied, such as in a call to map
(so inside the map
call the unfolding is exposed an it can actually be inlined)?
If the docs are right about this, my next question is why? If making something {-# INLINE #-}
or {-# INLINABLE #-}
exposes the implementation in the interface file why can’t the caller just transform it appropriately? It’s got all the code there anyway?
Also, is there any downside to doing this transformation if it is required for inlining?
The one thing I can think of is by transforming:
f x = {- something small -}
to
f = \x -> {- something small -}
Then f
is now a CAF and CAFs can be bottom, although if one exposes the implementation of f
via {-# INLINABLE #-}
, I’d hope GHC can statically work out that f
isn’t bottom and remove the check because even if:
f = \x -> undefined
f
is still non-bottom (even if f 5
is bottom).
Presumably there is a downside of doing this transformation, as otherwise I would think GHC would do it automatically (instead of insisting people uglify their code by pushing their function arguments into lambdas).