Type variable

Type variable #

A type variable stands for an underspecified type.

Example. Consider a function half that maps a list to its first half (say rounded down if the list has odd length).

The implementation of this function is identical for:

  • a list of strings,
  • a list of integers,
  • a list of Boolean values,
  • etc.

A type variable lets us declare such a function

  • in Haskell:
half :: [a] -> [a]    -- `a` is a type variable here
  • or in Java:
/* will be revealed later */

In Haskell #

Syntax. In Haskell, a type variable starts with a lowercase letter (like a regular variable).

By convention, the first letters of the alphabet (a, b, c, etc.) are often used as type variables.

Examples.

  • The type variable a stands for any type,
  • [a] is the type of lists,
  • a function with type [a] -> [a] maps a list to a list of the same type,
  • a function with type (a, a) -> Bool maps a pair of elements of the same type to a Boolean value, and its curried version has type a -> a -> Bool.

Example. The Haskell Prelude provides two (partial) functions head and tail, implemented as follows. Observe the type declarations in these definitions.

-- Maps an nonempty list to its head. Throws an error if the list is empty.
head :: [a] -> a
head (x : _) = x
head [] = error "Prelude.head: empty list"

-- Maps an nonempty list to its tail. Throws an error if the list is empty.
tail :: [a] -> [a]
tail (_ : xs) = xs
tail [] = error "Prelude.tail: empty list"

Implement the native function composition operator (.) of Haskell, where g . f is the composition of f and g.

(.) :: (b -> c) -> (a -> b) -> a -> c
(g . f) x = g (f x)

Benefits #

Example (continued).

Generality #

The function head above can be applied to any nonempty list, regardless of whether it consists of integers, strings, etc.
For instance:

  • the expression head [3,1,2] is valid (and evaluates to 3), and
  • the expression head ["because","we","can"] is also valid (and evaluates to "because").

Type safety #

The type declaration head :: [a] -> a ensures that the following program does not compile:

-- auxiliary function
even :: Int -> Bool
even x = x `mod` 2 == 0

main :: IO ()
{-
  Invalid expression:
  `head ["because","we","can"]` evaluates to a `String`,
  whereas `even` expects an `Int`.
-}
main = print $ even (head ["because","we","can"])