You can make it a lot simpler to read with some tricks:
- Make your functions as simple as possible. Avoid I/O if possible.
- Suppress unnecessary info like module qualifiers and type applications using
-dsuppress-all
. (Unless you actually care about that info)
- Do not suppress type signatures (
-dno-suppress-type-signatures
). Type signatures often tell me a lot about which functions Iâm looking at if the names have been mangled, so I re-enable this option.
- Disable âtypeable bindsâ using
-dno-typeable-binds
. By default, GHC generates a bunch of special bindings to support the Typeable
mechanism, but these are often not necessary. This can break programs that use Typeable
, though.
- Use
-fforce-recomp
if youâre only changing flags and not the code itself. Otherwise it will output nothing.
My full command is then:
ghc -ddump-simpl -dsuppress-all -dno-typeable-binds -dno-suppress-type-signatures -fforce-recomp
(With optionally -O
or -O2
for optimizations)
Using those tips we might start with a program like this:
module T where
f = abs . abs
g = abs
Which yields this output (if compiled with optimizations enabled):
f :: Integer -> Integer
f = \ (x_aAh :: Integer) -> integerAbs (integerAbs x_aAh)
g :: Integer -> Integer
g = integerAbs
Now you might try adding a rewrite rule:
module T where
{-# RULES "abs idem" forall x. abs (abs x) = abs x #-}
f = abs . abs
g = abs
Now if we compile with optimizations (-O
and -O2
both imply -fenable-rewrite-rules
and both give the same result in this case) we get⌠no change:
f :: Integer -> Integer
f = \ (x_aAv :: Integer) -> integerAbs (integerAbs x_aAv)
g :: Integer -> Integer
g = integerAbs
Wait, but didnât @darkxeroâs approach work? Well, yes, but that was really a coincidence. You might have noticed that Iâm using a slightly different definition of f
. Letâs see what happens when I use the exact same:
module T where
{-# RULES "abs idem" forall x. abs (abs x) = abs x #-}
f x = (abs . abs) x
g = abs
Compiling without optimizations gives us:
f :: forall {c}. Num c => c -> c
f = \ (@c_azf) ($dNum_azp :: Num c_azf) (x_agV :: c_azf) ->
. (abs $dNum_azp) (abs $dNum_azp) x_agV
g :: Integer -> Integer
g = abs $fNumInteger
Now we can see that the type of f
has changed drastically. It is now overloaded over the Num
type class instead of specialized to Integer
. Why did this happen? Because of the monomorphism restriction. In short, if we donât write a type signatures then the compiler will only generalize, i.e. add type class constraints, to function definitions with at least one explicit argument. So it add the constraints to f
, which has one explicit argument x
, but not to g
which has no explicit arguments.
If we now compile with optimizations we can see that the rewrite rule does work in this case:
f :: forall {c}. Num c => c -> c
f = \ (@c_azf) ($dNum_azp :: Num c_azf) (x_agV :: c_azf) ->
abs $dNum_azp x_agV
g :: Integer -> Integer
g = integerAbs
Now why is that? We can find a hint in the dump of the rewrite rule which is conveniently included (but we could otherwise use -ddump-rules
to view them):
"abs idem"
forall (@a_azD)
($dNum_azE :: Num a_azD)
($dNum1_azG :: Num a_azD)
(x_ajy :: a_azD).
abs $dNum_azE (abs $dNum1_azG x_ajy)
= abs $dNum_azE x_ajy
This shows that the rewrite rule only works on the general abs
method from the Num
class, but not on the specialized integerAbs
function. And specialization often happens before other rules can fire. GHC does tell us that a bit cryptically in a warning message:
T.hs:3:11: warning: [GHC-87502] [-Winline-rule-shadowing]
Rule "abs idem" may never fire
because rule "Class op abs" for âabsâ might fire first
Suggested fix: Add phase [n] or [~n] to the competing rule
|
3 | {-# RULES "abs idem" forall x. abs (abs x) = abs x #-}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The âClass op absâ is a special rewrite rule that GHC uses to specialize abs
(to absInteger
in this case). The suggestion to add phases is unhelpful in this case because the âClass op absâ rule is automatically generated by GHC and cannot be modified by you.
In conclusion, use some options to suppress unnecessary information, and rewrite rules on type class methods are not really useful.