Skip to content

Type-class specialiser fails to specialise a pretty simple program

Consider this code:

  wombat :: Show b => Int -> b -> String
  wombat a b | a>0       = wombat (a-1) b
             | otherwise = show a ++ wombat a b

  class C a where
    meth :: Show b => a -> b -> String
    dummy :: a -> () -- Force a datatype dictionary representation

  instance C Int where
    meth = wombat
    dummy _ = ()

  f :: (C a, Show b) => a -> b -> String
  {-# INLINABLE[0] f #-}
  f a b = meth a b ++ "!"

  main = putStrLn (f (42::Int) (True::Bool))

The call in main generates a specialised version of f; the specialised code contains a call (meth $dCInt). We must do the method selection so that we can in turn specialise meth, and thence wombat. This commit did the job:

    commit 4d2ee313f23a4454d12c9f94ff132f078dd64d31
    Author: Sebastian Graf <sebastian.graf@kit.edu>
    Date:   Thu Apr 7 17:21:08 2022 +0200

    Specialising through specialised method calls (#19644)

by adding rewriteClassOps, which rewrites (meth $dCInt) to wombat.

But ALAS it all falls apart if we have (a) two calls to meth and (b) the dictionary is let-bound, not an argument. Here:

  class C a => D a where
    op :: a -> a
  instance D Int where
    op x = x

  f :: (D a, Show b) => a -> b -> String
  {-# INLINABLE[0] f #-}
  f a b = meth a b ++ "!" ++ meth a b

Now f turns into:

  f @a @b (dd :: D a) (ds :: Show b) a b
     = let dc :: D a = %p1 dd  -- Superclass selection
       in meth @a dc ....
          meth @a dc ....

Now, for rewriteClassOpts to fire on (meth dc), when dd is instantiated to $dDInt :: D Int we must:

  • Fire the superclass selector %p1 dd (this is just another use of rewriteClassOps) to simplify it to $fCInt.

  • Make dc's new binding to $fCInt visible to (meth dc).

Currently we don't succeed in specialising meth or wombat. Boo!

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