Search code examples
haskellpolymorphismletmonomorphism

Avoiding monomorphism in let bindings without type annotation


I've got some code using types to disambiguate instances (the real code is using GHC.TypeLits singletons for type tags, but I don't think that's germane) and I'd like to use a let binding to avoid text-level duplication; unfortunately, this monomorphizes the result.

What follows is an example of the problem:

class Foo a where
  foo :: a

instance Foo Int where
  foo = 0

instance Foo Char where
  foo = 'a'

data Bar a = Bar String
  deriving (Show)

bar :: forall a. (Show a, Foo a) => a -> Bar a
bar _ = Bar $ show (foo :: a)

idInt :: Bar Int -> Bar Int
idInt = id

idChar :: Bar Char -> Bar Char
idChar = id

main = let quux = bar undefined in
  print (idInt quux) >> print (idChar quux)

The above code doesn't compile (but, of course, if I type annotate quux to be polymorphic, everything works fine), rightly complaining that it couldn't match Int with Char. Is there any way I could get compilation to succeed without type-annotating and without repeating bar undefined at each use site?


Solution

  • {-# LANGUAGE NoMonomorphismRestriction #-}
    

    Or if you want something less global

    let quux () = bar undefined in 
        print (idInt (quux ()) >> print (idChar (quux ()))
    

    The reason the latter works is that bindings are only monomorphised when they have no arguments to the left of the equals sign.

    let foo = \x y -> x + y   -- :: Integer -> Integer -> Integer
    let bar x y = x + y       -- :: (Num a) => a -> a -> a
    

    So to get quux to not monomorphize, you have to give it an argument to the left of the equals sign. If quux is not a value but a function, you can simply eta expand to get the same effect:

    let quux x = bar undefined x in ...
    

    For the former, don't worry about performance -- if you always call it as quux (), then it will be inlined and generate the same code as the version with an explicit type signature.