Why does haskell require multiple rewrite rules depending on the function composition technique and length? Is there a way to avoid this?
For example, given the following code…
{-# RULES
"f/f" forall a. f ( f a ) = 4*a
#-}
f a = 2 * a
this works for
test1 = f ( f 1 )
however we need to add a rule for
test2 = f . f $ 1
and
test3 = f $ f 1
leaving us with the following rules
{-# RULES
"f/f1" forall a. f ( f a ) = 4 * a
"f/f2" forall a. f . f $ a = 4 * a
"f/f3" forall a. f $ f $ a = 4 * a
#-}
However, when we string these together or use some other forms of composition the rules do not fire.
test4 = f . f . f $ 1
test5 = f $ f $ f $ 1
test6 = f $ 1
Why is this? Do I have to write rewrite rules for every possible implementation?
The rule doesn’t fire in many cases because the very simple function
fis inlined before the rule had a chance to fire. If you delay the inlining,the rule
should fire for all these cases (worked here with 7.2.2 and 7.4.1).
The reason is that the rule matcher is not overly elaborate, it matches only expressions having the syntactic form of the rule (not entirely true, the rule body undergoes some normalisation too). The expressions
f $ f 3orf . f $ 4do not match the syntactic form of the rule. For the rule to match, some rewriting has to take place,($)and(.)have to be inlined before the rule matches the expression. But if you do not preventffrom being inlined in the first phase of the simplifier, it gets replaced by its body in the same run as($)and(.)are inlined, so in the next iteration, the simplifier doesn’t seefanymore, it only sees2*(2*x), which doesn’t match the rule.