**Previously:** A document found in Level 2 discusses two primitive values in the SML language: integers and strings. This Level 2 document also discusses the *tuple*, a language construct which can be used to aggregate (i.e., group) values. In Bricklayer programs, 2-dimensional (and 3-dimensional) coordinates are represented as tuples constructed from 2 (and 3) integer values. For example, the Bricklayer origin in a 2D space is denoted by the tuple (0,0).

**This document** introduces the *list*. Slides covering this material can be found here.

Like a tuple, a list is a language construct which can be used to aggregate values. Syntactically, a list looks similar to a tuple. The only difference is that in a list, the open/close symbols are square brackets (while in a tuple the open/close symbols are parentheses).

Tuple |
List |

() | [] |

(1) | [1] |

(1,2) | [1,2] |

In general, a list is a sequence of comma-separated expressions enclosed in square brackets.

[expression_{1}, expression_{2}, … , expression_{n}]

Though they are syntactically similar, lists and tuples are very different semantically. A tuple can be used to group values of any type, whereas a list can only group values belonging to a single type. For example, in SML it is perfectly legal to write *(1,”hello”)*. However, writing *[1,”hello”]* results in a compile-time type error since the first element of the list is of type *int* while the second element of the list is of type *string*. To highlight this distinction, we say that tuples are a *heterogeneous aggregation* mechanism while lists are a *homogeneous aggregation* mechanism.

For technical reasons, because of their homogeneous nature, lists can be manipulated in important ways that tuples cannot. For example, it is possible to concatenate two lists to form a larger list. Elements can also be removed from lists to produce smaller lists. The central idea here is that lists can get larger and smaller *dynamically* (i.e., during program execution). In contrast, the size of a tuple is *static* and remains fixed during program execution. This flexibility makes the list a workhorse of SML (as well as other functional programming languages). In order for lists to change their size and still remain well-typed, the number of elements in a list cannot be part of its type. For example, the lists [1,2,3] and [4,5] both have the type *int list*. In contrast, the tuple (1,2,3) is of type *int*int*int* while the tuple (4,5) is of type *int*int*.

This brings us to the special case of the empty list which is denoted []. What is its type? For example, does the list [] have type *int list* or *string list* or does it have some other type? The solution is to let [] be a type that is compatible with all lists. The technical term for such a type is a *polymorphic type*.

## Examples of Lists

The table below gives various examples of lists. Note that the elements of a list can be other lists as well as tuples or any combination thereof so long as all elements are of the same type.

A list of integer values. |
[1,2,3,4] |

A list of integer lists. |
[ [1,2,3,4], [2,3,4], [3], [] ] |

A list of string lists. |
[ [“hello”], [], [“hello”,”world”] ] |

A list of int x int tuples. |
[ (0,0), (1,1), (2,2) ] |

A list of Bricklayer LEGO bricks. |
[ BLUE, RED, YELLOW ] |

A list of tuples consisting of Bricklayer LEGO bricks and 2D coordinates. |
[ (BLUE,(0,0)), (RED,(1,1)), (YELLOW,(2,2)) ] |

## List operators

SML provides two primitive operators for manipulating lists. The first operator is the *construction* operator and is generally referred to as the *cons* operator. The second operator is the *concatenation* operator.

### The Cons Operator ::

In SML, the symbol :: is used to denote the cons operator. The cons operator is a *binary infix operator*. The fact that it is a binary operator means it is applied to 2 values (in this context, values are also referred to as *operands*). Examples of well-known binary operators include the arithmetic operators + and -. The fact that the cons operator is an infix operator means that must be positioned between the operands to which it is applied. Examples of well-known infix operators include the arithmetic operators + and -.

Given an element *x* and a list of elements *xs* the expression *x :: xs *denotes a list expression. Let x = 1 and xs = [2,3,4]. The evaluation of the expression x::xs will produce the list value [1,2,3,4] as its result.

### The Concatenation Operator @

In SML, the symbol @ is used to denote the concatenation operator. The concatenation operator is also a binary infix operator. The concatenation operator may only be applied to two lists of the same type. For example, an *int list* and a *string list* cannot be concatenated because they are different types of lists.

Let xs = [1,2,3] and let ys = [4,5,6]. The evaluation of the expression xs @ ys will produce the list value [1,2,3,4,5,6] as its result.

### The List Functions hd and tl

SML provides two basic functions for decomposing a list. The first function is called *head* and is denoted *hd*. The second function is called *tail* and is denoted *tl*. Both functions are *unary functions*, meaning they are only applied to a single argument. When applied to a list, the function *hd* returns the first element of the list (e.g., hd [1,2,3] = 1). When applied to a list, the function *tl* returns a list constructed from all but the first element of the input list (e.g., tl [1,2,3] = [2,3]).

## Examples of List Expressions

The table below gives various examples of list expressions and the results of their evaluation.

List Expression |
Evaluation Result |
List Expression |
Evaluation Result |

1 :: [] | [ 1 ] | [1] @ [] | [ 1 ] |

1 :: [2,3,4] | [ 1,2,3,4 ] | [] @ [1] | [ 1 ] |

[1] :: [[2,3],[4]] | [ [1], [2,3], [4] ] | [1,2] @ [2,3] | [ 1,2,2,3 ] |

hd [1,2,3] | 1 | tl [1,2,3] | [2,3] |

## Order Of Evaluation

When evaluating arithmetic expressions multiplication and division are performed before addition and subtraction. For example, when evaluating the expression 5 + 6 * 7, the multiplication operation is performed first followed by the addition operation. The resulting evaluation sequence is the following: 5 + 6 * 7 → 5 + 42 → 47.

Parentheses can be used to alter the order of evaluation. For example, when evaluating the expression (5 + 6) * 7, the addition operation is performed first followed by the multiplication operation. The resulting evaluation sequence is the following: (5 + 6) * 7 → 11 * 7 → 77.

The rules governing the evaluation of list expressions are conceptually similar to those governing the evaluation of arithmetic expressions. Here we discuss the evaluation of a particular kind of list expression which we will call a *pure list-expression*. A pure list-expression is a well-formed (i.e., type correct) expression consisting only of (1) the list operators :: and @, (2) values, (3) variables, and (4) the functions *hd* and *tl*. Note that this definition implies that a pure list-expression contains no evaluation-altering parentheses.

The following algorithm can be used when evaluating pure list expressions. Let *e _{0}* denote the initial pure list-expression that we want to evaluate.

- Replace all variables in
*e*with the values to which they are bound. Let_{0}*e*denote the resulting expression._{1} - Process
*e*from_{1}*left-to-right*evaluating all calls to the functions*hd*and*tl*. Let*e*denote the resulting expression. The phrase_{2}*function application is left associative*is the formal way of saying that function applications are to be evaluated in a left-to-right fashion. - Process
*e*from_{2}*right-to-left*evaluating each operation*::*and*@*in the order encountered. The phrase*the list operators :: and @ are right associative*is the formal way of saying that these list operations are to be evaluated in a right-to-left fashion.

The table below gives some examples of pure list-expressions as well as their evaluation sequences.

Pure List-Expression |
Evaluation Sequence |

tl [1,2,3] @ [3,4] | tl [1,2,3] @ [3,4] → [2,3] @ [3,4] → [2,3,3,4] |

hd [1,2,3] :: [3,4] | hd [1,2,3] :: [3,4] → 1 :: [3,4] → [1,3,4] |

[1,2,3] @ tl [3,4] | [1,2,3] @ tl [3,4] → [1,2,3] @ [4] → [1,2,3,4] |

[1,2,3] @ 4 :: [5,6] | [1,2,3] @ 4 :: [5,6] → [1,2,3] @ [4,5,6] → [1,2,3,4,5,6] |

[1,2,3] @ 4 :: [5,6] @ [7] | [1,2,3] @ 4 :: [5,6] @ [7] → [1,2,3] @ 4 :: [5,6,7] → [1,2,3] @ [4,5,6,7] → [1,2,3,4,5,6,7] |

## Efficient Computations

The evaluation algorithm discussed in the previous section is a standard one and can be extended in a straightforward manner so that it is suitable for the evaluation of any kind of (well-formed) expression. Note that in this algorithm, each evaluation step consists of a single operation or function call. An important question is whether other algorithms exist which can also correctly evaluate expressions. In particular, does there exist an algorithm in which the evaluation of a list expression (or any expression for that matter) requires fewer evaluation steps than the standard algorithm given here? More specifically, is it possible to perform more than one operation or function call during an evaluation step?