Skip to content

Cross-module specialization fails due to irrelevant parametric polymorphism

Here are two modules:

module M1 where
import Data.Functor.Identity

class C f where
  c :: f a -> a

instance C Identity where
  c (Identity a) = a

newtype Tagged tag a = Tagged a

instance C (Tagged tag) where
  c (Tagged a) = a

f :: C f => f a -> a
f a = c a
{-# INLINABLE[0] f #-}
module M2 where
import Data.Functor.Identity
import M1

g :: Identity a -> a
g a = f a

h :: Tagged tag a -> a
h a = f a

Since f is marked INLINABLE, I would expect both g and h to trigger cross-module specialization of f. However, if I compile with -ddump-spec, I find that only f @Identity is specialized, not f @(Tagged tag):

==================== Specialise ====================
Result size of Specialise
  = {terms: 32, types: 45, coercions: 42, joins: 0/0}

Rec {
-- RHS size: {terms: 4, types: 5, coercions: 15, joins: 0/0}
$sf :: forall a. Identity a -> a
$sf
  = \ (@ a) (a1 :: Identity a) -> (M1.$fCIdentity1 `cast` <Co:15>) a1
end Rec }

-- RHS size: {terms: 5, types: 6, coercions: 12, joins: 0/0}
g :: forall a. Identity a -> a
g = \ (@ a) (a :: Identity a) ->
      f (M1.$fCIdentity1 `cast` <Co:12>) a

-- RHS size: {terms: 6, types: 11, coercions: 15, joins: 0/0}
h :: forall tag a. Tagged tag a -> a
h = \ (@ tag) (@ a) (a :: Tagged tag a) ->
      f (M1.$fCTagged1 `cast` <Co:15>) a

What gives? The tag type variable contributes nothing at all to the choice of overloading of f, so I would expect the specializer to produce a specialized version of f of type forall tag a. Tagged tag a -> a.

The difference in this contrived example is irrelevant, as f will get inlined into h, anyway. But in a real program of mine, I have a definition with a rather large unfolding that does not get inlined, and I’d like it to be specialized.

Environment

GHC versions tested with:

  • 8.6.5
  • 8.9.0.20191001
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information