This file gives simple examples to demonstrate the basic concepts introduced in this course. It is not suitable to be read from beginning to end as an introduction to Haskell. (The example functions are not very interesting.) It is more meant as a reference to look up specific concepts.

The source of the text is a literate Haskell file: Demo.lhs that you can download and load into GHC.


Function definition, local declarations

Simple function definition

-- Double the argument
fun1 :: Int -> Int
fun1 x = x*2

Function with local definitions in a where clause:

-- Square length of hypotenuse
fun2 :: Int -> Int -> Int
fun2 x y = x2 + y2
  where
    x2 = x*x
    y2 = y*y

Note that everything local to the definition has to be indented.

Local definitions using let (equivalent to fun2):

-- Square length of hypotenuse
fun2' :: Int -> Int -> Int
fun2' x y =
    let x2 = x*x in
    let y2 = y*y in
    x2 + y2

Using a local function instead (equivalent to fun2):

-- Square length of hypotenuse
fun2'' :: Int -> Int -> Int
fun2'' x y = square x + square y
  where
    square a = a*a

In general, a type a -> b -> c is for a function that takes an a and a b as arguments and return a c as result (but see the section on multiple arguments).

Function application

Functions are applied by just listing the arguments after the function name:

*Main> fun1 10
20

*Main> fun2 3 4
25

Infix operators

Any function of at least two arguments can be written infix, i.e., between its arguments by using back-ticks:

*Main> 3 `fun2` 4
25

*Main> 34 `max` 45
45

This notation can also be used when defining functions:

-- Square length of hypotenuse
hyp :: Int -> Int -> Int
x `hyp` y = x2 + y2
  where
    x2 = x*x
    y2 = y*y

Operators, such as +, *, &&, are normal function (of two arguments). They are written infix by default:

*Main> 1+2
3

*Main> True && False
False

This is also the case for user-defined operators (as long as the name is non-alphanumeric); e.g.

-- Almost equal
(=~) :: Int -> Int -> Bool
a =~ b = abs (a-b) < 2

It is however possible to use operators in prefix style (operator before its arguments) by surrounding it with parentheses:

-- Almost equal
(=~) :: Int -> Int -> Bool
(=~) a b = abs (a-b) < 2

Testing:

*Main> 4 =~ 5
True

*Main> 4 =~ 7
False

*Main> (=~) 4 7
False

Application operator

When applying several functions in sequence, such as in

-- Take the `n` last elements of a list
takeLast :: Int -> [a] -> [a]
takeLast n s = reverse (take n (reverse s))

the parentheses can quickly become unreadable. In that case, the ($) operator can be used to avoid parentheses:

-- Take the `n` last elements of a list
takeLast' :: Int -> [a] -> [a]
takeLast' n s = reverse $ take n $ reverse s

As an approximation, $ can be read as “a parenthesis from here to the end of the expression”.

Note though that ($) does not always mix well with other infix operators. If there are problems, try inserting parentheses.

Types

TODO: Under construction…

Type classes

TODO: Under construction…

fun1 could have been given a more general type:

fun1 :: Num a => a -> a

Data types

This is a redifinition of the standard Bool type (using capital letters to avoid name-clashes):

data BOOL = TRUE | FALSE

Optional values:

data MAYBE a = JUST a | NOTHING

Recursive type (list of Booleans):

data ListBool = EmptyB | AddB Bool ListBool

Recursive type (list of integers):

data ListInt = EmptyI | AddI Int ListInt

Even better – generalization of ListBool and ListInt:

data List a = Empty | Add a (List a)

We will use BOOL, MAYBE and LIST to demonstrate the use of data types. In real code, it is of course better to use the standard Bool, Maybe and [].

Construction

To construct values of our newly introduced types, we have to use the constructors.

thisIsFalse = FALSE
thisIsTrue  = TRUE

thisIsJustZero     = JUST 0
thisIsNothingAtAll = NOTHING

someNumbers :: List Int
someNumbers = Add 1 (Add 2 (Add 3 Empty))
  -- Corresponds to the list [1,2,3]

Note that the constructors are given arguments according to their types (see above). FALSE, TRUE and NOTHING have no arguments. JUST has one argument and Add has two.

Pattern matching

When defining functions that take data types as arguments, we can use patterns to distinguish between different inputs; e.g.:

-- Check if the argument is `Just 1`
isJUST1 :: MAYBE Int -> BOOL
isJUST1 (JUST 1) = TRUE
isJUST1 _        = FALSE

Patterns have the same form as ordinary values: The constructors are applied to argument patterns according to their type. In the above example JUST is applied to the pattern 1 which matches the integer 1.

Patterns are matched in top-down down order: if a pattern fails, the next pattern below is tried, and so on.

A difference between patterns and ordinary values is that patterns may contain wildcards, written as _. This is useful, for example, if we just want to check whether the argument is of the form JUST ...:

-- Check if the argument is of the form `Just ...`
isJUST :: MAYBE a -> BOOL
isJUST (JUST _) = TRUE
isJUST NOTHING  = FALSE

Another difference is that patterns can introduce new variables. This is useful if we are not just interested in knowing that the argument has a certain form, but want to be able to refer to parts of this form. For example, we may not just want to check whether the input has the form JUST ... but also be able to refer to the ... part:

-- Check if the argument is of the form `Just ...`
fromJUST :: MAYBE a -> a
fromJUST (JUST a) = a
fromJUST NOTHING  = error "fromJUST: NOTHING"

It is common to have patterns that match on a single constructor at a time. However, this does not have to be the case – patterns can be arbitrarily deep:

-- Check if the argument is the list 1,2,3
is123 :: List Int -> BOOL
is123 (Add 1 (Add 2 (Add 3 Empty))) = TRUE
is123 _ = FALSE

The pattern can be read as follows: A list whose first element is 1 and whose tail matches Add 2 (Add 3 Empty).

In summary, we see that pattern matching has two purposes:

  1. Distinguish between cases, e.g.

    isJUST (JUST _) = ...
    isJUST NOTHING  = ...
  2. Extract parts of an argument (“destruction”), e.g.

    -- Add the first two numbers in a list
    addFirstTwo :: List Int -> Int
    addFirstTwo (Add a (Add b _)) = a+b

Case expressions

In the above examples, pattern matching is done on the arguments of functions. If, instead, we want to match against an arbitrary value inside an expression, we can use a case expression. We can easily rewrite the above examples using case:

isJUST' :: MAYBE a -> BOOL
isJUST' ma = case ma of
    JUST _  -> TRUE
    NOTHING -> FALSE

fromJUST' :: MAYBE a -> a
fromJUST' ma = case ma of
    JUST a  -> a
    NOTHING -> error "fromJUST: NOTHING"

addFirstTwo' :: List Int -> Int
addFirstTwo' as = case as of
    Add a (Add b _) -> a+b

(These definitions mean exactly the same thing as the previous versions.)

As seen before, we do not actually need to use case for these examples. But whenever we need to match against a value that is not a function argument, we have to use case. For example, the following function matches on the words of a string:

fun3 :: String -> Int
fun3 text = case words text of
    "Alice" : _          -> 23
    "Bob" : "Marley" : _ -> 34

Testing:

*Main> fun3 "Bob Marley is back"
34

If expressions and guards

Instead of pattern matching on Boolean values, it is possible to use an if expression. For example, we can write

-- Absolute value
abs' :: Int -> Int
abs' a = if a < 0 then -a else a

instead of

abs'' :: Int -> Int
abs'' a = case a < 0 of
    True  -> -a
    False -> a

(It means the exact same thing.)

It is not very convenient to nest if expressions:

fun4 :: Int -> String
fun4 n =
    if n < 0   then "negative"    else
    if n < 5   then "quite small" else
    if n < 100 then "normal"      else
                    "big"

This should be read as if there were a parenthesis starting after each else; i.e.

    if ... then ... else
      (if ... then ... else
        (if ... then ... else
           ...)))

By using guards, fun4 can be rewritten in a way that looks more like conditional equations:

fun4' :: Int -> String
fun4' n
    | n < 0     = "negative"
    | n < 5     = "quite small"
    | n < 100   = "normal"
    | otherwise = "big"

The expressions in the guards (e.g. n < 0) must have type Bool. The expression otherwise is defined to be True, so the last case will always be taken, if no other case is taken.

It is possible to mix guards and pattern matching:

maxOfFirstTwo :: List Int -> Int
maxOfFirstTwo (Add a (Add b _)) | a > b     = a
                                | otherwise = b

We can also have guards in case expressions:

firstWordIsFourChars :: String -> Bool
firstWordIsFourChars s = case words s of
    w:_ | length w == 4 -> True
    _                   -> False

This function returns True if the string has at least one word, and this word is four characters long.

Built-in lists

Instead of the List type defined above, one should use Haskell’s built-in lists, which have the same structure, but nicer syntax and lots of predefined helper functions.

Conceptually, built-in lists are defined as:

data [a] = [] | a : [a]

(But this is not a syntactically valid definition.) The interpretation of this type is similar to the interpretation of List:

We see that [a] corresponds to List a; in fact, it is possible to write [] a instead of [a] which shows the correspondence between [] and List.

The (:) constructor is commonly called “cons”.

Construction

Built-in lists are constructed in the same way as List

someNumbers2 :: [Int]
someNumbers2 = (:) 1 ((:) 2 ((:) 3 []))

Since (:) is an infix operator, we can rewrite this more nicely as

someNumbers2' :: [Int]
someNumbers2' = 1 : 2 : 3 : []

And we can make it even nicer using Haskell’s special syntax for built-in lists:

someNumbers2'' :: [Int]
someNumbers2'' = [1,2,3]

Haskell also has special syntax for list enumeration:

*Main> [0..10]
[0,1,2,3,4,5,6,7,8,9,10]

*Main> [0,10..100]
[0,10,20,30,40,50,60,70,80,90,100]

Pattern matching

As always, patterns are written in mostly the same way as ordinary expressions. This is true for built-in lists as well. We

is_123 :: [Int] -> BOOL
is_123 ((:) 1 ((:) 2 ((:) 3 []))) = TRUE
is_123 _ = FALSE

is_123' :: [Int] -> BOOL
is_123' (1 : 2 : 3 : []) = TRUE
is_123' _ = FALSE

is_123'' :: [Int] -> BOOL
is_123'' [1,2,3] = TRUE
is_123'' _ = FALSE

The above three functions all mean the same thing. In this case, the last version is preferred.

If the length of the list is not known, we have to use a cons pattern:

addFirstTwo :: [Int] -> Int
addFirstTwo (a:b:_) = a+b

Higher-order functions

In general, higher-order functions are functions that take functions as arguments or give functions as result.

There are many higher-order functions in Haskell’s standard libraries. We will not go through them here, but just demonstrate the basic ideas.

Functions as arguments

It is often convenient to define general functions whose behavior can be controlled by the caller. This is done by making the function take a function as argument.

For example, the higher-order function

map :: (a -> b) -> [a] -> [b]

takes two arguments: a function (a -> b) and a list [a]. The result is obtained by applying the function to each element in the argument list:

eachEven :: [Int] -> [Bool]
eachEven as = map even as
*Main> eachEven [1..10]
[False,True,False,True,False,True,False,True,False,True]

TODO: Explain (.)

Functions with multiple arguments

In general, functions have one argument and one result. The type a -> b denotes the type of a function whose argument is of type a and whose result is of type b.

But we often see types with more than one arrow, e.g.

(+) :: Int -> Int -> Int

(Here, the overloaded type of (+) has been specialized to type Int.)

We usually think of (+) as taking two arguments. However, if we write out the implicit parentheses in the type, we get

(+) :: Int -> (Int -> Int)

(Note, the type is still the same: The parentheses are there even if they are not written, because -> is a right-associative operator.)

That is: (+) takes one argument of type Int and returns a function Int -> Int. We see that (+) is actually a higher-order function, since it returns a function as result.

So if we want to compute, e.g., 1+2, we can do this by first supplying the argument 1 to (+) and then the argument 2:

onePlus :: Int -> Int
onePlus = (+) 1

onePlusTwo :: Int
onePlusTwo = onePlus 2

Luckily, we don’t have to write this much code to add two numbers: By substituting the definition of onePlus into onePlusTwo, we get (see infix operators):

onePlusTwo' :: Int
onePlusTwo' = ((+) 1) 2

which is the same as

onePlusTwo'' :: Int
onePlusTwo'' = (+) 1 2

which is the same as

onePlusTwo''' :: Int
onePlusTwo''' = 1 + 2

Even though (+) actually takes only one argument, it is still often useful to think of it as a two-argument function. We take the liberty to do this whenever suitable (as common in the functional programming community).

When a function of n arguments is applied to fewer than n arguments, we say that the function is partially applied.

Switching views

Let us return to the definition of eachEven:

eachEven :: [Int] -> [Bool]
eachEven as = map even as

Here we supply two arguments to map, but we now know that map actually only takes one argument, which can be seen by writing out all parentheses in its type:

map :: (a -> b) -> ([a] -> [b])

This means that we could just as well have supplied only one argument and let map itself return the function:

eachEven' :: [Int] -> [Bool]
eachEven' = map even

Now, what is the difference between eachEven and eachEven' ?

The two definitions mean exactly the same thing! They just take a different view of what is defined.

Note that the definition of eachEven' is obtained by just deleting as on both sides of the definition of eachEven.

Anonymous functions (lambda)

Use a lambda function \x -> ... x ... when you need a local function but don’t want to give it a name:

mapSquare :: [Integer] -> [Integer]
mapSquare = map (\x -> x*x)

allPositiveEven :: [Integer] -> Bool
allPositiveEven = all (\a -> a>0 && even a)

These functions could just as well have used a named local function:

mapSquare' :: [Integer] -> [Integer]
mapSquare' = map square
  where
    square x = x*x

allPositiveEven' :: [Integer] -> Bool
allPositiveEven' = all posEven
  where
    posEven a = a>0 && even a

It is often convenient to construct anonymous functions by partial application:

*Main> map (min 6) [1..10]
[1,2,3,4,5,6,6,6,6,6]

Here the function argument to map is min 6 which is a function that returns the minimum of its argument and 6.

If we want to partially apply the second argument of a function, we can use a lambda:

*Main> map (\x -> x `mod` 4) [1..20]
[1,2,3,0,1,2,3,0,1,2,3,0,1,2,3,0,1,2,3,0]

Alternatively, we can use infix notation to partially apply the second argument:

*Main> map (`mod` 4) [1..20]
[1,2,3,0,1,2,3,0,1,2,3,0,1,2,3,0,1,2,3,0]

*Main> map (*2) [1..20]
[2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40]

Another convenient way of creating anonymous functions is to use function composition (.):

*Main> map ((*2) . abs) [-10 .. 10]
[20,18,16,14,12,10,8,6,4,2,0,2,4,6,8,10,12,14,16,18,20]

Here we used the anonymous function (*2) . abs which could just as well have been written using lambda:

*Main> map (\x -> abs x * 2) [-10 .. 10]
[20,18,16,14,12,10,8,6,4,2,0,2,4,6,8,10,12,14,16,18,20]

Generic programming

Haskell provides many ways of writing abstract functions that are meaningful for many different types of data. We are not going to go deep into this area here, but just mention two basic generic functions:

mapM :: Monad m => (a -> m b) -> [a] -> m [b]
fmap :: Functor f => (a -> b) -> f a -> f b

mapM

mapM is used, for example, to perform some IO instruction for each element in a list and collect the results in a list. The following example uses mapM

collectAnswers :: IO [String]
collectAnswers = mapM answer [1..5]
  where
    answer q = do
      putStr $ "What's your answer to question " ++ show q ++ "? "
      getLine

In this example, we used mapM at the type

mapM_ :: (Int -> IO String) -> [Int] -> IO String

(which works because IO is an instance of Monad).

But the nice thing about mapM is that it works for any monad. For example, we can also use it together with QuickCheck’s Gen monad. The following generator produces a list of random numbers in increasing intervals:

genNums :: Gen [Int]
genNums = mapM (\x -> choose (x, x+10)) [0,100 .. 1000]
*Main> sample genNums
[2,103,202,307,403,502,607,701,800,900,1009]
[3,109,205,301,406,506,608,700,807,902,1008]
[9,102,203,306,409,502,600,705,806,907,1006]
...

This example used mapM at the type

mapM_ :: (Int -> Gen Int) -> [Int] -> Gen [Int]

fmap

fmap is a generalization of the map function. First of all, it is perfectly fine to use fmap instead of map for lists:

*Main> map (*2) [1..10]
[2,4,6,8,10,12,14,16,18,20]

*Main> fmap (*2) [1..10]
[2,4,6,8,10,12,14,16,18,20]

The type of fmap says that it works for any type constructor f in the Functor class:

fmap :: Functor f => (a -> b) -> f a -> f b

In the above example, we used it at the type

fmap :: (Integer -> Integer) -> [] Integer -> [] Integer

(Note that [] Integer means the same as [Integer].)

Just like for mapM, the real benefit of fmap comes when we see that it can be used for many other types. For example, it can be used to update a value of type Maybe:

*Main> fmap (*2) (Just 3)
Just 6

Similarly, it can be used to update the value of monadic instructions:

-- Read the first 50 characters of Demo.lhs
readFirst50 :: IO String
readFirst50 = fmap (take 50) $ readFile "Demo.lhs"
*Main> readFirst50
"% Demo\n\n\n\n  <!--\n\\begin{code}\nimport Test.QuickChe"

In this example, fmap is used at the type

fmap :: (String -> String) -> IO String -> IO String

To understand how fmap works for monadic instructions, we can rewrite readFirst50 to

-- Read the first 50 characters of Demo.lhs
readFirst50' :: IO String
readFirst50' = do
    s <- readFile "Demo.lhs"
    return (take 50 s)

[Back to index]