I am currently trying to wrap my head around typeclasses and instances and I don\'t quite understand the point of them yet. I have two questions on the matter so far:
<
In short: because that is how Haskell was designed.
Why put
(Eq a)
in the signature. If==
is not defined for a then why not just throw the error when encounteringa == b
?
Why do we put the types in the signature of a C++ program (and not just somewhere as an assertion in the body)? Because that is how C++ is designed. Typically a concept on what programming languages are built is "make explicit what needs to be explicit".
It is not said that a Haskell module is open-source. So that means we only have the signature available. It would thus mean that when we for instance write:
Prelude> foo A A
:4:1: error:
• No instance for (Eq A) arising from a use of ‘foo’
• In the expression: foo A A
In an equation for ‘it’: it = foo A A
We would frequently write foo
here with types that have no Eq
typeclass. As a result, we would get a lot of errors that are only discovered at compile time (or if Haskell was a dynamic language, at runtime). The idea of putting Eq a
in the type signature is that we can look up the signature of foo
in advance, and thus ensure that the types are instance of the typeclass.
Note that you do not have to write type signatures yourself: Haskell can typically derive the signature of a function, but a signature should include all the necessary information to call and use a function effectively. By adding type constraints, we speed up development.
What is up with that? Why can't I have two functions with the same name but operating on different types.
Again: that is how Haskell is designed. Functions in functional programming languages are "first class citizens". It means these usually have a name and we want to avoid name clashes as much as possible. Just like classes in C++ typically have a unique name (except for namespaces).
Say you would define two different functions:
incr :: Int -> Int
incr = (+1)
incr :: Bool -> Bool
incr _ = True
bar = incr
Then which incr
would bar
have to select? Of course we can make the types explicit (i.e. incr :: Bool -> Bool
), but usually we want to avoid that work, since it introduces a lot of noise.
Another good reason why we do not do that, is because typically a typeclass is not just a collection of functions: it adds contracts to these functions. For instance the Monad
typeclass has to satisfy certain relations between the functions. For example (>>= return)
should be equivalent with id
. In other words, the typeclass:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
Does not describes two independent functions (>>=)
and return
: this is a set of functions. You have them both (usually with some contracts between the specific >>=
and return
), or none of these at all.