A value is polymorphic if, depending on its context, it can assume more than one type. If the value is specified separately for each type it can have, this is called ad-hoc polymorphism, also known as overloading. Ad-hoc polymorphic values may be used at a limited number of pre-specified types.
For instance, in C++, the addition operator may be used on any numeric type, like int or float, and may be applied to new types provided you write a definition for it using those types as arguments. The range of possible types of + in C++ is limited to those built-in and those for which the programmer has provided a definition. Similarly, in C, the operator + may operate on int or float or pointers. However, the programmer cannot supply their own definitions, and only the built-in types can be used.
In Haskell, type classes are used as a mechanism for "principled overloading", i.e. ad-hoc polymorphism. For instance, the following function:
show :: (Show a) => a -> String
may adopt any of the following types:
Int -> String Float -> String (Maybe Int, String, Either Char Float) -> String
The types which
show can adopt are precisely those which have an instance of the standard
Show class defined. Each instance gives a definition of the
show function to be used when the types match. Unlike in C++ function overloading, Haskell also allows overloaded values, like
maxBound, that adopt a different value based on their type (e.g.
maxBound :: Bool is equal to
maxBound :: Int may be
Contrast ad-hoc polymorphism with parametric polymorphism: e.g. the function
length :: [a] -> Int may be applied to lists of any type, but has only one definition which it uses regardless of which type it adopts.