Arrow notation desugaring defeats user-specified INLINE pragmas by trying to share applications of operations to dictionaries
When GHC desugars a proc
expression, it turns it into uses of the arrow combinators, such as arr
, >>>
, and first
. For example, given a definition like
foo = proc x -> do
y <- f -< x
returnA -< (x, y)
GHC desugars it into something like this:
foo =
(>>>) $dArrowFoo
(arr $dArrowFoo (\x -> (x, x))
((>>>) $dArrowFoo
(first $dArrowFoo f)
(arr $dArrowFoo (\(y, x) -> (x, y)))
But this leads to a lot of dictionary selectors on the same dictionary, so the desugaring tries to introduce a bit of sharing. The actual desugaring is closer to something like this:
foo =
let arr_Foo = arr $dArrowFoo
comp_Foo = (>>>) $dArrowFoo
first_foo = first $dArrowFoo
in comp_Foo (arr_Foo (\x -> (x, x))
(comp_Foo (first_foo f)
(arr_Foo (\(y, x) -> (x, y)))
This is a nice attempt to save on a bit of duplication, but it totally destroys optimization if the user really, really wants these combinators to be inlined. Even if they wrote {-# INLINE (.) #-}
, its definition will be inlined on the RHS of comp_Foo
, not at each use of comp_Foo
, which is probably not desired!
As a workaround, I’ve found that I can write {-# INLINE [100] (.) #-}
, which ensures the inlining won’t occur until after the gentle simplifier phase runs. This means comp_Foo
’s RHS will be optimized to $c._Foo
due to the class op rule firing, and since that RHS is trivial, it will be inlined into all use sites during the gentle simplifier phase. When the INLINE
pragma becomes active in phase 2, it will be aggressively inlined at all use sites, as desired. This works, but it is clearly a horrible hack.
It seems more likely that proc
notation ought to just desugar to uses of the combinators directly. After all, do
notation doesn’t, to my knowledge, make any attempt to try to share the application of >>=
to its dictionary. This would just make proc
consistent with that behavior.