Search code examples
haskelltypeclasstype-systems

Guessing the correct instance of Num from the context


Suppose:

import Data.Int (Int64)

data Unknown

class Meh a where
    meh :: a -> String
instance Meh Int64 where
    meh = show
instance Meh Unknown where
    meh _ = "Unknown"

It is obvious that meh can only take either an Int64 or an Unknown.

But we all know that

meh 1

will cause an error (No instance for (Num a0) arising from the literal '1') because 1 is Num a => a and compiler doesn't know which instance of Num it should be.

However, it should (logically) be possible to infer 1 to be an Int64 because it is passed to meh and meh can only take either an Int64 or an Unknown. In other words, it should be possible to calculate the "intersection" of Num and Meh and decide which instance 1 is.

So my question is: Do you know any way (compiler extension, code workaround, anything) that makes it possible to write meh 1 where 1 is correctly inferred to be an Int64 without adding type signatures or adding specialised functions?


Solution

  • When trying your example in GHC 7.8.3

    >>> meh 1
    

    I get:

    <interactive>:2:1:
        No instance for (Meh a0) arising from a use of ‘meh’
        The type variable ‘a0’ is ambiguous
        Note: there are several potential instances:
          instance Meh Unknown -- Defined at Meh.hs:9:10
          instance Meh Int64 -- Defined at Meh.hs:7:10
        In the expression: meh 1
        In an equation for ‘it’: it = meh 1
    
    <interactive>:2:5:
        No instance for (Num a0) arising from the literal ‘1’
        The type variable ‘a0’ is ambiguous
        Note: there are several potential instances:
        ...
    

    This is because compiler tries to typecheck:

    >>> meh (fromInteger (1 :: Integer))
    

    For which you get similar errors:

    <interactive>:4:1:
        No instance for (Meh a0) arising from a use of ‘meh’
        The type variable ‘a0’ is ambiguous
        Note: there are several potential instances:
          instance Meh Unknown -- Defined at Meh.hs:9:10
          instance Meh Int64 -- Defined at Meh.hs:7:10
        In the expression: meh (fromInteger (1 :: Integer))
        In an equation for ‘it’: it = meh (fromInteger (1 :: Integer))
    
    <interactive>:4:6:
        No instance for (Num a0) arising from a use of ‘fromInteger’
        The type variable ‘a0’ is ambiguous
        Note: there are several potential instances:
    

    Type-classes are open. No-one restricts someone else defining:

    newtype MyInt = MyInt Int deriving (Num)
    instance Meh MyInt where
       meh = ...
    

    Then even in your example meh 1 can be resolved to (meh :: Int64 -> String) 1, generally it can't. Arguments like "but based on the Meh instances visible in the scope" doesn't really work in Haskell, as instances are global.


    If you define data (which is closed), then using {-# LANGUAGE OverloadedStrings #-} you could do:

    data Meh = MehInt Int64 | MehString String
    
    instance IsString Meh where
       fromString = MehString
    

    Then

    "foo" :: Meh
    

    will compile, as GHC "will see"

    fromString "foo" :: Meh
    

    Similarly you can define incomplete Num instance, with only fromInteger defined. Yet I personally dislike incomplete instances, though this case is handy when defining eDSLs.