When I am in a REPL like GHCI with Prelude, and I write
*> compare 5 7
LT
Why can I call that function (compare
) like that directly in the REPL?
I know that compare
is defined in typeclass Ord
. The typeclass definition for Ord
of course shows that it is a subclass of Eq
.
Here is my line of reasoning:
5
has type Num a => a
, and Num
typeclass is not a subclass of Eq
.
Also,
Prelude> :t (compare 5)
(compare 5) :: (Num a, Ord a) => a -> Ordering
So, there is an additional constraint imposed here when I apply a numeric type argument. when I call compare 5 7
, the types of the arguments are narrowed to something that does have an instance of Ord
. I think the narrowing happens to the default concrete type associated with the typeclass: in the case of Num
, this is Integer
, which has an instance of Real
, which has an instance of Ord
.
However, coming from a non-functional programming background, I would have imagined that I would have to call compare
on one of the numbers (like calling it on an object in OOP). If 5
is Integer
, which does implement Ord
, then why do I call compare
in the REPL itself? This is obviously a question related to a paradigm shift for me and I still didn't get it. Hopefully someone can explain.
What's a typeclass in Haskell? A typeclass defines a set of methods that is shared across multiple types. For a type to belong to a typeclass, it needs to implement the methods of that typeclass. These implementations are ad-hoc: methods can have different implementations for different types.
(->) is often called the "function arrow" or "function type constructor", and while it does have some special syntax, there's not that much special about it. It's essentially an infix type operator. Give it two types, and it gives you the type of functions between those types.
The Eq typeclass provides an interface for testing for equality. Any type where it makes sense to test for equality between two values of that type should be a member of the Eq class. All standard Haskell types except for IO (the type for dealing with input and output) and functions are a part of the Eq typeclass.
Type classes define interfaces that in Haskell's terms are called dictionaries. For instance, from the Ord class definition the compiler will create a dictionary that stores all the class methods.
The type defaulting here comes into play. The interpreter can derive that 5
and 7
need to be of the same type, and members of the Ord
and Num
typeclass. The default for a Num
is Integer
, and since Integer
is an instance of Ord
as well, we can thus use Integer
.
The interpreter thus considers 5
and 7
to be Integer
s here in that case, and thus it can evaluate the function and obtain LT
.
GHCi has some additional defaulting rules, described in the GHCi documentation.
Methods like compare
are associated with types, not particular values. The compiler needs to be able to deduce the type in order to select the correct typeclass instance, but that doesn't require any special assistance.
The type of compare
is
compare :: (Ord a) => a -> a -> Ordering
Thus any of its arguments (of type a
) can be used to look up the Ord
instance.
As you correctly assumed, in the compare 5 7
example, the types of 5
and 7
default to Integer
. Thus a
in the compare
type is deduced to be Integer
and the Ord Integer
instance is selected.
This selection does not necessarily go through a function argument. Consider e.g.
read :: (Read a) => String -> a
Here it is the result type that drives instance selection, but the type checker is just fine with it:
> read "(2, 3)" :: (Int, Int)
(2,3)
(What would the OO equivalent be? "(2, 3)".read()
?)
In fact, methods don't even have to be functions:
maxBound :: (Bounded a) => a
This is a polymorphic value, not a function:
> maxBound :: Int
9223372036854775807
Class instances are uniquely connected to types, so as long as the type checker has enough information to figure out what that type variable represents, everything works out. That is, in
someMethod :: (SomeClass foo) => ...
foo
has to appear somewhere in the type signature ...
so the type checker can resolve SomeClass foo
from the way someMethod
is used at any given point (at least in the absence of certain language extensions).
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