Skip to content

Latest commit

 

History

History
111 lines (87 loc) · 3.59 KB

11.md

File metadata and controls

111 lines (87 loc) · 3.59 KB

Ad-hoc polymorphism

Let's implement a function that concatenates the String representation of two values of any type. We will use the show function to convert a value to String.

concatString :: a -> b -> String
concatString x y = show x ++ show y

This version of concatString does not compile. The problem is that a and b can be any type, so we do not know if it is possible to convert them to a String. We need to constrain the type variables to tell ghc that a and b can be converted to a String.

concatString :: (Show a, Show b) => a -> b -> String
concatString x y = show x ++ show y

In this version we tell ghc that it should substitute only those types in a and b that implement the Show typeclass. Typeclasses can be thought of as interfaces for now.

class Show a where
  show :: a -> String

The Show typeclass declares the show function. Every type that wants to be an instance of Show needs to implement a show function with the given type signature. When ghc compiles the concatString function, it sees that both arguments should implement the Show typeclass, therefore they need to have an implementation of the show function.

Implementing a typeclass instance

data Propulsion = Wind | Diesel

concatString Wind 10

This snippet can not be compiled since the Propulsion type does not implement the Show interface.

instance Show Propulsion where
  show Wind = "Wind"
  show Diesel = "Diesel"

Now that we have an implementation of Show and therefore the show function, everything should be fine. However the implementation looks really straightforward. For some typeclasses ghc can derive default implementations for us.

data Propulsion = Wind | Diesel deriving (Show)

With this declaration ghc generates an implementation of the Show interface.

Default function implementations

Functions in a typeclass might have a default implementation. An example is the Eq typeclass where both == and /= is defined as the negation of the other. When you define an instance for Eq it is perfectly fine to implement only one of these functions since the default implementation of the other will work automatically.

Constrains

The more constrains we have, the fewer the types that can be substituted. The more constrains we have, the more we know about the types that can be substituted so the more things we can do with them in our functions. However there is no point in constraining a type if we do not use the extra interfaces in the function body. The rule of thumb is that your functions should be as generic as they can be.

Let's see a silly example of too many constrains.

identity :: (Show a, Eq a) => a -> a
identity x = x

Another thing to consider is that the less we know about the function types the more we know about what a function might do by looking at the type declaration only. The more we know about the parameter types the less we know about the functions behavior just by looking at the type signature.

What might these functions do?

f :: a -> a
g :: a -> b -> a
h :: Int -> Int -> Int

Exercises

  • Correct the following function by constraining type variable a. The function should accept Double and Int parameters as well.
plusTwo :: a -> a
plusTwo x = x + 2
  • Implement the Eq typeclass instance of Propulsion and try to compare two values with the == operator.
  • Modify the Propulsion type to derive the Ord typeclass automatically and try to compare two values with the < operator.