Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Haskell choose methods for instances of type classes?

I was trying to understand why Haskell's show treats a list of chars different from a list of e.g. integers even without the FlexibleInstances Pragma.

Having read through the documentation of Show, I realized that I don't really understand how Haskell chooses methods for instances of type classes.

Consider the following code:

class MyShow a where
    myShow :: a -> String
    myShowList :: [a] -> String
    myShowTuple :: (a, b) -> String

    myShowList xs = "Default List Implementation"
    myShowTuple t = "Default Tuple Implementation"

instance MyShow Char where
    myShow c = "One Char"
    myShowList xs = "List of Chars"
    myShowTuple t = "Char Tuple"

instance MyShow Int where
    myShow n = "One Int"
    myShowList xs = "List of Integers"
    myShowTuple t = "Int Tuple"

instance MyShow Float where
    myShow n = show n

instance (MyShow a) => MyShow [a] where
    myShow = myShowList

instance (MyShow a) => MyShow (a, b) where
    myShowTuple t = "foo"
    myShow = myShowTuple

Now if I call e.g.

myShow (5::Int,5::Int)

I would expect that Haskell thinks 'Oh, myShow got a tuple as an argument. Let's see which implementation I have to call.' and chooses the last one which in return would result in "foo". Obviously, this is not the case. Haskell seems to look at the content of the tuple (namely the type of a) and decides to call the corresponding method, resulting in "Int Tuple".

Why is this?

like image 894
Aton Avatar asked Jun 02 '13 17:06

Aton


People also ask

What is an instance of a Haskell type class?

An instance of a class is an individual object which belongs to that class. In Haskell, the class system is (roughly speaking) a way to group similar types. (This is the reason we call them "type classes"). An instance of a class is an individual type which belongs to that class.

How do types work in Haskell?

In Haskell, every statement is considered as a mathematical expression and the category of this expression is called as a Type. You can say that "Type" is the data type of the expression used at compile time.

How does deriving work in Haskell?

The second line, deriving (Eq, Show) , is called the deriving clause; it specifies that we want the compiler to automatically generate instances of the Eq and Show classes for our Pair type. The Haskell Report defines a handful of classes for which instances can be automatically generated.

What is the difference between type and data in Haskell?

Type and data type refer to exactly the same concept. The Haskell keywords type and data are different, though: data allows you to introduce a new algebraic data type, while type just makes a type synonym. See the Haskell wiki for details.


2 Answers

When you write myShow (5::Int, 5::Int), Haskell does say "Oh, myShow got a tuple as an argument. Let's see which implementation I have to call." and it does choose the last one, i.e. myShow = myShowTuple. But that doesn't mean the result will be "foo". It means that the result of calling myShow (5::Int, 5::Int) will be the same as the result of calling myShowTuple (5 :: Int, 5 :: Int).

So now Haskell has to decide which version of myShowTuple it has to call. Since myShowTuple has type MyShow a => (a, b) -> String, the version of myShowTuple that's defined on the second-to-last line has type MyShow a => ((a, c), b) -> String, so that one doesn't fit. The one defined on line 17 has type (Int, b) -> String, so that one does fit. So that's the one that is picked.

like image 159
sepp2k Avatar answered Sep 19 '22 23:09

sepp2k


Haskell's thought process is something like this:

  1. It tries to figure out the instance of MyShow for (5 :: Int, 5 :: Int) or (Int, Int)
  2. It then finds the instance MyShow a => MyShow (a, b)
  3. Since this unifies, (a => a and b => a) it selects this instance.
  4. It checks to make sure a, in this case Int, is also an instance of MyShow, which it is. Notice: This check happens after the instance is selected.
  5. myShow (5 :: Int, 5 :: Int) call's the tuple's myShow and becomes myShowTuple (5 :: Int, 5 :: Int)
    • It doesn't call myShowTuple since this has the type (a, b) and in the tuple's case, a is (a, b) so myShowTuple has the type ((a, b) ,c) which is clearly not a match.
  6. This has the type MyShow a => (a, b) -> String) and since a in this case has the type Int haskell resolves this to the Int instance of MyShow
  7. It runs the appropriate myShowTuple
  8. You get "Int Tuple"

Just a sidenote, if this was an actual implementation of Show you'd want something like this for myShowTuple

myShowTuple :: MyShow b => (a, b) -> String

Otherwise you have no way to actual format that b which is after all, any type.

This would make

instance (MyShow a) => MyShow (a, b) where
  ...

into

instance (MyShow a, MyShow b) => MyShow (a, b) where
  ... 
like image 32
Daniel Gratzer Avatar answered Sep 18 '22 23:09

Daniel Gratzer