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?
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.
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.
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.
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.
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.
Haskell's thought process is something like this:
MyShow
for (5 :: Int, 5 :: Int)
or (Int, Int)
MyShow a => MyShow (a, b)
a
=> a
and b
=> a
) it selects this instance.a
, in this case Int
, is also an instance of MyShow
, which it is. Notice: This check happens after the instance is selected.myShow (5 :: Int, 5 :: Int)
call's the tuple's myShow
and becomes myShowTuple (5 :: Int, 5 :: Int)
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.MyShow a => (a, b) -> String)
and since a
in this case has the type Int
haskell resolves this to the Int
instance of MyShow
myShowTuple
"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
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With