Professor: Brad Lushman | Term: Winter 2026

Lecture 1

The course where ML doesn't mean machine learning

Untyped -calculus

Calculus - A system or method of calculation. Syntax & rules for manipulating syntax.

-calc (1934) - A calculus of functions.

Syntax (Backus-Naur form)

This is abstract syntax.

Use parentheses to disambiguate parses.

Conventions:

  1. Abstractions extend as far to the right as possible

  1. Applications associate left-to-right

Interpretation:

  • Variables: self-explanatory
  • Abstractions: : Function taking argument and returning expression .
  • Applications: result of applying function to argument .

If you wondered where you would ever use what you learned from CS245, this is the place

Semantics: Two types of variables: free & bound

Consider:

Informally, a variable is bound if it has a bound occurrence and free if it has a free occurrence.

Definition

Let be an expression. The bound variables of , , are given by

The free variables of , , are given by

Two occurrences of a variable mean the same thing if

  1. They are both free occurrences

Or

  1. They have the same binding occurrence

Can change the name of bound variables, but not free variables.

Definition

-conversion: For all , , , if .

means substitute for in .

More generally, if denotes an expression in which occurs as a sub-expression, then the if .

Consider

Computation:

Expression of the form . The rator is now a function. This is called a redex (reducible expression).

((lambda (x) M) N)

We expect evaluate , with substituted for . i.e. .

Definition

(reduction): For all , , , .

More generally, .

is a binary relation on terms.

: -reduces to in one step.

: -reduces to in steps.

: -reduces to in or more steps.

: -reduces to in or more steps.

Evaluating .

Want: Substitute for all free occurrences of in .

Definition

Substitution (naïve): . . . . .

For example:

The last example is referred to as dynamic binding. As the expression is reduced at runtime, the meaning could change.

Question

What happened here?

The free variable became bound after substitution. The binding occurrence of changed: On substitution, the last changed from the first to the .

Dynamic Binding: The meaning of variables is uncertain until runtime.

We want static binding. The meaning of variables is fixed before runtime.

Definition

Fixed substitution: . . . . We modify the last rule from before. . , is a brand new variable.

This is the correct definition.

Now

Lecture 2

Recall

Computation--reduction until you reach a normal form.

Definition

An expression is in -BF if it contains no -redices

Definition

An expression is in Weak Normal Form (WNF) if the only -redices are inside abstractions.

Consider

Two different reduction sequences.

Question

Does it matter which we take?

Theorem

(Church-Rosser): Let , , , be expressions such that and . Then there is an expression such that (up to -equivalence) and .

Corollary: -NFs are unique.

Question

Do all expressions have a -NF?

No.

Consider an infinite loop

Question

Which expressions have a -NF?

This is undecidable. It is equivalent to the Halting problem.

Consider

Order matters when infinite reductions are possible.

Reduction Strategies: Plans for choosing a redex to reduce

Applicative Order Reduction (AOR): Choose the leftmost, innermost redex.

  • Innermost not containing any other redex
  • ”Eager evaluation”, “call-by-value”

Normal Order Reduction (NOR): Choose the leftmost, outermost redex.

  • Outermost not contained in any other redex
  • ”Lazy evaluation”, “call-by-name”

For example,

Theorem

(Standardization): If an expression has a -NF, then NOR is guaranteed to reach it.

But many programming languages use applicative order, including Scheme.

-Reduction:

Consider:

So behaves exactly like .

Definition

-reduction) if . More generally, . .

Programming in the -Calculus

Shorthand for convenience:

: (real-world Programming language) -calc

For a real world expression , is our replication of in the -calc.

Booleans:

If <bool> then <true-part> else <false-part>

If (b,t,f) if b then t else f

Alternatively,

Question

Does it work?

Storage: Use lists

In Scheme, lists are based on pairs

(cons a b) creates the pair.

Nested pairs create lists. (cons a (cons b c))

Need to represent:

  • cons
  • nil- empty list
  • null? - is the list empty?
  • car - 1st component of the pair
  • cdr - 2nd component of the pair

Modelling pairs: Pair is a function that takes a selector as a parameter. If the selector is true, return the first component. If it is false, return the second component.

So

car: return the head, i.e. pass the selector true

Similarly,

Example

null? must return false when given a pair. . Pass a selector that consumes , , return false.

Lecture 3

Recall

nil: something to make null? return .

So

Numbers - consider only non-negative integers.

Easy - encode as a list of length .

Primitives: pred, succ, isZero?

Another solution is Church Numerals

Represent as the act of applying a function times to an argument .

Addition:

  • Apply times to
  • Apply times to the result

Special Case: .

Subtraction is considerably harder.

Find a function to apply times that produces .

Consider: Start with

Apply the function

times:

then take the cdr.

Multiplication: Apply -fold repetition of times. .

This is function composition.

Exponents: .

If you are okay with reversing the arguments,

Recursion: Find the length of the list

Error

is defined in terms of itself.

Question

How do we get a closed form repetition of ?

Solve the equation

Aside: Fixed points.

A fixed point of a function is a value , such that .

Example

has fixed point since .

satisfies

Need a fixed point of .

Consider:

Therefore, is a fixed point of .

Now, parameterize by , get

This expression is known as Curry’s Paradoxical Combinator (or Y combinator).

This returns the fixed point of any function.

Therefore, for any , i.e. is a fixed point of .

Any combinator (closed expression, i.e. no free variables) , such that is called a fixed point combinator.

Consider

Lecture 4

Recall:

This works under NOR, but not under AOR.

For recursion under eager evaluation, we need 3 things.

  1. Modified reduction strategy - Applicative Order Evaluation (AOE)
    1. Choose the leftmost, innermost, redex that is not within the body of an abstraction.
  1. Modified Y combinator: . Note: .
  1. Short-circuit “if-then-else”

Type Theory

Consider

produces false.

Unrestricted combinations unexpected results.

Question

Is there a way to do only things that “make sense”?

Introduce a system of types.

Type

  • Set of values an expression can have
  • Interpretation of raw data

Type system

  • Set of types set of rules for assigning types

An expression is well-typed if a type is derivable for it from the rules, else, it is ill-typed.

Strong vs. Weak Typing - How strictly are the rules enforced?

Static vs. Dynamic Typing - When are the types determined?

  • Static: At compile-time
  • Dynamic: At run-time

Monomorphic vs. Polymorphic typing

  • Monomorphic: Entities have a unique type
  • Polymorphic: Entities can have multiple types

Focus: Strong, static, monomorphic (for now) typing

The Simply Typed -Calculus

Syntax:

Types:

Primitive type: “Built-in”, e.g., int.

Constructed types: Built from other types. Constituent type type constructor

int -> bool is the type constructor for functions.

is right-to-left associative. .

Type Checking / Type Inference

Type theory Institutionistic Implicational Logic

Called the Curry-Howard Isomorphism

Rules presented as a formal inference system (CS245)

No premises - axioms. Conclusion always holds

Example

Modus Ponens:

Type rules for STC

Type Environment: Map from identifiers to types. List of name, type pairs “symbol table”. Denoted (“assumptions”) or

“look up in ” - if , .

Type Judgement: Statement of the form . “Expr has type “.

“The statement is derivable from the assumptions in “.

Variables:

Abstractions: Assume the parameter has the given type, then type the body.

Applications: Type the rator and the rand. Type of the rand must match the param type of the rator. The type of the expression is the result type of the rator.

Example

Find the type of

Lecture 5

Evaluating Type Systems

Question

How do we know these rules work?

Two theorems: Progress and preservation.

Theorem

Let be a closed, well-typed term in the STC, i.e. for some , , . Then either is a value (WNF) or there is an expression such that .

Proof:

Induction on the length of the type derivation for .

Cases:

  1. is a variable. Impossible since is closed.
  2. is an abstr. is in WNF.
  3. . Then the derivation for ends with

By induction, , are in WNF or reducible.

If or is reducible, then so is . If neither is reducible, both are values.

In that case, is some value ‘.

And this is reducible .

Progress can’t be stuck e.g. can’t derive a type for .

Theorem

(Preservation, Subject-Reduction Theorem): Let be a type environment, and be -expressions such that (i.e. reduces to by steps of and/or reduction). Suppose . THen .

Preservation: Well-typed terms continue to be well-typed after reduction.

Progress Preservation Safety

Well-typed terms either reduce forever, or reach a sensible value.

Theorem

(Strong Normalization Theorem): The set of well-typed terms in the STC is strongly normalizing - that is, infinite reductions are impossible.

STC is not Turing complete.

Intuition: Consider .

Question

What type can we give ?

If the second has some type , then the first must have some type .

There is no way and can represent the same type. no type for in this context. no type for . no type for .

Cannot type either, so would need special facilities to support recursion.

Polymorphism

Entities having multiple types.

Cardelli & Wegner (1985): Hierarchy of polymorphism

flowchart LR
a[Polymorphism]
b[Universal]
c[Ad hoc]
d[Parametric]
e[Inclusion]
f[Overloading]
g[Coercion]
a --> b
a --> c
b --> d
b --> e
c --> f
c --> g

Definition

Universal: Generally, one implementation with many types. Unbounded number of specializations. Works on unknown (not yet created) types.

Definition

Parametric: “Type parameter” (possibly implicit)

function id (x : t) : t { return x} generic programming: ML, Haskell

Definition

Inclusion: “Sub-typing” - hierarchy of types. Use a subtype anywhere that calls for a supertype. OO languages.

Definition

Ad-hoc: Generally several implementations. Bounded number of specializations. Usually only on known (existing) types.

Definition

Overloading: “Name sharing”. Functions with the same name and different types

Definition

Coercion: Types automatically converted.

System F (2nd order -Calculus)

Discovered independently by Girard (“System F”) and Reynolds (“polymorphic -calculus”). Models parametric polymorphism.

Example

Same implementation, different annotations> Idea is to make the type a parameter to the function.

, , are as before. , are type abstraction, type application. is a type variable, placeholder for types.

- quantified type, the type of a type abstraction.

is a free type variable. Its meaning depends on external context.

Now abstract over .

To apply :

Type rules:

These are the same. Need to create two new rules.

Nested Quantifiers

Say . Parameter type is . Result type is . is not a polymorphic function, it requires a polymorphic function as its argument, then returns .

Lecture 6

Abstract out the self-application:

.

So

So is a well-typed implementation of .

can be written as with type (.

Another solution

So can have type . Replace with and abstract.

So is another valid type.

Another solution

So is another valid type.

Three valid types for .

Question

What kinds of programs can we write in System F?

Question

Which of our untyped constructions actually type-check?

Booleans:

Let .

Natural #‘s - Church numerals: .

If has type , must have type (since ‘s result is passed back to ).

So

Let .

Plus -

.

Times -

.

Question

Is there a way to not pull these out of a hat?

No.

Exponents -

.

Polymorphism in is essential for this type-check.

Pairs:

If , then .

So .

Lecture 7

Recall for pairs,

So .

Lists: Can’t just nest pairs. Types won’t be the same.

Represent a list by its foldr function:

If

Then

The type .

Conclusion: You can do a lot in System F.

Theory: Progress and Preservation hold for System F.

However, System F is strongly normalizing, so it is not Turing Complete.

ML (Meta-Language)

Robin Milner, 1978

Major dialects: SML (Standard ML) Caml (Categorical Abstract Machine Language

Standard ML of New Jersey

ML is a mostly functional, statically typed, eager evaluation.

1 + 2; 

ML responds with

Declarations:

val x : int = 1; (*case-sensitive*)

ML:

val x = 1 : int
x + 3;
- val it = 4 : int

Functions

val f = fn ( x : int ) => x + 1; 
- val f = fn : int -> int *function from int -> int*

Does not permit recursive definitions.

Recursion

val rec fact = fn ( n : int ) => 
	if n = 0 then 1 else n * fact (n-1)

Shorthand

fun f ( x : int ) = x + 1
fun fact ( n : int ) = if n = 0 then 1 
	else n * fact(n-1)

FYI: Negation is ~ tilde.

Primitive types: int, bool, real, char, string.

Constructed types: functions , tuples (cartesian product) (*).

(3, true) : int * bool

val x = (4,3);
	val x = (4,3): int * int
 
#1 x; 
	val it = 4 : int
	
#2 x
	val it = 3 : int

Lists: Homogeneous

[1,2,3];
	val it = [1,2,3] : int list
 
(*empty list : nil)

Functions: All functions in ML accept exactly one parameter.

Question

How do we pass multiple parameters?

Solution 1: Pass a tuple

fun add (x : int * int) = #1 x + #2 x
	 val add = fn : int * int -> int
 
val x = (3,4);
add x (or add (3,4))

Solution 2: -calculus approach

val add = fn(x : int) => fn (y : int) x + y 
	vall add = fn : int -> int -> int
	
add 3 4

Curried, permits partial application.

Shorthand for Solution 1

fun add (x : int , y : int ) = x + y
	val add = fn : int * int -> int

This is a pattern, parameter is a pair.

For solution 2:

fun add (x : int ) (y : int) = x + y
	int -> int -> int

Question

What if a function takes no parameters.

Degenerate type unit only value is ()

fun f ( x : unit) = 5
	val f = fn : unit -> int
	
(*f(); 5)

Shorthand:

fun f() = 5
	val f = fn : unit -> int

Pattern, matches type unit.

Question

May have noticed, assigning types in System F is tricky. Wouldn’t it be nice if there was a tool to automatically convert untyped expressions to their typed equivalents?

Type inference.

Lecture 8

Recall: Assigning types in System F is tricky.

Definition

The erasure of a term in System F is defined as

The type inference problem is: given an untyped term , find a term that is well-typed in System F, such that .

Theorem

The type inference problem for System F is undecidable.

So confine ourselves to a decidable subset.

Hindley-Milner Polymorphic-Calculus

Restriction: Quantifiers cannot be nested inside , i.e. outermost only. This means no polymorphic parameters. is okay, is not.

Syntax: Identical to untyped -calculus.

Types:

We assign polytypes to top-level expressions. Abstractions may only demand that their arguments have a monotype.

E.g.

Note: Can still have polymorphic values as arguments.

is still valid, but can non longer demand polymorphism.

No type annotations types assigned purely by inference.

Type rules: Let range over monotypes, and range over polytypes.

New rules

Can quantify any variable that is not being used in an enclosing expression (i.e. not free in ).

Question

How do we do type inference?

Rules are not syntax-directed. A set of rules is syntax-directed if

  1. Exactly one rule for each element of abstract syntax.
  2. Semantics (type) of an expression is completely determined by the semantics of its pieces.

Type rules not syntax-directed choices to make, inference is harder.

ML (Polymorphic)

fun f x = x + 1;
	val f = fn : int -> int
  
fun g (x, y) = x + y;
	val g = fn : int * int -> int
 
fun h x y = x + y;
	val h = fn : int -> int -> int

ML chooses int by default for arithmetic operators.

If you want real:

fun f ( x : real ) y = x + y
fun f x ( y : real ) = x + y
fun f x y = x + y : real

Polymorphism

fun id x = x
	val id = fn : 'a -> 'a   

Pattern matching

fun append (x,y) = 
	if x = nil then y 
	else hd x :: append(tl x, y)

Better

fun append ([], y) = y | appned (x :: xs, y) = x::append(xs, y)
fun merge([], N) = n 
| merge(M, []) = M 
| merge()M as m::ms, N as n::ns) = 
if m < n then m::merge(ms, N) 
else n::merge(M, ns)

Special pattern: _ wild-card “don’t-care” pattern.

fun empty [] = true 
| empty _ = false

Split a list:

fun split [] = ([], [])
| split[a] = ([a], [])
| split (a::b::cs) = 
let val (M, N) = split cs
in (a::M, b::N)
end
fun mergesort L = 
	let val (M,N) = split L
		val M' = mergesort M
		val N' = mergesort N
	in
		merge(M, N')
end
let 
	<decl1>
	.
	.
	<decl2>
in
	<body>
end

Establishes a local environment. decl1, , decln evaluate and bound in the order in which they appear. decli is visible in declj for and in <body>.

Higher-order functions:

fun foldl fe [] = e
| foldl f e (x::xs) = foldl f (f(x,e))xs

Building our own types:

type intpair = int * int (* creates a type synonym)

Construct a new type:

datatype suit = heart | spade | diamond | club;

Binary tree

datatype 'a tree = Empty | Node of 'a * 'a tree * 'a tree
fun dfs Empty _  = false
| dfs (Node(x,L,R)) y = x = y
	orelse dfs L y
	orelse dfs R y
 
	val dfs = fn : ''a tree -> ''a -> bool

Special type variable ''a can only be instantiated with an eqtype, a type that can be compared for equality.

eqtypes are int, bool, char, unit, tuples and lists of these, data types based on these.

Not eqtypes - real, functions.

Lecture 9

Definition

Let and be types, a unifier of and . is a Most General Unifier (MGU) of and if for every unifier of and there exists a substitution such that .

If and have a unifier, then they have an MGU.

Algorithm - Robinson’s Unification Algorithm

Given types and , find an MGU if one exists

int , bool [bool / , int / ] is a unifier int, bool - no unifier int, int [int / , int / ] is a unifier, [] is a unifier.

Condition called the occurs-check.

Example

, can never represent the same type. Without the occurs-check, .

Substitution across environments:

Algorithm : takes a type environment and an expression. Returns a substitution and a type.

Variables: Look up in environment, return empty substitution. .

Abstractions: Type the body with bound to a fresh type variable, then construct the type.

Applications:

  • Type rator and rand
  • Type of rand must match parameter type of rator (unification)
  • Result is the result type of the rator

Extensions: Milner

Conditionals: if then else

  • must have type bool
  • and must have the same type
  • result is the type of and

H-M calculus - strongly normalizing. need explicit facilities for recursion. computes the fixed point of .

Name-binding

Evaluates with bound to the .

Operationally equivalent to .

Milner’s observation: The argument is guaranteed to be present. So if is polymorphic, can take advantage of the polymorphism.

Consider

But we can’t demand polymorphism in the H-M calculus. But if the argument is present

Then we could use the polymorphism

Lecture 10

(Missed the first 55 minutes of the lecture)

ML Exceptions

exception DivByZero;
fun safeDiv(_, 0) = raise DivByZero
	SafeDiv(x,y) = x div y
	
(* int x int -> int)

Handling exceptions

val quot = safeDiv(3,0) handle DivByZero => 0
	exn -> 'a

Parameterized exceptions

exception DivByZero of int *string
 
fun safeDiv(x,0) = raise DivByZero(x, "Division by Zero")
	safeDiv(x,y) = x div y
 
val quote = safeDiv(3,0) handle DivByZero(x,_) => x

Assignment is only permitted on reference variables.

val x = ref 5; (*mutable reference containing the value 5*)
	val x = ref 5 : int ref
 
val x = !x (*dereference*)
	val y = 5 : int
x := !x + 1; (* assignment - returns unit*)
 
val it = 6 : int

Also: mutable arrays, loops: while <expr> do <expr>.

I/O: print "Hello"

Real.toString
Int.toString

Real and Int are modules.

Sequencing: ( ; ; ; e) are evaluated sequentially, result is e.

use "file.sml, load a file.

Type Errors

Type mismatch (hd tl)lst

ML Error: Operator and operand don’t agree.

Operator domain: 'Z list

Operand: 'Y list -> 'Y list

In expression: hd tl

Circularity

fun f [] = []
	f (x:: xs) = xs::x

Operator domain: 'Z list * 'Z list list

Operand: 'Z list * 'Z

Can’t unify 'Z with 'Z list list in expression xs::x.

Similarly, cannot type . Type is circular (self-referential).

Can create self-referential types with datatype

fun fred x = (x, fn () => fred(x+1))

ML: Cannot unify int * unit -> 'Z with 'Z.

Solution:

datatype fredtype = Fred of int * unit -> fredtype
fun fred x = Fred(x, fn() -> fred(x+1))
	val fred = fn : int -> fredtype

Lecture 11

Skipped for interview

Lecture 12

Hiding helper functions, e.g. gcd:

local 
	fun gcd(x,y) =
in 
	fun plus r1 r2 = 
	fun times r1 r2 = 
end
(*can use local inside and outside of abstract types)

SML Module System

Structure: Collection of values, types, etc. -

structure Rational = struct
datatype rational = Rat of int * int
fun mkRat x y = Rat(x,y)
fun plus r1 r2 =
fun times r1 r2 = 
fun gcd(x,y) = 
end
val x = Rational.Rat(3,4)

Signature - the type of a structure.

signature RATIONAL = sig
	type rational
	val mkRat : int -> int -> rational
	val plus : rational -> rational -> rational
	val tmies : rational -> rational -> rational
end
structure RestrictedRational : RATIONAL = Rational

RestrictedRational.gcd is not visible.

Implementation of type rational is still visible.

Opaque structures:

structure OpaqueRational :> RATIONAL = Rational

Functor: High-level function (turn structures into other structures).

E.g.

signature COMPARABLE = sig
	eqtype element
	val leq : element * element -> bool
end
signature ORDERFIND = sig
	eqtype element
	val find : element list *element -> bool
end
functor MkOrderedFind(C : COMPARBALE) : ORDEREDFIND = struct
	type element = C.element
	fun find([], _) = false
		find(x :: xs, y) = if x = y then true
							else if C.leq(x,y) then find (xs, y)
							else false
end
structure IntPair = struct
	type element = int * int
	fun leq((x1, y1), (x2, y2)) = x1 <= x2 or else
								(x1 = x2 and also y1 <= y2)
end
structure IntPairFind = MkOrderedFind(IntPair)
IntPairFind.find(__)

Sharing declarations: putting 2 or more modules together.

Sometimes the combination only works if certain types are forced to be equal.

Modify OrderedFind to print out its findings instead of returning bool.

sig/struct for printable elements

signature PRINTABLE = sig
	eqtype element
	val toString : element -> string
end
functor MkOrderedPrintFind(structure C:COMPARABLE and P:PRINTABLE sharing
				type C.element = P.element) = struct
	type element = C.element
	fun find([], _) = ()
		find(x::xs, y) = if x = y then print(P.toString x^"\n")
				else if C.leq(x, y) then find (xs, y)
				else()
end
 
structure PrintableIntPair = struct
	type element = int * int
	fun toString(m,n) = "("^Int.toString m ^","^Int.toString n^")"
end
structure IntPairPrintFind = MkOrderedPrintFind(structure C = IntPair and P = PrintableIntPair)
IntPairPrintFind.find(_)

Existential Types

, for any type , the item has type (polymorphism)

Question

What can be said of a type like ?

For some type , the item has type . But we don’t know what is.

Consider the type

“for some type , has type

Every typeable term has type . This tells us nothing useful.

Question

But what if we combine existentials with record types?

Type Nat =

fun f(x : Nat) = 
let 
	(τ, {z, s, n}) = x
in 
	n(s(s(s z)))
end

Can use without knowing what is.

Existentials model Abstract Data Types

abstype rat = Rat of int * int
with 
	mkRat, lpus, times, num, denom, gcd
end

Let type RatType =

val x: RatType = (int * int, {mkRat = _, plus = _, denom= _, gcd = _})
let 
	(τ, {mkRat, plus, minus, num, denom}) = x
	fun equal (x : τ, y : z) = num x *denom y = num y *denom  x
in 
	equal (mkRat 12, mkRat 24)
end

Can be encoded as universals

Let be as before

Encode as

Lecture 13

Lazy Evaluation

Arguments are substituted before they are evaluated. Potential inefficiency, argument used more than once evaluated more than once.

Scheme & ML - eager

Lazy evaluation in scheme (delay and force)

(delay <expr>) : <expr> is not evaluated. Returns a promise, suspended computation.

(force <promise>) : returns value computed by the suspended computation. Result is cached once it is forced. Subsequent force s just return the cached. This avoids NOR inefficiency, but side effects only happen once.

Or there is thunking, a body of abstraction is not evaluated, and we delay computation by wrapping it in an abstraction.

(define lazy-add (lambda x y)
	(lambda () (+ x y )))
 
(define x (lazy-add 3 4))
(x) (* addition occurs here)
=> 7

Lazy Data Structures

Lazy evaluation can have infinite data structures.

Infinite lists - streams

(define ones (cons 1 (lambda () ones)))
(defnie s-car car)
(define (s-cdr x) ((cdr x)))
(define make-seq (lambda (start)
	(letrec 
		((next (lambda (n)
			(cons n 
				(lambda () (next (+ n 1)))))))
		(next start))))
(define seq (make-seq 0))
(s-car seq) => 0
(s-car (s-cdr seq)) => 1
(s-car (s-cdr (s-cdr seq))) => 2

A Lazy Language: Haskell

Statically typed, pure functional, lazy evaluation.

Lexicographic rules: Case-sensitive, variables start with lowercase letters, types and constructors start with uppercase letters.

Offside rule:

  1. Definitions all begin in the same column
  2. Multi-line definitions (lines after the 1st) are indented.
add: Int -> Int -> Int
add = \ x -> \ y -> x + y

Types: Primitive: Int, Float, Char, Bool

Constructed:

  • Int -> Bool (functions)
  • [Int] (lists)
  • (Float, Char) (tuples)

Polymorphism:

  • id:: a-> a (pattern-matching, like ML)
  • id x = x

Name-binding

sumSquares :: Int -> Int -> Int
sumSquares a b = 
	let 
		square x = 
			square x * x
	in
		square a + square b

OR

sumSquares :: Int -> Int -> Int
sumSquares a b = 
	square a + square b 
	where
	square x = x * x

Booleans: True, False, &&, ||, not

https://people.willamette.edu/~fruehr/haskell/evolution.html

3 ways to write factorial.

Pattern-match:

fact :: Int -> Int
fact 0 = 1
fact n = n * fact(n-1)

If-then-else:

fact:: Int -> Int
fact n = if n == 0 then 1
	else n * fact(n-1)

Guards:

fact:: Int -> Int
fact n
	n == 0 = 1
	otherwise = n * fact(n-1)

Lists:

  • [] empty list
  • : “cons”

Maps:

  • map::(a -> b) -> [a] -> [b]
  • map _ [] = []
  • map f (x : xs) = f x : map f xs
  • ++ = list concat{:haskell}

List comprehensions

qsort[] = []
qsort(x : xs) = 
	qsort[y | y <- xs, y < x]
	++ x : qsort[y | y <- xs, y >= x]

Type declarations:

Type - creates synonyms (like ML)

type String = [Char]

Data datatype in ML

data Tree a = Empty | Node(a, Tree a , Tree a)

OR

data Tree a = Empty | Node a (Tree a) (Tree a)

Inorder:

  • inorder:: Tree a -> [a]
  • inorder Empty = []
  • inorder (Node(a,b,c)) = inorder b ++ [a] ++ inorder c

Lazy Evaluation

Arguments to functions not evaluated until they are used. Arguments used more than once, result is cached (“call by need”).

Data structures realized only as much as needed for computation to proceed.

can express algorithms as operations on large data structures.

Compute

Solution:

sum [] = 0
sum(x: xs) = x + sum xs
sumSquares:: Int -> Int
sumSquare n = sum(map sq [1..n])
sumSquare 4 = sum(map sq [1..4])
	= sum(map sq 1:[2..4])
	= sum(sq 1 : map sq[2..4])
	= sq1 + sum(map sq [2..4])
	= 1 + sum(map sq[2..4])
	= 1 + sum(map sq 2 : [3..4])
	= 1 + sum(sq 2 : map sq[3..4])
	= 1 + (sq2 + sum(map sq[3..4]))
	= 1 + (4 + sum(map sq[3..4]))
	= 1 + (4 + (9 + (16 + 0)))
	= 30

Notice: Entire list if never constructed, no additional space overhead.

Extra unresolved computation due to not being tail recursive.

fact:: Int -> Int
fact n = fact' n 1
	where 
		fact' 0 a = a
		fact' n a = fact' (n-1) (n*a)
fact 5 = fact' 5 1
	= fact' (5 - 1) (5 * 1)
	= fact' 4 (5 * 1)
	= fact' (4-1) (4 * (5 * 1))
	= fact' 3 (4 * (5 * 1))
	= fact' (3-1) (3 * (4 * (5 * 1)))
	= fact' 2 (3 * (4 * (5 * 1)))

Suspended computation grows, tail recursive doesn’t help with lazy code. this is not constant space.