@@ -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.

>dataListawhere

>Nil::Lista

>Cons::a->Lista->Lista

>dataListelemTypewhere

>Nil::ListelemType

>Cons::elemType->ListelemType->ListelemType

>derivingShow

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::ListInteger

>intList1=Cons43Nil

...

...

@@ -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::ListInteger

>intList2=Cons1(Cons2(Cons3Nil))

...

...

@@ -96,7 +96,7 @@ We can now also define _polymorphic functions_.

We define `lengthList` inductively over the structure of the list argument.

>lengthList::Lista->Integer

>lengthList::ListelemType->Integer

>lengthListNil=0

>lengthList(Conselemlist)=1+lengthListlist

...

...

@@ -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

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)->Lista->Lista

...

...

@@ -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.

@@ -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)=Justelem2

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.

>typeKeyValuePairskeyvalue=[]((,)keyvalue)

>>-- 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.