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:
- Abstractions extend as far to the right as possible
- 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
- They are both free occurrences
Or
- 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:
consnil- empty listnull?- is the list empty?car- 1st component of the paircdr- 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.
- Modified reduction strategy - Applicative Order Evaluation (AOE)
- Choose the leftmost, innermost, redex that is not within the body of an abstraction.
- Modified Y combinator: . Note: .
- 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 -> boolis 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:
- is a variable. Impossible since is closed.
- is an abstr. is in WNF.
- . 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 : intFunctions
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 : intLists: 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 4Curried, permits partial application.
Shorthand for Solution 1
fun add (x : int , y : int ) = x + y
val add = fn : int * int -> intThis is a pattern, parameter is a pair.
For solution 2:
fun add (x : int ) (y : int) = x + y
int -> int -> intQuestion
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 -> intPattern, 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
- Exactly one rule for each element of abstract syntax.
- 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 -> intML 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 : realPolymorphism
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 _ = falseSplit a list:
fun split [] = ([], [])
| split[a] = ([a], [])
| split (a::b::cs) =
let val (M, N) = split cs
in (a::M, b::N)
endfun mergesort L =
let val (M,N) = split L
val M' = mergesort M
val N' = mergesort N
in
merge(M, N')
endlet
<decl1>
.
.
<decl2>
in
<body>
endEstablishes 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))xsBuilding 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 treefun 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 -> boolSpecial 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 -> 'aParameterized 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,_) => xAssignment 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 : intAlso: mutable arrays, loops: while <expr> do <expr>.
I/O: print "Hello"
Real.toString
Int.toStringReal 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::xOperator 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 -> fredtypefun fred x = Fred(x, fn() -> fred(x+1))
val fred = fn : int -> fredtypeLecture 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) =
endval 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 = RationalRestrictedRational.gcd is not visible.
Implementation of type rational is still visible.
Opaque structures:
structure OpaqueRational :> RATIONAL = RationalFunctor: 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
endstructure 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)))
endCan use without knowing what is.
Existentials model Abstract Data Types
abstype rat = Rat of int * int
with
mkRat, lpus, times, num, denom, gcd
endLet 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)
endCan 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)
=> 7Lazy 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))) => 2A 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:
- Definitions all begin in the same column
- Multi-line definitions (lines after the 1st) are indented.
add: Int -> Int -> Int
add = \ x -> \ y -> x + yTypes: 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 bOR
sumSquares :: Int -> Int -> Int
sumSquares a b =
square a + square b
where
square x = x * xBooleans: 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 xssumSquares:: 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)))
= 30Notice: 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.