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.