As an example, suppose I want to write a monadic and non-monadic map over lists. I'll start with the monadic one:
import Control.Monad
import Control.Monad.Identity
mapM' :: (Monad m) => (a -> m b) -> ([a] -> m [b])
mapM' _ [] = return []
mapM' f (x:xs) = liftM2 (:) (f x) (mapM f xs)
Now I want to reuse the code to write the pure map
(instead of repeating the code):
map' :: (a -> b) -> ([a] -> [b])
map' f = runIdentity . mapM' (Identity . f)
What is necessary to make map'
as optimized as if it were written explicitly like map
is? In particular:
Is it necessary to write
{-# SPECIALIZE mapM' :: (a -> Identity b) -> ([a] -> Identity [b]) #-}
or does GHC optimize
map'
itself (by factoring outIdentity
completely)?Anything else (more pragmas) need to be added?
- How can I verify how well the compiled
map'
is optimized wrt the explicitly written code formap
?
Well, let us ask the compiler itself.
Compiling the module
with
ghc -O2 -ddump-simpl -ddump-to-file PMap.hs
(ghc-7.6.1, 7.4.2 produces the same except for unique names) produces the following core formap'
Yup, only
cast
s, no real overhead. You get a local workergo
that acts exactly asmap
does.Summing up: You only need
-O2
, and you can verify how well optimised the code is by looking at the core (-ddump-simpl
) or, if you can read it, at the produced assembly (-ddump-asm
) resp LLVM bit code-ddump-llvm
).It is probably good to elaborate a bit. Concerning
the answer is that if you use the specialisation in the same module as the general function is defined, then in general you don't need a
{-# SPECIALISE #-}
pragma, GHC creates the specialisation on its own if it sees any benefit in that. In the above module, GHC created the specialisation rulethat also benefits any uses of
mapM'
at theIdentity
monad outside the defining module (if compiled with optimisations, and the monad is recognised asIdentity
in time for the rule to fire).However, if GHC doesn't understand the type to specialise to well enough, it may not see any benefit and not specialise (I don't know it well enough to tell whether it will try anyway - so far I have found a specialisation each time I looked).
If you want to be sure, look at the core.
If you need the specialisation in a different module, GHC has no reason to specialise the function when it compiles the defining module, so in that case a pragma is necessary. Instead of a
{-# SPECIALISE #-}
pragma demanding a specialisation for a few hand-picked types, it is probably better - as of ghc-7 - to use an{-# INLINABLE #-}
pragma, so that the (slightly modified) source code is made accessible in importing modules, which allows specialisations for any required types there.Different uses may of course require different pragmas, but as a rule of thumb,
{#- INLINABLE #-}
is the one you want most. And of course{-# RULES #-}
can do magic the compiler cannot do on its own.