Search code examples
haskelltypeclassambiguityambiguous

Resolving type ambiguities using available class instances


Given the following code:

import Data.Word

data T = T deriving (Eq, Show)

class    C a      where f :: a -> ()
instance C T      where f _ = ()
instance C Word16 where f _ = ()

main = return $ f 0x16

GHC complains that it can't infer what the type for the literal 0x16 should be with the error:

No instance for (Num a0) arising from the literal ‘22’
The type variable ‘a0’ is ambiguous

It is easy to see why this would be -- Haskell allows numeric literals to be of any type which has an instance of Num, and here we can't disambiguate what the type for the literal 0x16 (or 22) should be.

It's also clear as a human reading this what I intended to do -- there is only one available instance of the class C which satisfies the Num constraint, so obviously I intended to use that one so 0x16 should be treated as a Word16.

There are two ways that I know to fix it: Either annotate the literal with its type:

main = return $ f (0x16 :: Word16)

or define a function which essentially does that annotation for you:

w16 x = x :: Word16
main = return $ f (w16 0x16)

I have tried a third way, sticking default (Word16) at the top of the file in the hope that Haskell would pick that as the default type for numeric literals, but I guess I'm misunderstanding what the default keyword is supposed to do because that didn't work.

I understand that typeclasses are open, so just because you can make the assumption in the context quoted above that Word16 is the only numeric instance of C that may not hold in some other module. But my question is: is there some mechanism by which I can assume/enforce that property, so that it is possible to use f and have Haskell resolve the type of its numeric argument to Word16 without explicit annotations at the call site?

The context is that I am implementing an EDSL, and I would rather not have to include manual type hints when I know that my parameters will either be Word16 or some other non-numeric type. I am open to a bit of dirty types/extensions abuse if it makes the EDSL feel more natural! Although if solutions do involve the naughty pragmas I'd definitely appreciate hints on what I should be wary about when using them.


Solution

  • Quick solution with "naughty pragmas" with GHC 7.10:

    {-# LANGUAGE TypeFamilies, FlexibleInstances #-}
    
    class    C a where f :: a -> ()
    instance C T where f _ = ()
    instance {-# INCOHERENT #-} (w ~ Word16) => C w where f _ = ()
    

    And with GHC 7.8:

    {-# LANGUAGE TypeFamilies, FlexibleInstances, IncoherentInstances #-}
    
    class    C a where f :: a -> ()
    instance C T where f _ = ()
    instance (w ~ Word16) => C w where f _ = ()
    

    Here, GHC essentially picks an arbitrary most specific instance that remains after trying to unify the instances heads and constraints.

    You should only use this if

    • You have a fixed set of instances and don't export the class.
    • For all use cases of the class method, there is a single possible most specific instance (given the constraints).

    Many people advise against ever using IncoherentInstances, but I think it can be quite fun for DSL-s, if we observe the above considerations.