Unit 4: List comprehensions

Introduction

List comprehensions are a convenient way of defining lists in Haskell. List comprehensions used to be called ZF-expressions. The following is an example of such an expression:

[ x * x | x <- [1, 2, 7, 12], even x ]

This list comprehension is the list of all those things of the form x * x such that x is drawn from the list [1, 2, 7, 12] and x is even. Thus, the above evaluates to [4, 144]. The general form of a list comprehension is:

[ EXP | QUAL, ..., QUAL ]

where QUAL is either a Boolean-valued expression or a generator. A generator has one of two forms: either VARIABLE <- LIST or PATTERN <- LIST.

Alternative notation

The list comprehension displayed above can also be written using the do-notation. To use this you need to load the Control.Monad module:

ghci> import Control.Monad
ghci> do { x <- [1,2,7,12] ; guard (even x) ; return (x * x) }
[4,144]

This works because lists constitute a monad. The do-notation is usually written without using curly brackets and semicolons like this:

do x <- [1,2,7,12] 
   guard (even x) 
   return (x * x)

Reduction rules for list comprehensions

The behaviour of list comprehension is completely determined by the following five reduction rules:

(ZF1) [ e | v <- [], q ] reduces to [], where q is a sequence of zero or more qualifiers.
(ZF2) [ e | v <- f:fs, q ] reduces to [ e | q ] [ v := f ] ++ [ e | v <- fs, q ], where h [ v := f ] represents h with all occurrences of v in it replaced by f.
(ZF3) [ e | False, q ] reduces to [].
(ZF4) [ e | True, q ] reduces to [ e | q ].
(ZF5) [ e | ] reduces to [ e ].

Quicksort

Hoare's Quicksort algorithm can be expressed very concisely by using a couple of list comprehensions:

quick :: Ord a => [a] -> [a]
quick []     = []
quick [x]    = [x]
quick (x:xs) = quick [ u | u <- xs, u < x ]
               ++ [x] ++
               quick [ u | u <- xs, u >= x ]

© Antoni Diller (17 September 2021)