Commit 8074894b authored by Sandra Dylus's avatar Sandra Dylus

Revise polymorphism notes; add paragraph about type synonyms

parent 0bd01a58
......@@ -12,14 +12,14 @@ In the last lecture we discussed how to abstract repetitive schemes that occur a
Let us define such polymorphic list data type as follows.
> data List a where
> Nil :: List a
> Cons :: a -> List a -> List a
> data List elemType where
> Nil :: List elemType
> Cons :: elemType -> List elemType -> List elemType
> deriving Show
As before, we have one nullary constructor `Nil` that marks the end of the list, and a constructor `Cons` that takes the element of the list and the remaining list as argument.
The new part is the usage of the small letter `a` all over the type definition.
A letter like `a` that is introduced in the name of a data type is called _type variable_ or _type parameter_.
The new part is the usage of the variable `elemType` all over the type definition.
A variable like `elemType` that is introduced in the name of a data type is called _type variable_ or _type parameter_.
We are already used to functions like `(+) :: Integer -> Integer -> Integer` or `eqDirection :: Direction -> Direction -> Bool` to take arguments, now we have seen the first type that takes an argument as well.
A type like `List` is, thus, a _type function_ (in Haskell usually called a _type constructor_), because in order to have type that we use in a type signature, we need to apply `List` to a type.
Similar to all our functions (and constructors, which are special kind of functions), we can also give a signature for types; in Haskell such signature are called _kind signatures_ (that is, functions have type signature and types have kind signatures).
......@@ -41,8 +41,8 @@ In order to use `List` in type signature, we need to apply it to a type.
$> :k List Bool
List Bool :: *
What happens to the parameter `a` we used in the type signature?
Similar to how we replace parameters introduced on the left-hand side of function definition on their corresponding right-hand side, we replace all occurrences of the type parameter `a` with the more concrete type.
What happens to the parameter `elemType` we used in the type signature?
Similar to how we replace parameters introduced on the left-hand side of function definition on their corresponding right-hand side, we replace all occurrences of the type parameter `elemType` with the more concrete type.
> intList1 :: List Integer
> intList1 = Cons 43 Nil
......@@ -53,29 +53,29 @@ What happens to the parameter `a` we used in the type signature?
When specifying that `intList1` should be of type `List Integer`, as given in its type signature, Haskell checks if `Cons` and `Nil` are used in the correct way.
That is, given the type signature of `Cons`
Cons :: a -> List a -> List a
Cons :: elemType -> List elemType -> List elemType
we know that we need instantiate the type parameter `a` in the resulting type with `Integer` in order to yield a value of type `List Integer` as the type signature of `intList1` demands.
we know that we need instantiate the type parameter `elemType` in the resulting type with `Integer` in order to yield a value of type `List Integer` as the type signature of `intList1` demands.
Cons :: a -> List a -> List a { a -> Integer } as demanded by the resulting type of `List Integer`
Cons :: elemType -> List elemType -> List elemType { elemType -> Integer } as demanded by the resulting type of `List Integer`
~>
Cons :: Integer -> List Integer -> List Integer
That is, we need to use `Cons` with a value of type `Integer` as first argument and a value of type `List Integer` as second argument.
In the example expression above, we then know that `Nil`'s type needs to be instantiate as `List Integer` in order to fit the overall usage.
Nil :: List a { a -> Integer } as demanded by the usage of `Cons 42`
Nil :: List elemType { elemType -> Integer } as demanded by the usage of `Cons 42`
~>
Nil :: List Integer
Intuitively, we can instantiate the type variables for each constructor with the concrete type we are using.
data List a where List Bool List Integer
Nil :: List a :: List Bool :: List Integer
Cons :: a -> List a -> List a :: Bool -> List Bool -> List Bool :: Integer -> List Integer -> List Integer
data List elemType where List Bool List Integer
Nil :: List elemType :: List Bool :: List Integer
Cons :: elemType -> List elemType -> List elemType :: Bool -> List Bool -> List Bool :: Integer -> List Integer -> List Integer
Before introducing polymorphic data types we needed to define every time we needed a different element type for our list-like structure.
Now we can reuse `List a` by instantiating the type parameter with a concrete type.
Now we can reuse `List elemType` by instantiating the type parameter with a concrete type.
> intList2 :: List Integer
> intList2 = Cons 1 (Cons 2 (Cons 3 Nil))
......@@ -96,7 +96,7 @@ We can now also define _polymorphic functions_.
We define `lengthList` inductively over the structure of the list argument.
> lengthList :: List a -> Integer
> lengthList :: List elemType -> Integer
> lengthList Nil = 0
> lengthList (Cons elem list) = 1 + lengthList list
......@@ -114,17 +114,17 @@ In case of an empty list, the lengt is `0`, otherwise we add `1` to the resultin
When using the function `lengthList` with a specific list, Haskell checks if we're allowed to make this function call at compile-time again.
lengthList :: List a -> Integer { a -> Integer } (in case of using the function with `intList1` or `intList2`)
lengthList :: List elemType -> Integer { elemType -> Integer } (in case of using the function with `intList1` or `intList2`)
~>
lengthList :: List Integer -> Integer
lengthList :: List a -> Integer { a -> Bool } (in case of using the function with `boolList1`)
lengthList :: List elemType -> Integer { elemType -> Bool } (in case of using the function with `boolList1`)
~>
lengthList :: List Bool -> Integer
lengthList :: List a -> Integer { a -> List Integer } (in case of using the function with `intListList`)
lengthList :: List elemType -> Integer { elemType -> List Integer } (in case of using the function with `intListList`)
~>
lengthList :: List (List Integer) -> Integer
......@@ -136,7 +136,7 @@ Recall the higher-order example we implemented last week: instead of defining a
> mapCoordMap fCoord EmptyC = EmptyC
> mapCoordMap fCoord (ACoord coord cm) = ACoord (fCoord coord) (mapCoordMap fCoord cm)
We define a similar function for our polymorphic data type `List a` as well.
We define a similar function for our polymorphic data type `List` as well; for brevity we use the type variable `a` instead of `elemType`.
Instead of using a functional argument of type `Coordinate -> Coordinate`, we need a function that maps the element type `a` to a new value.
> mapListA :: (a -> a) -> List a -> List a
......@@ -195,9 +195,9 @@ Thus, instead of transforming a `List a` into a `List a`, we yield a final struc
Cons 1 (Cons 3 Nil)
The other higher-order function that we defined for `CoordMap` as well as for `IntList` (see Exercise 3) is a filtering function that takes a predicate as first argument and decides based on this predicate if an argument should be kept in the final list or discarded.
We can define this function polymorphic in the element type `a` for `List a` as follows.
We can define this function polymorphic in the element type `elemType` for `List elemType` as follows.
> filterList :: (a -> Bool) -> List a -> List a
> filterList :: (elemType -> Bool) -> List elemType -> List elemType
> filterList pred Nil = Nil
> filterList pred (Cons elem list) = if pred elem
> then Cons elem (filterList pred list)
......@@ -261,7 +261,7 @@ Using `(:)` infix is also convenient when defining functions using pattern match
# More predefined polymorphic data types
Haskell has also predefined data types for a polymorphic polymorphic product type (pairs)...
Haskell has also predefined data types for a polymorphic product type (pairs)...
> data (,) a b where
> (,) :: a -> b -> (,) a b
......@@ -308,3 +308,17 @@ We can use the product type to represent a pair of `Integer` and `Bool`...
> secondElem (elem1 : elem2 : list) = Just elem2
The function `secondElem` is a polymorphic function: it accepts lists of arbitrary type and yields the second element of that list (wrapped in a `Just`-constructor) or `Nothing` in case the list has only one or none elements.
Similar to the special syntax for lists, Haskell also allows to write the product type and its constructor using a special syntax.
> intAndBool :: (Integer, Bool) -- using a "mixfix"-notation for products
> intAndBool = (42, True) -- using a "mixfix"-notation for values of product type
Last but not least, we can use the abstraction mechanism of polymorphic data structures for type synonyms as well.
Recall that type synonyms allow us to introduce abbreviations/new names for already predefined types.
For example, we define a list of key-value pairs as follows.
> type KeyValuePairs key value = [] ((,) key value)
> > -- we could also write it mixfix: [(key, value)]
As for polymorphic data types, we introduce the type variable on the left-hand side of the type synonym definition, here `key` and `value` are the type parameters corresponding to the types used as first and second component of the pair.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment