Types, expressions, values and functions

ITT8060 Advanced Programming · Autumn 2026

Tallinn University of Technology

Full prose version with runnable examples: notes.html

ITT8060

From last week to this week

Last week: a lightning tour — let, functions, tuples, if, recursion, match.

This week: what do they mean?

  • What does a type tell us?
  • How is an expression evaluated?

Typing rules and evaluation rules.

ITT8060
f 0       = 1
f (n + 1) = (n + 1) * f n
f 3               // n is 2
~> 3 * f 2        // rhs is (2 + 1) * f 2
~> 3 * (2 * f 1)
~> 3 * (2 * (1 * f 0))
~> 3 * (2 * (1 * 1))
~> 6

Functions by equations

Read the equations defining the function top-to-bottom, left-to-right.

An expression matching a left-hand side reduces to the right-hand side (reading top-to-bottom).

e ~> e': expression e reduces to e'.

ITT8060

Notation

\(e \leadsto e'\)reduction: \(e\) reduces to \(e'\). In traces: ~>.

\(e[v/x]\)substitution: \(e\) with the value \(v\) substituted for the variable \(x\).

\(e : t\)typing: expression \(e\) is of type \(t\).

Rules have the following form where premises are above and conclusion is below the bar.

\[\dfrac{\text{premise}_1 \qquad \text{premise}_2}{\text{conclusion}}\]

Program text in teletype, metavariables in italics: \(\mathtt{if}\ b\ \mathtt{then}\ e_1\ \mathtt{else}\ e_2\).

ITT8060

Types, expressions, values

and type safety

ITT8060

Expressions and values

A functional program is an expression; evaluating (or running) it means reducing it.

f 3 is an expression. It is reducible: f 3 ~> 3 * f 2, which is again reducible.

Some expressions do not reduce any further: the values. f 0 is not a value; 1 is.

Evaluation terminates with a value.

Not every expression is sensible. What is f f? What is 3 3?

A type system excludes certain syntactically correct expressions.

ITT8060

The typing relation

\(e : t\) — "expression \(e\) is of type \(t\)". For example 5 : int and 3 + 2 : int.

For an undesirable expression (such as f f) there is no t with e : t.

For every type, a designated set of expressions are its values (including the literals: 0, true, "abc", 1.0, …).

Which of the following are values?

  • 2 + 2 : int
  • 4 : int
  • 4 % 2 = 0 : bool
  • 4 % 2 : int
  • sqrt 2.0 : float
ITT8060

Type safety

Preservation: reduction steps preserve the type.

\[\text{if } e : t \text{ and } e \leadsto e' \text{, then } e' : t.\]

An expression of type int cannot evaluate to a value of type string.

Progress: a well-typed expression is a value or can take a reduction step.

\[\text{if } e : t \text{, then } e \text{ is a value, or } e \leadsto e' \text{ for some } e'.\]

Evaluation does not get stuck (append s : string to a function f : int -> int).

Well-typed programs don’t go wrong.

ITT8060

Variables

ITT8060
3.14 * 1.0 * 1.0      // radius 1.0
3.14 * 2.0 * 2.0      // radius 2.0
3.14 * 3.0 * 3.0      // radius 3.0

3.14 * r * r          // any radius

Variables

We do not want a distinct program for every possible radius.

An expression may contain variables. We may think of a variable as a hole into which we plug values.

A variable may occur multiple times in an expression.

The occurrences of r in 3.14 * r * r are free: nothing in the expression introduces the variable r.

ITT8060
(3.14 * r * r)[2.0/r]
  is  3.14 * 2.0 * 2.0

((3.14 * r * r)[2.0/r])[3.0/r]
  is  (3.14 * 2.0 * 2.0)[3.0/r]
  is  3.14 * 2.0 * 2.0

Substitution

\(e[v/x]\): substitute \(v\) for the free occurrences of \(x\) in \(e\).

Once substituted, the variable occurrence is gone.

Substitution is consistent: every free occurrence of the same variable gets the same value.

Different values for different occurrences? Use different variables: 3.14 * r * s.

ITT8060

let expressions

binding, scope, shadowing

ITT8060

let-expressions

let x = e in e'

x is an identifier, e is the binding expression and e' is the body expression.

Local variable x with scope e'; its value is the result of evaluating e.

In e', occurrences of x are bound (no longer free) as the let introduces x. (Says nothing about e.)

Substituting for x does not touch e' as those occurrences are bound (but not those in e):

\((\mathtt{let}\ x = e\ \mathtt{in}\ e')[v/x]\)   is   \(\mathtt{let}\ x = e[v/x]\ \mathtt{in}\ e'\)

ITT8060

Evaluating let

Binding expression is a value: the let disappears.

\[\mathtt{let}\ x = v\ \mathtt{in}\ e' \;\leadsto\; e'[v/x] \qquad (v \text{ a value})\]

Binding expression is not yet a value: it takes a step.

\[\dfrac{e \leadsto e''}{\mathtt{let}\ x = e\ \mathtt{in}\ e' \;\leadsto\; \mathtt{let}\ x = e''\ \mathtt{in}\ e'}\]

  1. Evaluate e to v.
  2. Substitute v for x in e'.
  3. Evaluate e'[v/x] to v'. The overall result is v'.
ITT8060

Reduction steps and the interpreter

let r = 1.0 + 1.0 in 3.14 * r * r
~> let r = 2.0 in 3.14 * r * r
~> (3.14 * r * r)[2.0/r]
~> 3.14 * 2.0 * 2.0
~> 12.56
let letExample =
  let r = 1.0 + 1.0 in 3.14 * r * r

// val letExample: float = 12.56

On the left: reduction steps.

On the right: same expression in F# interpreter.

ITT8060

Typing let

\[\dfrac{e : t \qquad e' : t' \ (\text{ assuming } x : t)}{(\mathtt{let}\ x = e\ \mathtt{in}\ e') : t'}\]

Note that e and x have the same type. Recall that the evaluation rule substitutes the value of e for x.

In F#, the keyword in may be replaced by a newline. Then the scope extends to the rest of the enclosing block.

ITT8060

A let needs a body

> (let x = 1) + 1;;

  (let x = 1) + 1;;
  -^^^

error FS0588: The block following this
'let' is unfinished. Every code block is
an expression and must have a result.
let letInline = (let x = 1 in x + x) + 1

// val letInline: int = 3

Recall that the result of a let-expression is the result of the body e'. If the body is missing, then the compiler will complain.

The addition of in x + x completes the let expression (with x + x as the body).

ITT8060

Shadowing

let r = 1.0 in
  r + let r = 2.0 in 3.14 * r * r

Every let x = … binds a new variable. Same name as one in scope? The new one shadows the outer one in its scope.

Recall that we substitute into free occurrences of variables.

(r + let r = 2.0 in 3.14 * r * r)[1.0/r]
  is  1.0 + let r = 2.0 in 3.14 * r * r

The body of the let (since it introduces r) is not affected.

The result of evaluating (r + let r = 2.0 in 3.14 * r * r)[1.0/r] is 13.56.

ITT8060

let definitions

At the top level, a let does not require a body expression (the rest of the scope is the body).

let bad = 1 + pi   // pi not in scope

let pi = 3.14

let good = 2 + pi  // pi in scope
ITT8060

Conditionals

ITT8060

Conditionals are expressions

if b then e1 else e2
  • b condition
  • e1 the true branch
  • e2 the false branch
let ifInline = 1 + if true then 0 else 1
// val ifInline: int = 1

A conditionl can appear anywhere an expression is expected (unlike statements in imperative languages).

ITT8060

Evaluating if

Two rules when condition is a value:

\[\mathtt{if}\ \mathtt{true}\ \mathtt{then}\ e_1\ \mathtt{else}\ e_2 \;\leadsto\; e_1 \qquad \mathtt{if}\ \mathtt{false}\ \mathtt{then}\ e_1\ \mathtt{else}\ e_2 \;\leadsto\; e_2\]

One rule when condition is not yet a value:

\[\dfrac{b \leadsto b'}{\mathtt{if}\ b\ \mathtt{then}\ e_1\ \mathtt{else}\ e_2 \;\leadsto\; \mathtt{if}\ b'\ \mathtt{then}\ e_1\ \mathtt{else}\ e_2}\]

  1. Evaluate b to a value
  2. Evaluate the branch dictated by b. The other branch is not evaluated.
ITT8060

Example

if 5 % 2 = 0 then 1 + 1 else 2 + 2
~> if 1 = 0 then 1 + 1 else 2 + 2
~> if false then 1 + 1 else 2 + 2
~> 2 + 2
~> 4

Interpreter gives the same result.

let ifExample =
  if 5 % 2 = 0 then 1 + 1 else 2 + 2

// val ifExample: int = 4
ITT8060

Typing if

\[\dfrac{b : \mathtt{bool} \qquad e_1 : t \qquad e_2 : t}{\mathtt{if}\ b\ \mathtt{then}\ e_1\ \mathtt{else}\ e_2 : t}\]

Both branches have the same type, and that is the type of the whole expression.

Type information is static. Before we execute the program, we do not know whether b is going to be true or false.

Recall preservation: whichever evaluation rule applies, the type stays the same.

ITT8060

Tuples

ITT8060

What are tuples?

Group together a fixed number of values of possibly different types.

Tuples are also called product types.

The Cartesian product of two sets \(A\) and \(B\), denoted \(A \times B\), is the set of all ordered pairs \((a, b)\) where \(a \in A\) and \(b \in B\).

\[ A \times B = \{ (a, b) \mid a \in A \land b \in B \} \]

Example: \(\{ A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K \} \times \{ ♡, ♢, ♧, ♤ \}\).

ITT8060

Evaluation

Syntax: A \(k\)-tuple is constructed with \((e_1, ..., e_k)\).

Simplified evaluation rule:

\[\dfrac{e_i \leadsto e_i'}{(v_1, \ldots, v_{i-1}, e_i, \ldots, e_k) \; \leadsto \; (v_1, \ldots, v_{i-1}, e_i', \ldots, e_k)}\]

Values are tuples of the form \((v_1, ..., v_k)\) where each \(v_i\) is a value.

The rule fixes a left-to-right order: to evaluate \(e_i\), everything to its left must already be a value.

ITT8060

Example

(1 + 1, "a" + "b", true || false)
~> (2, "a" + "b", true || false)
~> (2, "ab", true || false)
~> (2, "ab", true)

Reduction steps agree with the interpreter.

let tupleExample =
  (1 + 1, "a" + "b", true || false)

// val tupleExample: int * string * bool
//   = (2, "ab", true)
ITT8060

Typing

If \(t_1, \ldots, t_k\) are types, then so is \(t_1 * \cdots * t_k\).

\[\dfrac{e_1 : t_1 \qquad \cdots \qquad e_k : t_k}{(e_1, \ldots, e_k) : t_1 * \cdots * t_k}\]

t1 * (t2 * t3), t1 * t2 * t3 and (t1 * t2) * t3 are "almost" the same, but not equal.

let flat    : int * int * int   = 1, 2, 3
let nested  : int * (int * int) = 1, (2, 3)
let nested' : (int * int) * int = (1, 2), 3
ITT8060

Pairs and unit

A 2-tuple is also called a pair.

let pair : string * int = "ab", 1

Two projections: fst : 'a * 'b -> 'a and snd : 'a * 'b -> 'b.

unit is the 0-tuple type. It has exactly one value, ().

let unitValue : unit = ()
ITT8060

Sequential composition

e; e'

ITT8060

Evaluation and typing e; e'

Read e; e' as e then e'. The ; is usually a newline.

\[v;\ e' \;\leadsto\; e' \quad (v \text{ a value}) \qquad \qquad \dfrac{e \leadsto e''}{e;\ e' \;\leadsto\; e'';\ e'}\]

  1. Evaluate e to a value v. Discard v.
  2. Evaluate e' to a value v'. Final result: v'

 

Typing rule:

\[\dfrac{e : t \qquad e' : t'}{(e;\ e') : t'}\]

ITT8060

Discarding a result

2 + 2; 1 + 1
~> 4; 1 + 1
~> 1 + 1
~> 2
> let seqExample = 2 + 2; 1 + 1;;

warning FS0020: The result of this
expression has type 'int' and is
implicitly ignored. ...

val seqExample: int = 2

The compiler accepts it and then warns us: computing 2 + 2 (a value of type int) only to discard it is usually a mistake.

Why evaluate e and then discard the result? For its side effect.

Note that there is no warning if e : unit (i.e., we discard ()).

ITT8060

Functions

fun x -> e

ITT8060

Anonymous functions

We construct a function with argument x and body e as fun x -> e.

Example: fun r -> 3.14 * r * r.

fun x -> e introduces the variable x with scope e (similar to let). Occurrences of x in e are no longer free.

(fun x -> x + y)[2/x] is unchanged.

(fun x -> x + y)[2/y] is (fun x -> x + 2).

No evaluation rules: fun x -> e is a value.

ITT8060

Typing rule

\(s \rightarrow t\) is the type of functions from \(s\) to \(t\).

\[\dfrac{e : t \ (\text{ assuming } x : s)}{(\mathtt{fun}\ x \rightarrow e) : s \rightarrow t}\]

Example: assuming x : int, x % 2 = 0 : bool. Then fun x -> x % 2 = 0 is of type int -> bool.

Note that there are two different arrows here.

  • In fun x -> e, the -> constructs a function value.
  • In s -> t, the -> constructs a function type.
ITT8060

Function application

ITT8060

Evaluation

Function value applied to argument value: substitute.

\[(\mathtt{fun}\ x \rightarrow e)\ v \;\leadsto\; e[v/x] \qquad (v \text{ a value})\]

Function or argument not yet a value: they take a step, function first.

\[\dfrac{f \leadsto f'}{f\ a \;\leadsto\; f'\ a} \qquad \dfrac{a \leadsto a'}{(\mathtt{fun}\ x \rightarrow e)\ a \;\leadsto\; (\mathtt{fun}\ x \rightarrow e)\ a'}\]

  1. Evaluate f to fun x -> e.
  2. Evaluate a to v.
  3. Evaluate e[v/x].
ITT8060

Typing

\[\dfrac{f : s \rightarrow t \qquad a : s}{f\ a : t}\]

If f : int -> bool and x : int, then f x : bool.

If f : int -> bool, then f "5" is a typing error. The function f expects an argument of type int but the provided argument is a string.

Typing errors are static.

ITT8060

Evaluation steps vs interpreter

(fun r -> 3.14 * r * r) (1.0 + 1.0)
~> (fun r -> 3.14 * r * r) 2.0
~> (3.14 * r * r)[2.0/r]
~> 3.14 * 2.0 * 2.0
~> 12.56
let applyExample =
  (fun r -> 3.14 * r * r) (1.0 + 1.0)

// val applyExample: float = 12.56
ITT8060

let vs function application

let x = e in e'
~> let x = v in e'    // e ~> v
~> e'[v/x]
(fun x -> e') e
~> (fun x -> e') v    // e ~> v
~> e'[v/x]

Both evaluate e to a value and substitute it for x in e'.

ITT8060

Naming functions

Functions are values: bind them with let.

let circleFun = fun r -> 3.14 * r * r

Shorthand: move the argument to the left of =, keep only the body on the right.

let circle r = 3.14 * r * r

Annotations: on the argument, and after the argument list for the result.

let circleAnnotated (r : float) : float =
  3.14 * r * r
ITT8060

Recursive definitions

ITT8060

Factorial in F#

f 0       = 1
f (n + 1) = (n + 1) * f n

The function f is defined in terms of itself.

In F#, the function f needs to be in scope in its definition. This requires the keyword rec.

let rec factIf n =
  if n = 0
  then 1
  else n * factIf (n - 1)

Without rec, the factIf in the else branch is undefined (or worse, refers to something else).

ITT8060
> let bar n = -n;;
val bar: n: int -> int

> let bar n =
    if n = 0 then 1 else n * bar (n - 1);;
val bar: n: int -> int

> bar 3;;
val it: int = -6

Omitting rec in the interpreter

No error, no warning.

Nothing wrong with the first bar.

The second bar refers to bar in its body (else branch). As the definition is not let rec, the bar in the else branch is the bar defined above.

bar 3 is 3 * (-2).

Duplicate definitions are not allowed at top level.

ITT8060

Evaluating fact 3

fact 3
~> (fun n -> if n = 0 then 1 else n * fact (n - 1)) 3
~> (if n = 0 then 1 else n * fact (n - 1))[3/n]
~> if 3 = 0 then 1 else 3 * fact (3 - 1)
~> if false then 1 else 3 * fact (3 - 1)
~> 3 * fact (3 - 1)
~> 3 * fact 2
~> ...
~> 3 * (2 * (1 * fact 0))
~> 3 * (2 * (1 * (if n = 0 then 1 else n * fact (n - 1))[0/n]))
~> 3 * (2 * (1 * (if true then 1 else fact (0 - 1))))
~> 3 * (2 * (1 * 1))
~> 6
ITT8060

Base case, step case

let rec fact n = if n = 0 then 1 else n * fact (n - 1)

Base case: n = 0, no recursive call, result is 1.

Step case: n <> 0, recursive call fact (n - 1), result is n * fact (n - 1).

Typically, we want the step case to take as "closer" to the base case. When we reach the base case, the recursion terminates.

In this example, we only move closer to the base case when n > 0.

ITT8060

Mutually recursive definitions

let rec foo x = x * bar (x - 1)

let rec bar y =
  if y <= 0 then 1 else y + foo (y - 1)

Separate let rec: bar is not in scope in the definition of foo.

let rec foo x = x * bar (x - 1)

and bar y =
  if y <= 0 then 1 else y + foo (y - 1)

// foo 3 = 9

With and: we define both simultaneously; each may refer to the other.

ITT8060

Functions again

currying and associativity

ITT8060

Arrows and applications

\(s\) and \(t\) in \(s \to t\) can themselves be function types.

\(\rightarrow\) is right-associative:

\[s \rightarrow t \rightarrow u \quad\text{stands for}\quad s \rightarrow (t \rightarrow u)\]

Application is left-associative:

\[f\ a\ b \quad\text{stands for}\quad (f\ a)\ b\]

Formally, every function takes one argument. The ability to return a function gives the impression of several arguments.

ITT8060

One argument at a time

(fun x -> (fun y -> x + y)) 2
~> (fun y -> x + y)[2/x]
~> fun y -> 2 + y        : int -> int

(fun y -> 2 + y) 3
~> 2 + 3
~> 5                     : int
let addTwo =
  (fun x -> (fun y -> x + y)) 2
// val addTwo: (int -> int)

let curryExample = (fun x y -> x + y) 2 3
// val curryExample: int = 5

fun x y -> x + y is sugar for fun x -> (fun y -> x + y).

Let \(f : S \rightarrow T \rightarrow U\), \(s : S\) and \(t : T\). (Recall that \(S \to T \to U\) stands for \(S \to (T \to U)\).)

Then \(f\ s : T \rightarrow U\) and \(f\ s\ t : U\). (Recall that \(f\ s\ t\) stands for \((f\ s)\ t\).)

ITT8060

Pattern matching

match e with

ITT8060

The match expression

match e with
| p_1 [when c_1] -> e_1
...
| p_n [when c_n] -> e_n

Similar to if-then-else, but we can choose the patterns.

p_i patterns, c_i optional Boolean guards, e_i bodies.

At the moment, for us it is essentially a switch.

ITT8060

Simplified evaluation and typing rules

\[\dfrac{v \text{ matches } p_i \text{ and } c_i \leadsto \mathtt{true}}{\mathtt{match}\ v\ \mathtt{with}\ \ldots \;\leadsto\; e_i} \qquad \dfrac{e \leadsto e'}{\mathtt{match}\ e\ \mathtt{with}\ \ldots \;\leadsto\; \mathtt{match}\ e'\ \mathtt{with}\ \ldots}\]

\[\dfrac{e : t \qquad p_i \text{ a pattern for } t \qquad c_i : \mathtt{bool} \qquad e_i : u}{(\mathtt{match}\ e\ \mathtt{with}\ \ldots) : u}\]

Patterns \(p_i\) may introduce bindings that are in scope for \(c_i\) and \(e_i\).

\(\mathtt{match}\ v\ \mathtt{with}\ \ldots \;\leadsto\; e_i[v_1/x_1]\ldots[v_k/x_k]\) when

  • \(v\) matches \(p_i\)
  • \(c_i[v_1/x_1]\ldots[v_k/x_k] \leadsto \mathtt{true}\)

where \(x_i \mapsto v_i\) are the bindings introduced by \(p_i\).

ITT8060

Example

let classify p =
    match p with
    | (0, 0)                -> "Both zero"
    | (x, y) when x % 2 = 0 -> "x even, y is: " + string y
    | w                     -> "Whatever: " + string p

(0, 0) — matches when both components zero, introduces no bindings.

(x, y) when … — matches any pair; binds x and y to first and second component. What is their scope?

w — any pair; binds w to the whole pair.

ITT8060

Evaluating classify (1 + 1, 2 + 2)

classify (1 + 1, 2 + 2)
~> classify (2, 4)
~> match (2, 4) with
   | (0, 0)                -> "Both zero"
   | (x, y) when x % 2 = 0 -> "x even, y is: " + string y
   | w                     -> "Whatever: " + string (2, 4)

(0, 0) fails. (x, y) matches with x = 2, y = 4; the guard (x % 2 = 0)[2/x][4/y] ~> 2 % 2 = 0 ~> true.

~> ("x even, y is: " + string y)[2/x][4/y]
~> "x even, y is: " + string 4
~> "x even, y is: 4"
ITT8060

A non-example

let s = 100

let isEqualTo100 x =
  match x with
  | s -> "Yes!"
  | _ -> "No."

// isEqualTo100 1 = "Yes!"
warning FS0026:
  This rule will never be matched

val isEqualTo100: x: 'a -> string

An identifier in a pattern introduces a binding — this s is a new variable, the pattern matches everything, and shadows the outside s.

The compiler notices the second case/pattern is unreachable, and infers 'a -> string as the type. Why?

The pattern _ matches everything but does not introduce any bindings.

ITT8060
let rec fact n =
  match n with
  | 0  -> 1
  | n' -> n' * fact (n' - 1)

let (x, y) = (1, 3)

let fst (x, y) = x
let snd (_, y) = y

Patterns everywhere

Factorial with match: the base case is the literal pattern 0.

Patterns also work in let bindings and in function parameters.

fact 3
~> match 3 with ...
~> (n' * fact (n' - 1))[3/n']
~> 3 * fact (3 - 1)
~> 3 * fact 2
~> ...
ITT8060

Operations on basic types

ITT8060
abs   : int -> int
(+)   : int -> int -> int
not   : bool -> bool
(&&)  : bool -> bool -> bool
(||)  : bool -> bool -> bool
float : int -> float

Operators are functions

A binary operator in parentheses is a prefix function: (+) 1 2 evaluates to 1 + 2.

\[\mathtt{false}\ \mathtt{\&\&}\ b_2 \leadsto \mathtt{false}\]

\[\mathtt{true}\ \mathtt{\&\&}\ b_2 \leadsto b_2\]

\[\dfrac{b_1 \leadsto b_1'}{b_1\ \mathtt{\&\&}\ b_2 \leadsto b_1'\ \mathtt{\&\&}\ b_2}\]

ITT8060

&& versus a function

let shortCircuit =
  false && (failwith "evaluated!")

// val shortCircuit: bool = false
> (fun x y -> x && y)
    false (failwith "evaluated!");;

System.Exception: evaluated!
Stopped due to error

Isn't b1 && b2 the same as (fun x y -> x && y) b1 b2?

No. The rule for false && b2 discards b2 without evaluating it. Function application evaluates both arguments to values and then substitutes into the body.

ITT8060

Type aliases and type inference

ITT8060
type Position = int * int

let origin : Position = (0, 0)

Type aliases

let names values, type names types

Position is an alias for int * int.

e : Position then also e : int * int but the former is often more readable.

ITT8060

Type inference by example

let mystery x =
  let (i, b) = x in
  not b, i + 1

Task: find U such that mystery : U.

mystery is a function. U = S -> T, x : S, and not b, i + 1 : T.

x matched against a pair pattern. S = S1 * S2, i : S1, b : S2.

Result is a pair: T = T1 * T2, not b : T1, i + 1 : T2.

not : bool -> bool, so T1 = bool and S2 = bool.

(+) : int -> int -> int, so T2 = int and S1 = int.

ITT8060

Putting it together

U  = S -> T
S  = S1 * S2
T  = T1 * T2
S2 = bool
T1 = bool
S1 = int
T2 = int

U = S1 * S2 -> T1 * T2
  = int * bool -> bool * int

mystery : int * bool -> bool * int
> let mystery x =
    let (i, b) = x in
    not b, i + 1;;

val mystery: int * bool -> bool * int
ITT8060

Next

Reading: Hansen & Rischel, chapters 1 and 2.

Coursework 1 is out this week.

Full prose version with runnable examples: notes.html. The script is 02-types-expressions-functions.fsx in course-materials.

ITT8060