4
votes

I would like to know what are the rules used by Haskell to always decide for the Integer instance instead of the others when evaluating in GHCi the expression 1 .+. 2:

import Debug.Trace

class MyFuns a where
    (.+.) :: a →  a →  a

instance MyFuns Double where
    x .+. y = trace "Double " $ x + y

instance MyFuns Integer where
    x .+. y = trace "Integer " $ x + y

instance MyFuns Int where 
    x .+. y = trace "Int " $ x + y

EDIT: if I add the following code at the end of the file

main = do
   let x = 1 .+. 2
   print x

why do I get this error?

No instance for (Num a0) arising from the literal ‘1’
The type variable ‘a0’ is ambiguous
Relevant bindings include x :: a0 (bound at fun2.hs:19:7)
Note: there are several potential instances:
  instance Integral a => Num (GHC.Real.Ratio a)
    -- Defined in ‘GHC.Real’
  instance Num Integer -- Defined in ‘GHC.Num’
  instance Num Double -- Defined in ‘GHC.Float’
  ...plus three others
In the first argument of ‘(.+.)’, namely ‘1’
In the expression: 1 .+. 2
In an equation for ‘x’: x = 1 .+. 2

Yet, if I load the file at GHCi prompt without the main = ..., and then type 1 .+. 2, GHCi prints 3 as expected. Why this behavior?

Thanks

1

1 Answers

5
votes

See section 4.3.4 in the Haskell report. When Haskell reads a literal that looks integral (no decimal component), the type is (unless more specifically inferred) Num a => a. The moment it needs to choose a type, it uses the defaulting rules, and normally the default is Integer. When a literal has a decimal component, it is Fractional a => a, which is normally defaulted to Double.

By using a default top-level declaration, you can change these settings, e.g.:

default (Int, Float)

would default Num to Int and Fractional to Float (because Int is not Fractional). Note that the effect of this statement is local to the module in which it is declared.

The default statement has the following effect (quoting the report):

Each defaultable variable is replaced by the first type in the default list that is an instance of all the ambiguous variable’s classes. It is a static error if no such type is found.

The -XExtendedDefaultRules GHC flag has additional effects, see here.


Edit

As for your error, the source is the following statement, which is in the GHC user guide and in different wording in section 4.3.4 of the report:

However, it is tiresome for the user to have to specify the type, so GHCi extends Haskell's type-defaulting rules (Section 4.3.4 of the Haskell 2010 Report) as follows. The standard rules take each group of constraints (C1 a, C2 a, ..., Cn a) for each type variable a, and defaults the type variable if

  1. The type variable a appears in no other constraints

  2. All the classes Ci are standard.

  3. At least one of the classes Ci is numeric.

Where I intentionally put focus on the second bullet. Because you're using .+., one of the classes of the numbers is MyFuns - which is not a class from the Prelude or the standard library, so it is not a "standard" class. Luckily, the text continues as follows:

At the GHCi prompt, or with GHC if the -XExtendedDefaultRules flag is given, the following additional differences apply:

  • Rule 2 above is relaxed thus: All of the classes Ci are single-parameter type classes.

  • Rule 3 above is relaxed this: At least one of the classes Ci is numeric, or is Show, Eq, or Ord.

  • The unit type () is added to the start of the standard list of types which are tried when doing type defaulting.

In conclusion, if you use the ExtendedDefaultRules flag (which as you can see is active in GHCi by default), your code will compile just fine also with your custom class:

{-# LANGUAGE ExtendedDefaultRules #-}

import Debug.Trace

default (Int, Float, Double)

class MyFuns a where
    (.+.) :: a -> a -> a

instance MyFuns Double where
    x .+. y = trace "Double " $ x + y

instance MyFuns Integer where
    x .+. y = trace "Integer " $ x + y

instance MyFuns Int where
    x .+. y = trace "Int " $ x + y

main = do
   print $ 1 .+. 2 -- Interpreted as Int

Note that in this example 1.0 .+. 2.0 is interpreted as double, while 1.0 + 2.0 is interpreted as float: this is because there is no MyFuns instance for Float, so its entry in the default list is skipped.