Transitivity of Auto-Specialization in GHC
Solution 1:
Short answers:
The question's key points, as I understand them, are the following:
- "is the auto-specialization transitive?"
- Should I only expect (+) to be specialized transitively with an explicit pragma?
- (apparently intended) Is this a bug of GHC? Is it inconsistent with the documentation?
AFAIK, the answers are No, mostly yes but there are other means, and No.
Code inlining and type application specialization is a trade-off between speed (execution time) and code size. The default level gets some speedup without bloating the code. Choosing a more exhaustive level is left to the programmer's discretion via SPECIALISE
pragma.
Explanation:
The optimiser also considers each imported INLINABLE overloaded function, and specialises it for the different types at which it is called in M.
Suppose f
is a function whose type includes a type variable a
constrained by a type class C a
. GHC by default specializes f
with respect to a type application (substituting a
for t
) if f
is called with that type application in the source code of (a) any function in the same module, or (b) if f
is marked INLINABLE
, then any other module that imports f
from B
. Thus, auto-specialization is not transitive, it only touches INLINABLE
functions imported and called for in the source code of A
.
In your example, if you rewrite the instance of Num
as follows:
instance (Num r, Unbox r) => Num (Qux r) where
(+) = quxAdd
quxAdd (Qux x) (Qux y) = Qux $ U.zipWith (+) x y
-
quxAdd
is not specifically imported byMain
.Main
imports the instance dictionary ofNum (Qux Int)
, and this dictionary containsquxAdd
in the record for(+)
. However, although the dictionary is imported, the contents used in the dictionary are not. -
plus
does not callquxAdd
, it uses the function stored for the(+)
record in the instance dictionary ofNum t
. This dictionary is set at the call site (inMain
) by the compiler.