In contrast, overloaded functions don't have the same behaviour. This makes type inference problematic. A hoary example is arithmetical multiplication. The values and algorithms involved in integer and floating-point multiplication are different. We can't write a single function mult to do the job. We must write different overloaded versions, e.g.:
mult :: Int → Int → Int mult x y = ... -- integer multiplication mult :: Float → Float → Float mult x y = ... -- floating-point multiplication
In this setting, overloading is resolved in terms of scope and type signatures of arguments, which means type signatures must be written explicitly. For example, without its type signature it is impossible to tell whether double below is a function on integers or floats:
double x = mult x x
However, providing type signatures leads to code bloating:
double :: Int → Int double x = mult x x double :: Float → Float double x = mult x x
This is unsatisfactory. Despite the different behaviour, all the versions of mult and double are about the same idea, namely, about "multiplication" and "doubling" of numbers.
class Mult a where mult :: a → a → a instance Mult Int where mult = ... -- integer multiplication instance Mult Float where mult = ... -- floating-point multiplication
We can now define double x = mult x x. The type checker infers it has type ∀a. Mult a ⇒ a → a, meaning ∀a∈Mult. a → a. The idea is simple and clever: the overloaded function is now a parameter. Internally, double has type ∀a. MultD a → a → a where MultD is a dictionary type (record) with a mult member funcion. Overloading resolution for applications of double to actual values is based on the types of those values, which determine the particular instance of Mult and, consequently, the particular dictionary value of type MultD to be passed at run time.
push :: a → Stack a → Stack a push :: Door → IO ()
The first function is a well-known function on stacks. The second displays on the screen the pushing of an emergency door in a fire simulation program. Both functions are meant to be used in the simulation. But stacks and doors don't belong in the same class. We must resort to resolving overloading manually in terms of scope, e.g., Stack.push, Door.push. In short, we must give up overloading and its automatic resolution.
We can't use within the same scope different abstractions sharing operator names. For instance, constructor type classes are employed in the definition of abstract data type interfaces:
class Queue q where empty :: q a isEmpty :: q a → Bool enqueue :: a → q a → q a head :: q a → a tail :: q a → q a length :: Queue q ⇒ q a → Int length q | isEmpty q = 0 | otherwise = 1 + length (tail q)
We can't use head, tail, and length operators together with the list counterparts within the same scope unless we qualify their names by class/module. The conflict is between operators in different classes.
We can't define overloaded functions within the same type class:
class Vector m where ... mult :: m a → m a → m a mult :: a → m a → m a
We can't overload (and override) functions in subclasses. This is why multiple subclassing is not a problem, in contrast with classes in object-oriented languages. For example:
class A a where .. class A a ⇒ B a where .. class A a ⇒ C a where .. class (B a, C a) ⇒ D a where .. A / \ B C \ / DThere's no ‘diamon of death‘ conflict between B's, C's and D's function names cause they are all required to differ.
Related stuff: can't overload value constructors nor record members.
Value constructors play the role of type tags that help the type checker figure out the type of the value. For example, below is the type of disjoint sums:
data Sum a b = Inl a | Inr b
We cannot define overloaded value constructors:
MkSum :: a → Sum a b MkSum :: b → Sum a b
What would the type of MkSum 3 be ?
We may provide type annotations to disambiguate:
(MkSum 3) :: Sum Int Bool
but this fails when both components have the same type (is 3 an Inl or an Inr?)
(MkSum 3) :: Sum Int Int
A way to disambiguate is to provide a type-tag proxy:
data INL = INL data INR = INR MkSum :: INL → a → Sum a b MkSum :: INR → b → Sum a b (MkSum INL 3) :: Sum Int Int
There is no confusion about which value is constructed. But this is resolving overloading manually, as with the original value constructors:
Inl = MkSum INL Inr = MkSum INRSo better to have different value constructor names.