3
votes

For the following code:

module Main where

data EitherOr a b = Hello a | Goodbye b deriving Show

instance (Eq a, Eq b) => Eq (EitherOr a b) where
  (==) (Hello x) (Hello x') = x == x'
  (==) (Goodbye x) (Goodbye x') = x == x'
  (==) _ _ = False

main :: IO ()
main = do
  print (Hello 2 == Hello 2)
  -- print (Hello 3 == Hello 2)
  -- print (Goodbye 3 == Goodbye 3)
  -- print (Goodbye 4 == Goodbye 3)
  -- print (Hello 3 == Goodbye 3)

executed under runhaskell, i.e., under ghc, I get the following error:

    • Ambiguous type variable ‘b0’ arising from a use of ‘==’
      prevents the constraint ‘(Eq b0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘b0’ should be.
      These potential instances exist:
        instance Eq Ordering -- Defined in ‘GHC.Classes’
        instance Eq Integer
          -- Defined in ‘integer-gmp-1.0.2.0:GHC.Integer.Type’
        instance (Eq a, Eq b) => Eq (EitherOr a b)
          -- Defined at /tmp/runghcXXXX61964-0.hs:5:10. <-- This is because I am using org-mode source blocks
        ...plus 23 others
        ...plus 11 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the first argument of ‘print’, namely ‘(Hello 2 == Hello 2)’
      In a stmt of a 'do' block: print (Hello 2 == Hello 2)
      In the expression: do print (Hello 2 == Hello 2)
   |
12 |   print (Hello 2 == Hello 2)
   |          ^^^^^^^^^^^^^^^^^^

I thought I could give the compiler a type hint by doing

print ((Hello (2 :: Int)) == (Hello (2 :: Int)))

or something similar, but that doesn't seem to be enough. I see that a and b are polymorphic, but I thought the use of == in main might be enough to help the compiler infer types.

Next I loaded the data type and typeclass instance in ghci and explored the types a bit and found that, for instance

λ> :t Hello (2 :: Int)
Hello (2 :: Int) :: EitherOr Int b

As expected. Again in ghci, I do more exploration and see that default types are being used

λ> :t (Hello 2 == Hello 2)

<interactive>:1:2: warning: [-Wtype-defaults]
    • Defaulting the following constraints to type ‘Integer’
        (Eq a0) arising from a use of ‘==’ at <interactive>:1:2-19
        (Num a0) arising from the literal ‘2’ at <interactive>:1:8
    • In the expression: (Hello 2 == Hello 2)

<interactive>:1:2: warning: [-Wtype-defaults]
    • Defaulting the following constraint to type ‘()’
        Eq b0 arising from a use of ‘==’
    • In the expression: (Hello 2 == Hello 2)
(Hello 2 == Hello 2) :: Bool

which is what I want, of course.

Then I actually execute the code in ghci and get the right answer with some defaulting going on

λ> Hello 2 == Hello 2

<interactive>:27:1: warning: [-Wtype-defaults]
    • Defaulting the following constraints to type ‘Integer’
        (Eq a0) arising from a use of ‘==’ at <interactive>:27:1-18
        (Num a0) arising from the literal ‘2’ at <interactive>:27:7
    • In the expression: Hello 2 == Hello 2
      In an equation for ‘it’: it = Hello 2 == Hello 2

<interactive>:27:1: warning: [-Wtype-defaults]
    • Defaulting the following constraint to type ‘()’
        Eq b0 arising from a use of ‘==’
    • In the expression: Hello 2 == Hello 2
      In an equation for ‘it’: it = Hello 2 == Hello 2

<interactive>:27:1: warning: [-Wtype-defaults]
    • Defaulting the following constraints to type ‘Integer’
        (Eq a0) arising from a use of ‘==’ at <interactive>:27:1-18
        (Num a0) arising from the literal ‘2’ at <interactive>:27:7
    • In the expression: Hello 2 == Hello 2
      In an equation for ‘it’: it = Hello 2 == Hello 2

<interactive>:27:1: warning: [-Wtype-defaults]
    • Defaulting the following constraint to type ‘()’
        Eq b0 arising from a use of ‘==’
    • In the expression: Hello 2 == Hello 2
      In an equation for ‘it’: it = Hello 2 == Hello 2
True

But the same code executed under runhaskell, i.e., under ghc compilation, fails with the error I first gave. What do I need to learn here?

1
The issue here is that Hello (2 :: Int) chooses a (good!) but does not allow GHC to infer b (bad). GHCi uses more aggressive defaulting rules and chooses b = () (I think), but GHC is less eager to do that, since it might lead to unwanted surprising results, in the general case. Hence, we need an annotation for the whole expression Hello 2 :: EitherOr MyTypeA MyTypeB (as done below by Thomas).chi
@chi Maybe you can add another answer or merge it with Thomas M. DuBuissons answer (with his permission of course). Since you are essentially answering the question. (And merging would provide the best answer for future readers imho.)Micha Wiedenmann

1 Answers

5
votes

The type defaulting rules in GHCi are different than those used when compiling a program such as with GHC. When a type is ambiguous you should give an explicit signature such as:

print (Hello 2 == (Hello 2 :: EitherOr Integer ())

In practice this isn't needed too often because the types are implied by other parts of the program. Toy and educational snippets like the above do not have much in the way of context which could add information for the type checker.