We have seen an example of a bespoke list (the list of shapes). We could define other specific kinds of lists, but it would be painful to have to write essentially the same type definitions and functions every time we wanted to make a list of values.

Fortunately, Typed Racket has a generic (or polymorphic) list type. Most functional languages, including Racket, use lists as their primary mechanism for dealing with sequences. Therefore, they support lists as a built-in mechanism.

Let us consider the simple example of representing lists of booleans.

;; A (Listof Boolean) is either
;; - empty, (written '()) or
;; - (cons b bs), where b is a Boolean and bs is a (listof Boolean)

The empty list '() and the list constructor cons come with the language. There are corresponding predicates empty? and cons? with type any -> Boolean. There is also a predefined variable empty that stands for the empty list.

(: empty : Null)
(: empty? : Any -> Boolean : Null)
(: cons  : (All (T) T (Listof T) -> (Listof T)))
(: cons? : Any -> Boolean : (Listof Any))

The type Listof is a type constructor. The function cons is polymorphic.

Note

The types of cons and cons? are actually slightly different in stock Typed Racket, but in the version we use in the course, we have simplified them to be as above. In the unsimplified version, cons can not only be used to make lists, but also pairs of items.

Let’s write down a few lists of booleans.

empty
(cons true empty)
(cons true (cons false empty))
(cons false (cons true (cons false empty)))

We can also construct lists using the quotation syntax:

'()
(list)
(list true)
'(#t)
'(#t #f)
(list false true false)

While it is important you be comfortable reading the '(…​) format for lists, since the REPL will print out list values this way, it is best to avoid writing lists in this manner yourself. This is because Racket "distributes" the quotation mark over the list when given a list of identifiers, making them all symbols. For instance, '(true false) becomes the same as (list 'true 'false), a list of two Symbols. This list is completely different than (list #t #f). a list of Booleans and probably what you intended.

The functions first and rest are selectors on cons structures.

(: first  : (All (T) (Listof T) -> T))
(: rest   : (All (T) (Listof T) -> (Listof T)))
(: second : (All (T) (Listof T) -> T))
...
(: eighth : (All (T) (Listof T) -> T))
(first '(2)) -> 2
(rest '(2))  -> '()

Your program will halt with an error if you try to take, for instance, the first element of an empty list, or the seventh element of a list with only five. Thus, these functions should be used cautiously, only when the list is known to have the requisite element. Often, pattern matching with cases that cover the different possible list lengths is a more robust approach.

There are also more historical names: car, cdr.

(: car : (All (T) (Listof T) -> T))
(: cdr  : (All (T) (Listof T) -> (Listof T)))

We will not use these less-intuitively named versions in class, so you needn’t memorize them. We include them here just so you may recognize them if you ever encounter them.

Functions on lists

Typed Racket provides various standard operations on lists as polymorphic functions. We can compute the length of a list with

(: length : (All (X) (Listof X) -> Integer))

(check-expect (length '()) 0)
(check-expect (length '(1 2 3)) 3)

Reverse the order of a list using

(: reverse : (All (X) (Listof X) -> (Listof X))

(check-expect (reverse '(1 2 3)) '(3 2 1))

Two or more lists can be concatenated using the append function.

(: append : (All (X) (Listof X) * -> (Listof X)))

(check-expect (append '(1 2 3) '(4) '(5 6))
	      '(1 2 3 4 5 6))

That star means that you can provide any number of lists to append to a single call of append.

We can extract an element from a list by its index using

(: list-ref : (All (X) (Listof X) Integer -> X))

(check-expect (list-ref '(1 2 3) 0) 1)
(check-expect (list-ref '(1 2 3) 1) 2)
(check-expect (list-ref '(1 2 3) 2) 3)

Notice that indexing starts at 0. (This convention is common across most programming languages.) Specifying a negative index or one that is too large results in an error.

Lastly, the make-list function creates a list of the given length, where all of the list elements are the same value.

(: make-list : (All (X) (Integer X -> (Listof X)))

(check-expect (make-list 3 "hi") (list "hi" "hi" "hi"))
Last updated 2022-02-05 11:38:36 -0600
Table of Contents | Back to CMSC 15100 Home