Polymorphism in Haskell
In a dynamic language like Python, there is no strict concept of
function overloading. Though, one can overload operators, function
overloading(other than operators) just means that you shadow the
newest instance of a function (that is functions sharing the same
name) over the old instance.
Function overloading is also referred to as adhoc polymorphism.
Haskell uses type classes
to provide adhoc polymorphism.
Adhoc polymorphism by way of type classes
is much powerful when
compared to function overloading features you get in other static
languages like Java. For a language to support function overloading,
it is required that the runtime know the type of arguments and return
types that is inferred at the call site. This knowledge of types is
used in dispatching the call to the appropriate overloaded function.
Dispatching to methods can be done based on

arguments

return type

a combination of both.
Not all languages let you do dispatching based on all the above(for example, in Java, dispatching to overloaded functions is only based on argument types and not return type. Haskell can do a dispatch to an function based on the return type as well.
What follows are some example of adhoc polymorphism in use. We will assume we are working with following data types:
data ClassRoom = ClassRoom {className::String, studentCount::Int}
data SportsTeam = SportsTeam {teamName::String, memberCount::Int}
Overloading on one argument of a function.
Say we have the two record types shown above, and we want an overloadeded function
called checkValid
to work with both types.
We can use type classes
to implement the overloaded functions
 Wrapped the checkValud function in a type class
class OnOneParameter a where
 One one parameter
checkValid :: a > Bool
instance OnOneParameter ClassRoom where
checkValid v = studentCount v > 0
instance OnOneParameter SportsTeam where
checkValid v = memberCount v > 0 && memberCount v < 10
This can be easily tested using the below snippet
mainp :: IO ()
main = do
let class_room = ClassRoom {className="Math", studentCount=10}
sports_team = SportsTeam {teamName="L", memberCount=6}
putStrLn $ show $ checkValid class_room
putStrLn $ show $ checkValid sports_team
Overloading on more than one argument.
If we want to overload the function with more than one argument, then
we will also have to use MultiParamTypeClasses
extension. Using this
extensions, we get the power to dispatch on multiple arguments.
We see that the class definition has two parameters and we can define
three different instances of the checkValidTogether
method. Each of
these instances will be called based on the types of both the
arguments at the call site.
 On 2 parameters
class OnTwoParameters a b where
checkValidTogether :: a > b > Bool
instance OnTwoParameters ClassRoom SportsTeam where
checkValidTogether a b = checkValid a && checkValid b
instance (OnOneParameter b) => OnTwoParameters SportsTeam b where
checkValidTogether a b = checkValid a && checkValid b
instance (OnOneParameter b) => OnTwoParameters ClassRoom b where
checkValidTogether a b = checkValid a && checkValid b
checkValid2 :: SportsTeam > SportsTeam > Bool
checkValid2 a b = checkValidTogether a b
One example which is ambiguous is:
testFn :: (OnOneParameter b) => ClassRoom > b > Bool
testFn a b = checkValidTogether a b
Here the compiler, does not know which version of the overloaded function should the call be dispatched to. Compiling, the above snippet will cause the compile to fail with this error:
Overlapping instance error
Overlapping instances for OnTwoParameters ClassRoom b
arising from a use of ‘checkValidTogether’
Matching instances:
instance OnOneParameter b => OnTwoParameters ClassRoom b
Defined at /tmp/flycheck10671M3X/Polymorphism.hs:40:10
instance OnTwoParameters ClassRoom SportsTeam
Defined at /tmp/flycheck10671M3X/Polymorphism.hs:34:10
Overloading on return type
Here, the function arguments could be the same, but notice that the dispatch is happening on the return type of the function
class OnReturnType a where
iAmCalledBasedOnReturnType :: Int > a
instance OnReturnType Bool where
iAmCalledBasedOnReturnType _ = True
instance OnReturnType Int where
iAmCalledBasedOnReturnType _ = 10
Here is the output, from calling the above functions.
> iAmCalledBasedOnReturnType 10::Int
10
> iAmCalledBasedOnReturnType 10::Bool
True
>
Overloading functions on both argument types and return type
This example just builds on the examples we have seen above.
class OnReturnTypeAndParams a b where
iAmCalledBasedOnReturnTypeAndParams :: a > b
instance OnReturnTypeAndParams Int Bool where
iAmCalledBasedOnReturnTypeAndParams a = if a == 1 then True else
False
instance OnReturnTypeAndParams Bool Int where
iAmCalledBasedOnReturnTypeAndParams a = if a then 1 else 0
Here is the output of using the above code
>
> let x_int = 10::Int
> iAmCalledBasedOnReturnTypeAndParams x_int::Bool
False
> let y_bool = True
> iAmCalledBasedOnReturnTypeAndParams y_bool::Int
1
>
By now, we see that Haskell provides constructs that lets us build a powerful set of abstractions using adhoc polmorphism.
As a bonus, here is an example of adhoc polymorphism for nested types:
class OnNestedType a where
nestedType :: a > String
 Examples of nested types
instance (Show a) => OnNestedType [a] where
nestedType c = show c