From logic rules to programming constructs
What logical connectives do you know?
\top | \bot | \wedge | \vee | \rightarrow |
---|---|---|---|---|
a \wedge b | a \vee b | a \rightarrow b | ||
truth | falsehood | conjunction | disjunction | implication |
“trivial” | “impossible” | a and b | a or b | a gives b |
shouldn’t get | got both | got at least one | given a, we get b |
How can we define them? Think in terms of derivation trees:
\frac{\begin{matrix} \frac{\begin{matrix} \frac{\,}{\text{a premise}} & \frac{\,}{\text{another premise}} \end{matrix}}{\text{some fact}} & \frac{\frac{\,}{\text{this we have by default}}}{\text{another fact}} \end{matrix}}{\text{final conclusion}}
Define by providing rules for using the connectives: for example, a rule \frac{\begin{matrix} a & b \end{matrix}}{c} matches parts of the tree that have two premises, represented by variables a and b, and have any conclusion, represented by variable c.
Try to use only the connective you define in its definition.
Introduction rules say how to produce a connective.
Elimination rules say how to use it.
Text in parentheses is comments. Letters are variables: stand for anything.
Connective | Introduction Rules | Elimination Rules |
---|---|---|
\top | \frac{}{\top} | doesn’t have |
\bot | doesn’t have | \frac{\bot}{a} (i.e. anything) |
\wedge | \frac{a \; b}{a \wedge b} | \frac{a \wedge b}{a} (take first) \frac{a \wedge b}{b} (take second) |
\vee | \frac{a}{a \vee b} (put first) \frac{b}{a \vee b} (put second) | \frac{{a \vee b} \; {{{\frac{\,}{a} \tiny{x}} \atop {\text{\textbar}}} \atop {c}} (\text{consider }a) \; {{{\frac{\,}{b} \tiny{y}} \atop {\text{\textbar}}} \atop {c}} (\text{consider }b)}{c} using x, y |
\rightarrow | \frac{{{\frac{\,}{a} \tiny{x}} \atop {\text{\textbar}}} \atop {b}}{a \rightarrow b} using x | \frac{{a \rightarrow b} \; a}{b} |
Notations
{{{\frac{\,}{a} \tiny{x}} \atop {\text{\textbar}}} \atop {b}} \text{, \ \ or \ \ } {{{\frac{\,}{a} \tiny{x}} \atop {\text{\textbar}}} \atop {c}}
match any subtree that derives b (or c) and can use a (by assumption \frac{\,}{a} \tiny{x}) although otherwise a might not be warranted. For example:
\frac{\frac{\frac{\frac{\frac{\,}{\text{sunny}} \small{x}}{\text{go outdoor}}}{\text{playing}}}{\text{happy}}}{\text{sunny} \rightarrow \text{happy}} \small{\text{ using } x}
Such assumption can only be used in the matched subtree! But it can be used several times, e.g. if someone’s mood is more difficult to influence:
\frac{\frac{\begin{matrix} \frac{\frac{\frac{\,}{\text{sunny}} \small{x}}{\text{go outdoor}}}{\text{playing}} & \frac{\begin{matrix} \frac{\,}{\text{sunny}} \small{x} & \frac{\frac{\,}{\text{sunny}} \small{x}}{\text{go outdoor}} \end{matrix}}{\text{nice view}} \end{matrix}}{\text{happy}}}{\text{sunny} \rightarrow \text{happy}} \small{\text{ using } x}
Elimination rule for disjunction represents reasoning by cases.
How can we use the fact that it is sunny\veecloudy (but not rainy)?
\frac{\begin{matrix} \frac{\,}{\text{sunny} \vee \text{cloudy}} \tiny{\text{ forecast}} & \frac{\frac{\,}{\text{sunny}} \tiny{x}}{\text{no-umbrella}} & \frac{\frac{\,}{\text{cloudy}} \tiny{y}}{\text{no-umbrella}} \end{matrix}}{\text{no-umbrella}} \small{\text{ using } x, y}
We know that it will be sunny or cloudy, by watching weather forecast. If it will be sunny, we won’t need an umbrella. If it will be cloudy, we won’t need an umbrella. Therefore, won’t need an umbrella.We need one more kind of rules to do serious math: reasoning by induction (it is somewhat similar to reasoning by cases). Example rule for induction on natural numbers:
\frac{\begin{matrix} p (0) & {{{\frac{\,}{p(x)} \tiny{x}} \atop {\text{\textbar}}} \atop {p(x+1)}} \end{matrix}}{p (n)} \text{ by induction, using } x
So we get any p for any natural number n, provided we can get it for 0, and using it for x we can derive it for the successor x + 1, where x is a unique variable (we cannot substitute for it some particular number, because we write “using x” on the side).
Logic | Type | Expression | Introduction Rules | Elimination Rules |
---|---|---|---|---|
\top | unit |
() |
\frac{\;}{\texttt{()} : \texttt{unit}} |
Writing out expressions and types repetitively is tedious: we need
definitions. Definitions for types are written:
type ty =
some type.
Writing A (
s) : A of
a| B of
b in the table was cheating.
Usually we have to define the type and then use it, e.g. using
int
for a and
string
for b:
type int_string_choice = A of int | B of string
allows us to write A (
s) : int_string_choice
.
Without the type definition, it is difficult to know what other variants there are when one infers (i.e. “guesses”, computes) the type!
In OCaml we can write `A(s) : [`A of a | `B of b]
.
With “`
” variants, OCaml does guess what other variants
are. These types are fun, but we will not use them in future
lectures.
Tuple elements don’t need labels because we always know at which position a tuple element stands. But having labels makes code more clear, so we can define a record type:
type int_string_record = {a: int; b: string}
and create its values: {a = 7; b = "Mary"}
. * We access
the fields of records using the dot notation:
{a=7; b="Mary"}.b = "Mary"
.Recursive expression {\texttt{rec}} \; x \; {\texttt{=}} \; e in
the table was cheating: rec
(usually called
fix
) cannot appear alone in OCaml! It must be part of a
definition.
Definitions for expressions are introduced by rules a bit more complex than these:
\frac{\begin{matrix} e_{1} : a & {{{\frac{\,}{x : a} \tiny{x}} \atop {\text{\textbar}}} \atop {e_2 : b}} \end{matrix}}{{\texttt{let}} \; x \; {\texttt{=}} \; e_{1} \; {\texttt{in}} \; e_{2} : b}
(note that this rule is the same as introducing and eliminating \rightarrow), and:
\frac{\begin{matrix} {{{\frac{\,}{x : a} \tiny{x}} \atop {\text{\textbar}}} \atop {e_1 : a}} & {{{\frac{\,}{x : a} \tiny{x}} \atop {\text{\textbar}}} \atop {e_2 : b}} \end{matrix}}{{\texttt{let rec}} \; x \; {\texttt{=}} e_{1} \; {\texttt{in}} \; e_{2} \: b}
We will cover what is missing in above rules when we will talk about
polymorphism.* Type definitions we have seen above are
global: they need to be at the top-level, not nested in
expressions, and they extend from the point they occur till the end of
the source file or interactive session. *
let
-in
definitions for expressions: {\texttt{let}} \; x \; {\texttt{=}} \; e_{1} \;
{\texttt{in}} \; e_{2} are local, x is only visible in e_{2}. But let
definitions are
global: placing {\texttt{let}} \; x
\; {\texttt{=}} \; e_{1} at the top-level makes x visible from after e_{1} till the end of the source file or
interactive session. * In the interactive session, we mark an end of a
top-level “sentence” by ;; – it is unnecessary in source files. *
Operators like +, *, <, =, are names of functions. Just like other
names, you can use operator names for your own functions:
let (+:) a b = String.concat “” [a; b];;Special way of defining”Alpha” +: “Beta”;;but normal way of using operators. * Operators in OCaml are not overloaded. It means, that every type needs its own set of operators. For example, +, , / work for intigers, while +., ., /. work for floating point numbers. Exception: comparisons <, =, etc. work for all values other than functions.
Exercises from Think OCaml. How to Think Like a Computer Scientist by Nicholas Monje and Allen Downey.
Assume that we execute the following assignment statements:
let width = 17;;let height = 12.0;;let delimiter = ‘.’;;
For each of the following expressions, write the value of the expression and the type (of the value of the expression), or the resulting type error.
Practice using the OCaml interpreter as a calculator:
The volume of a sphere with radius r is \frac{4}{3} \pi r^3. What is the volume of a sphere with radius 5?
Hint: 392.6 is wrong!
Suppose the cover price of a book is $24.95, but bookstores get a 40% discount. Shipping costs $3 for the first copy and 75 cents for each additional copy. What is the total wholesale cost for 60 copies?
If I leave my house at 6:52 am and run 1 mile at an easy pace (8:15 per mile), then 3 miles at tempo (7:12 per mile) and 1 mile at easy pace again, what time do I get home for breakfast?
You’ve probably heard of the fibonacci numbers before, but in case you haven’t, they’re defined by the following recursive relationship:
\left\lbrace\begin{matrix} f (0) & = & 0 & \\\\\\ f (1) & = & 1 & \\\\\\ f (n + 1) & = & f (n) + f (n - 1) & \text{for } n = 2, 3, \ldots \end{matrix}\right.
Write a recursive function to calculate these numbers.
A palindrome is a word that is spelled the same backward and forward, like “noon” and “redivider”. Recursively, a word is a palindrome if the first and last letters are the same and the middle is a palindrome.
The following are functions that take a string argument and return the first, last, and middle letters:
let firstchar word = word.[0];;let lastchar word = let len = String.length word - 1 in word.[len];;let middle word = let len = String.length word - 2 in String.sub word 1 len;;
The greatest common divisor (GCD) of a and b is the largest number that divides both of them with no remainder.
One way to find the GCD of two numbers is Euclid’s algorithm, which is based on the observation that if r is the remainder when a is divided by b, then \gcd (a, b) = \gcd (b, r). As a base case, we can consider \gcd (a, 0) = a.
Write a function called gcd that takes parameters a and b and returns their greatest common divisor.
If you need help, see http://en.wikipedia.org/wiki/Euclidean_algorithm.
Algebraic Data Types and some curious analogies
For a refresher, let’s try to use the rules we introduced last time
on some simple examples. Starting with fun x -> x
. [?] will mean “dunno yet”.
\begin{matrix} & \frac{[?]}{{\texttt{fun x -> x}} : [?]} & \text{use } \rightarrow \text{ introduction:}\\\\\\ & \frac{\frac{\,}{{\texttt{x}} : a} \tiny{x}}{{\texttt{fun x -> x}} : [?] \rightarrow [?]} & \frac{\,}{{\texttt{x}} : a} \tiny{x} \text{ matches with } {{{\frac{\,}{x : a} \tiny{x}} \atop {\text{\textbar}}} \atop {e : b}} \text{ since } e = {\texttt{x}}\\\\\\ & \frac{\frac{\,}{{\texttt{x}} : a} \tiny{x}}{{\texttt{fun x -> x}} : a \rightarrow a} & \text{since } b = a \text{ because } x : a \text{ matched with } e : b \end{matrix}
Because a is arbitrary, OCaml puts a
type variable 'a
for it:
fun x -> x;;
# fun> - : 'a -> 'a = <
Let’s try fun x -> x+1
, which is the same as
fun x -> ((+) x) 1
(try it with OCaml/F#!). [? \alpha] will mean “dunno yet, but the same
as in other places with [?
\alpha]”.
\begin{matrix} & \frac{[?]}{{\texttt{fun x -> ((+) x) 1}} : [?]} & \text{use } \rightarrow \text{ introduction:}\\\\\\ & \frac{\frac{[?]}{{\texttt{((+) x) 1}} : [? \alpha]}}{{\texttt{fun x -> ((+) x) 1}} : [?] \rightarrow [? \alpha]} & \text{use } \rightarrow \text{ elimination:}\\\\\\ & \frac{\frac{\begin{matrix} \frac{[?]}{{\texttt{(+) x}} : [? \beta] \rightarrow [? \alpha]} & \frac{[?]}{{\texttt{1}} : [? \beta]} \end{matrix}}{{\texttt{((+) x) 1}} : [? \alpha]}}{{\texttt{fun x -> ((+) x) 1}} : [?] \rightarrow [? \alpha]} & \text{we know that {\texttt{1}}} : {\texttt{int}}\\\\\\ & \frac{\frac{\begin{matrix} \frac{[?]}{{\texttt{(+) x}} : {\texttt{int}} \rightarrow [? \alpha]} & \frac{\,}{{\texttt{1}} : {\texttt{int}}} \tiny{\text{(constant)}} \end{matrix}}{{\texttt{((+) x) 1}} : [? \alpha]}}{{\texttt{fun x -> ((+) x) 1}} : [?] \rightarrow [? \alpha]} & \text{application again:}\\\\\\ & \frac{\frac{\begin{matrix} \frac{\begin{matrix} \frac{[?]}{{\texttt{(+)}} : [? \gamma] \rightarrow {\texttt{int}} \rightarrow [? \alpha]} & \frac{[?]}{{\texttt{x}} : [? \gamma]} \end{matrix}}{{\texttt{(+) x}} : {\texttt{int}} \rightarrow [? \alpha]} & \frac{\,}{{\texttt{1}} : {\texttt{int}}} \tiny{\text{(constant)}} \end{matrix}}{{\texttt{((+) x) 1}} : [? \alpha]}}{{\texttt{fun x -> ((+) x) 1}} : [?] \rightarrow [? \alpha]} & \text{it's our {\texttt{x}}!}\\\\\\ & \frac{\frac{\begin{matrix} \frac{\begin{matrix} \frac{[?]}{{\texttt{(+)}} : [? \gamma] \rightarrow {\texttt{int}} \rightarrow [? \alpha]} & \frac{\,}{{\texttt{x}} : [? \gamma]} \tiny{{\texttt{x}}} \end{matrix}}{{\texttt{(+) x}} : {\texttt{int}} \rightarrow [? \alpha]} & \frac{\,}{{\texttt{1}} : {\texttt{int}}} \tiny{\text{(constant)}} \end{matrix}}{{\texttt{((+) x) 1}} : [? \alpha]}}{{\texttt{fun x -> ((+) x) 1}} : [? \gamma] \rightarrow [? \alpha]} & \text{but {\texttt{(+)}}} : {\texttt{int}} \rightarrow {\texttt{int}} \rightarrow {\texttt{int}}\\\\\\ & \frac{\frac{\begin{matrix} \frac{\begin{matrix} \frac{\,}{{\texttt{(+)}} : {\texttt{int}} \rightarrow {\texttt{int}} \rightarrow {\texttt{int}}} \tiny{\text{(constant)}} & \frac{\,}{{\texttt{x}} : {\texttt{int}}} \tiny{{\texttt{x}}} \end{matrix}}{{\texttt{(+) x}} : {\texttt{int}} \rightarrow {\texttt{int}}} & \frac{\,}{{\texttt{1}} : {\texttt{int}}} \tiny{\text{(constant)}} \end{matrix}}{{\texttt{((+) x) 1}} : {\texttt{int}}}}{\text{{\texttt{fun x -> ((+) x) 1}}} : {\texttt{int}} \rightarrow {\texttt{int}}} & \end{matrix}
When there are several arrows “on the same depth” in a function type, it means that the function returns a function: e.g. {\texttt{(+)}} : {\texttt{int}} \rightarrow {\texttt{int}} \rightarrow {\texttt{int}} is just a shorthand for {\texttt{(+)}} : {\texttt{int}} \rightarrow \left( {\texttt{int}} \rightarrow {\texttt{int}} \right). It is very different from
{\texttt{fun f -> (f 1) + 1}} : \left( {\texttt{int}} \rightarrow {\texttt{int}} \right) \rightarrow {\texttt{int}}
For addition, instead of (fun x -> x+1)
we can write
((+) 1)
. What expanded form does ((+) 1)
correspond to exactly (computationally)?
We will get used to functions returning functions when learning about the lambda calculus.
unit
type, variant types
like:type int_string_choice = A of int | B of string
and also tuple types, record types, and type definitions. * Variants
don’t have to have arguments: instead of A of unit
just use
A
. * In OCaml, variants take multiple arguments rather than
taking tuples as arguments: A of int * string
is different
thanA of (int * string)
. But it’s not important unless you
get bitten by it. * Type definitions can be recursive!
type int_list = Empty | Cons of int * int_list
Let’s see what we have in int_list
:Empty
,
Cons (5, Cons (7, Cons (13, Empty)))
, etc. * Type
bool
can be seen as type bool = true | false
,
type int
can be seen as a very large
type int = 0 | -1 | 1 | -2 | 2 | …
* Type definitions can
be parametric with respect to types of their components (more
on this in lecture about polymorphism), for example a list elements of
arbitrary type:
type 'elem list = Empty | Cons of 'elem * 'elem list
'a
, 'b
, 'c
, 'd
…list<'elem>
. OCaml syntax mimics English, silly
example: type 'white_color dog = Dog of 'white_color
type ('a, 'b) choice = Left of 'a | Right of 'b
type choice<'a,'b> = Left of 'a | Right of 'b
data Choice a b = Left a | Right b
type my_bool = True | False
Only constructors and module names can start with capital letter. *
Modules are “shelves” with values. For example,
List
has operations on lists, like List.map
and List.filter
. * Did I mention that we can use
record.field
to access a field? *
fun x y -> e
stands for
fun x -> fun y -> e
, etc. – and of
course,fun x -> fun y -> e
parses as
fun x -> (fun y -> e)
*
function A x -> e1 | B y -> e2
stands for
fun p -> match p with A x -> e1 | B y -> e2
,
etc. * the general form is: function *PATTERN-MATCHING*
stands forfun v -> match v with *PATTERN-MATCHING*
* let f ARGS = e
is a shorthand for
let f = fun ARGS -> e
fst
and snd
as
means to access elements of a pair. But what about bigger tuples? The
“basic” way of accessing any tuple reuses the match
construct. Functions fst
and snd
can easily be
defined!let fst = fun p -> match p with (a, b) -> a
let snd = fun p -> match p with (a, b) -> b
type person = {name: string; surname: string; age: int}
match {name="Walker"; surname="Johnnie"; age=207}
with {name=n; surname=sn; age=a} -> "Hi "^sn^"!"
match
expressions are
called patterns.match Some (5, 7) with None -> "sum: nothing"
Some (x, y) -> "sum: " ^ string_of_int (x+y) |
match f x with v ->
… is the same as
let v = f x in
…_
(which is not a variable!)let fst (a,_) = a
let snd (_,b) = b
when
,
so linearity is not really a problem!match p with (x, y) when x = y -> "diag" | _ -> "off-diag"
let compare a b = match a, b with
when x < y -> -1
| (x, y) when x = y -> 0
| (x, y) 1 | _ ->
type month =
| Jan | Feb | Mar | Apr | May | Jun
| Jul | Aug | Sep | Oct | Nov | Dectype weekday = Mon | Tue | Wed | Thu | Fri | Sat | Sun
type date =
int; month: month; day: int; weekday: weekday}
{year: let day =
2012; month = Feb; day = 14; weekday = Wed};;
{year = match day with
"Weekend!"
| {weekday = Sat | Sun} -> "Work day" | _ ->
(pattern **as** v)
to name a nested
pattern:match day with
as** wday)}
| {weekday = (Mon | Tue | Wed | Thu | Fri **when not (day.month = Dec && day.day = 24) ->
Some (work (get_plan wday))
None | _ ->
Let’s do a peculiar translation: take a data type and replace | with +, * with \times, treating record types as tuple types (i.e. erasing field names and translationg ; as \times).
There is a special type for which we cannot build a value:
type void
(yes, it is its definition, no = something
part).
Translate it as 0.
Translate the unit
type as 1. Since variants without arguments behave as
variants of unit
, translate them as 1 as well. Translate bool
as
2.
Translate int
, string
, float
,
type parameters and other types of interest as variables. Translate
defined types by their translations (substituting variables if
necessary).
Give name to the type being defined (denoting a function of the variables introduced). Now interpret the result as ordinary numeric polynomial! (Or “rational function” if it is recursively defined.)
Let’s have fun with it.
type date = {year: int; month: int; day: int}
D = xxx = x^3
type 'a option = None | Some of 'a (* built-in type *)
O = 1 + x
type 'a my_list = Empty | Cons of 'a * 'a my_list
L = 1 + xL
type btree = Tip | Node of int * btree * btree
T = 1 + xTT = 1 + xT^2
When translations of two types are equal according to laws of high-school algebra, the types are isomorphic, that is, there exist 1-to-1 functions from one type to the other.
Let’s play with the type of binary trees:
\begin{matrix} T & = & 1 + xT^2 = 1 + xT + x^2 T^3 = 1 + x + x^2 T^2 + x^2 T^3 =\\\\\\ & = & 1 + x + x^2 T^2 (1 + T) = 1 + x (1 + xT^2 (1 + T)) \end{matrix}
Now let’s translate the resulting type:
type repr =
int * (int * btree * btree * btree option) option) option (
Try to find the isomorphism functions iso1
and
iso2
val iso1 : btree -> repr
val iso2 : repr -> btree
i.e. functions such that for all trees t
,
iso2 (iso1 t) = t
, and for all representations
r
, iso1 (iso2 r) = r
.
My first failed attempt:
let iso1 (t : btree) : repr =
# match t with
None
| Tip -> Some (x, None)
| Node (x, Tip, Tip) ->
| Node (x, Node (y, t1, t2), Tip) ->Some (x, Some (y, t1, t2, None))
| Node (x, Node (y, t1, t2), t3) ->Some (x, Some (y, t1, t2, Some t3));;
32-261: […]
Characters 8: this pattern-matching is not exhaustive.
Warning of a value that is not matched:
Here is an example Node (_, Tip, Node (_, _, _))
I forgot about one case. It seems difficult to guess the solution, have you found it on your try?
Let’s divide the task into smaller steps corresponding to selected intermediate points in the transformation of the polynomial:
type ('a, 'b) choice = Left of 'a | Right of 'b
type interm1 =
int * btree, int * int * btree * btree * btree) choice)
((option
type interm2 =
int, int * int * btree * btree * btree option) choice)
((option
let step1r (t : btree) : interm1 =
match t with
None
| Tip -> Some (Left (x, t1))
| Node (x, t1, Tip) ->
| Node (x, t1, Node (y, t2, t3)) ->Some (Right (x, y, t1, t2, t3))
let step2r (r : interm1) : interm2 =
match r with
None -> None
| Some (Left (x, Tip)) -> Some (Left x)
| Some (Left (x, Node (y, t1, t2))) ->
| Some (Right (x, y, t1, t2, None))
Some (Right (x, y, t1, t2, t3)) ->
| Some (Right (x, y, t1, t2, Some t3))
let step3r (r : interm2) : repr =
match r with
None -> None
| Some (Left x) -> Some (x, None)
| Some (Right (x, y, t1, t2, t3opt)) ->
| Some (x, Some (y, t1, t2, t3opt))
let iso1 (t : btree) : repr =
step3r (step2r (step1r t))
Define step1l
, step2l
, step3l
,
and iso2
. Hint: now it’s trivial!
Take-home lessons:
Of course, you would say, the pompous title is wrong, we will differentiate the translated polynomials. But what sense does it make?
It turns out, that taking the partial derivative of a polynomial resulting from translating a data type, gives us, when translated back, a type representing how to change one occurrence of a value of type corresponding to the variable with respect to which we computed the partial derivative.
Take the “date” example:
type date = {year: int; month: int; day: int}
\begin{matrix} D & = & xxx = x^3\\\\\\ \frac{\partial D}{\partial x} & = & 3 x^2 = xx + xx + xx \end{matrix}
(we could have left it at 3 xx as well). Now we construct the type:
type date_deriv =
of int * int | Month of int * int | Day of int * int Year
Now we need to introduce and use (“eliminate”) the type
date_deriv
.
let date_deriv {year=y; month=m; day=d} =
[Year (m, d); Month (y, d); Day (y, m)]
let date_integr n = function
| Year (m, d) -> {year=n; month=m; day=d}
| Month (y, d) -> {year=y; month=n; day=d}
| Day (y, m) -> {year=y; month=m, day=n}
;;List.map (date_integr 7)
2012; month=2; day=14}) (date_deriv {year=
Let’s do now the more difficult case of binary trees:
type btree = Tip | Node of int * btree * btree
\begin{matrix} T & = & 1 + xT^2\\\\\\ \frac{\partial T}{\partial x} & = & 0 + T^2 + 2 xT \frac{\partial T}{\partial x} = TT + 2 xT \frac{\partial T}{\partial x} \end{matrix}
(again, we could expand further into \frac{\partial T}{\partial x} = TT + xT \frac{\partial T}{\partial x} + xT \frac{\partial T}{\partial x}).
Instead of translating 2 as
bool
, we will introduce new type for clarity:
type btree_dir = LeftBranch | RightBranch
type btree_deriv =
of btree * btree
| Here of btree_dir * int * btree * btree_deriv | Below
(You might someday hear about zippers – they are “inverted” w.r.t. our type, in zippers the hole comes first.)
Write a function that takes a number and a btree_deriv
,
and builds a btree
by putting the number into the “hole” in
btree_deriv
.
Solution:
let rec btree_integr n =
| Here (ltree, rtree) -> Node (n, ltree, rtree)
| Below (LeftBranch, m, rtree) ->
Node (m, btree_integr n ltree, rtree)
| Below (RightBranch, m, ltree) -> Node (m, ltree, btree_integr n rtree)
Write a function btree_deriv_at
that takes a predicate
over integers (i.e. a function f: int -> bool
), and a
btree
, and builds a btree_deriv
whose “hole”
is in the first position for which the predicate returns true. It should
actually return a btree_deriv option
, with
None
in case the predicate does not hold for any node.
This homework is due for the class after the Computation class, i.e. for (before) the Functions class.
Type inference example derivation
\frac{[?]}{{\texttt{fun x -> ((+) x) 1}} : [?]}
\text{use } \rightarrow \text{ introduction:}
\frac{\frac{[?]}{{\texttt{((+) x) 1}} : [? \alpha]}}{{\texttt{fun x -> ((+) x) 1}} : [?] \rightarrow [? \alpha]}
\text{use } \rightarrow \text{ elimination:}
\frac{\frac{\begin{matrix} \frac{[?]}{{\texttt{(+) x}} : [? \beta] \rightarrow [? \alpha]} & \frac{[?]}{{\texttt{1}} : [? \beta]} \end{matrix}}{{\texttt{((+) x) 1}} : [? \alpha]}}{{\texttt{fun x -> ((+) x) 1}} : [?] \rightarrow [? \alpha]}
\text{we know that {\texttt{1}}} : {\texttt{int}}
\frac{\frac{\begin{matrix} \frac{[?]}{{\texttt{(+) x}} : {\texttt{int}} \rightarrow [? \alpha]} & \frac{\,}{{\texttt{1}} : {\texttt{int}}} \tiny{\text{(constant)}} \end{matrix}}{{\texttt{((+) x) 1}} : [? \alpha]}}{{\texttt{fun x -> ((+) x) 1}} : [?] \rightarrow [? \alpha]}
\text{application again:}
\frac{\frac{\begin{matrix} \frac{\begin{matrix} \frac{[?]}{{\texttt{(+)}} : [? \gamma] \rightarrow {\texttt{int}} \rightarrow [? \alpha]} & \frac{[?]}{{\texttt{x}} : [? \gamma]} \end{matrix}}{{\texttt{(+) x}} : {\texttt{int}} \rightarrow [? \alpha]} & \frac{\,}{{\texttt{1}} : {\texttt{int}}} \tiny{\text{(constant)}} \end{matrix}}{{\texttt{((+) x) 1}} : [? \alpha]}}{{\texttt{fun x -> ((+) x) 1}} : [?] \rightarrow [? \alpha]}
\text{it's our {\texttt{x}}!}
\frac{\frac{\begin{matrix} \frac{\begin{matrix} \frac{[?]}{{\texttt{(+)}} : [? \gamma] \rightarrow {\texttt{int}} \rightarrow [? \alpha]} & \frac{\,}{{\texttt{x}} : [? \gamma]} {\texttt{x}} \end{matrix}}{{\texttt{(+) x}} : {\texttt{int}} \rightarrow [? \alpha]} & \frac{\,}{{\texttt{1}} : {\texttt{int}}} \tiny{\text{(constant)}} \end{matrix}}{{\texttt{((+) x) 1}} : [? \alpha]}}{{\texttt{fun x -> ((+) x) 1}} : [? \gamma] \rightarrow [? \alpha]}
\text{but {\texttt{(+)}}} : {\texttt{int}} \rightarrow {\texttt{int}} \rightarrow {\texttt{int}}
\frac{\frac{\begin{matrix} \frac{\begin{matrix} \frac{\,}{{\texttt{(+)}} : {\texttt{int}} \rightarrow {\texttt{int}} \rightarrow {\texttt{int}}} \tiny{\text{(constant)}} & \frac{\,}{{\texttt{x}} : {\texttt{int}}} {\texttt{x}} \end{matrix}}{{\texttt{(+) x}} : {\texttt{int}} \rightarrow {\texttt{int}}} & \frac{\,}{{\texttt{1}} : {\texttt{int}}} \tiny{\text{(constant)}} \end{matrix}}{{\texttt{((+) x) 1}} : {\texttt{int}}}}{\text{{\texttt{fun x -> ((+) x) 1}}} : {\texttt{int}} \rightarrow {\texttt{int}}}
Exercise 1.
Due to Yaron Minsky.
Consider a datatype to store internet connection information. The
time when_initiated
marks the start of connecting and is
not needed after the connection is established (it is only used to
decide whether to give up trying to connect). The ping information is
available for established connection but not straight away.
type connectionstate = | Connecting | Connected | Disconnectedtype
connectioninfo = { state : connectionstate; server : Inetaddr.t;
lastpingtime : Time.t option; lastpingid : int option; sessionid :
string option; wheninitiated : Time.t option; whendisconnected : Time.t
option;}
(The types Time.t and Inetaddr.t come from the library Core
used where Yaron Minsky works. You can replace them with
float
and Unix.inet_addr. Load the Unix library in the
interactive toplevel by #load "unix.cma";;
.) Rewrite the
type definitions so that the datatype will contain only reasonable
combinations of information.
Exercise 2.
In OCaml, functions can have named arguments, and also default arguments (parameters, possibly with default values, which can be omitted when providing arguments). The names of arguments are called labels. The labels can be different from the names of the argument values:
let f \simmeaningfulname:n = n+1let = f \simmeaningfulname:5We do not need the result so we ignore it.
When the label and value names are the same, the syntax is shorter:
let g \simpos \simlen = StringLabels.sub
“0123456789abcdefghijklmnopqrstuvwxyz” \simpos \simlenlet () =A nicer way to mark
computations that do not produce a result (return unit
).
let pos = Random.int 26 in let len = Random.int 10 in printstring (g
\simpos \simlen)
When some function arguments are optional, the function has to take non-optional arguments after the last optional argument. When the optional parameters have default values:
let h ?(len=1) pos = g \simpos \simlenlet () = printstring (h 10)
Optional arguments are implemented as parameters of an option type. This allows us to check whether the argument was actually provided:
let foo ?bar n = match bar with | None -> “Argument =” stringofint n | Some m -> “Sum =” stringofint (m + n);;foo 5;;foo \simbar:5 7;;
We can also provide the option value directly:
let bar = if Random.int 10 < 5 then None else Some 7 infoo ?bar 7;;
#load "graphics.cma";;
. Use
“functions” invalid_arg
, Graphics.open_graph
and Graphics.draw_rect
.Exercise 3.
From last year’s exam.
(int -> int) -> bool
'a option -> 'a list
Exercise 4.
We have seen in the class, that algebraic data types can be related to analytic functions (the subset that can be defined out of polynomials via recursion) – by literally interpreting sum types (i.e. variant types) as sums and product types (i.e. tuple and record types) as products. We can extend this interpretation to all OCaml types that we introduced, by interpreting a function type a \rightarrow b as b^a, b to the power of a. Note that the b^a notation is actually used to denote functions in set theory.
('a,'b) choice = Left of 'a | Right of 'b
datatype for +. Write the bijection
function in both directions.'t exp
, that shares with the
exponential function the following property: \frac{\partial \exp (t)}{\partial t} = \exp
(t), where we translate a derivative of a type as a context,
i.e. the type with a “hole”, as in the lecture. Explain why your answer
is correct. Hint: in computer science, our logarithms are mostly base
2.Further reading: http://bababadalgharaghtakamminarronnkonnbro.blogspot.com/2012/10/algebraic-type-systems-combinatorial.html
‘‘Using, Understanding and Unraveling the OCaml Language’’ Didier Rémy, chapter 1
‘‘The OCaml system’’ manual, the tutorial part, chapter 1
let (-|) f g x = f (g x)
let (<<) f g x = f (g x)
(.) f g = \x -> f (g x)
iso1
and iso2
from
previous lecture?let iso2 = step1l -| step2l -| step3l
let (|-) f g x = g (f x)
let (>>) f g x = g (f x)
let iso1 = step1r |- step2r |- step3r
((+) 1)
from last
week: we don’t pass all arguments a function needs, in result we get a
function that requires the remaining arguments. How is it used
above?let rec power f n =
if n <= 0 then (fun x -> x) else f -| power f (n-1)
let derivative dx f = fun x -> (f(x +. dx) -. f(x)) /. dx
where the intent to use with two arguments is stressed, or for short:
let derivative dx f x = (f(x +. dx) -. f(x)) /. dx
(+): int -> int -> int
, so cannot use
with float
ing point numbers – operators followed by dot
work on float
numbers.let pi = 4.0 *. atan 1.0
let sin''' = (power (derivative 1e-5) 3) sin;;
sin''' pi;;
Programs consist of expressions:
\begin{matrix} a := & x & \text{variables}\\\\\\ | & {\texttt{fun }} x {\texttt{->}} a & \text{(defined) functions}\\\\\\ | & a a & \text{applications}\\\\\\ | & C^0 & \text{value constructors of arity } 0\\\\\\ | & C^n (a, \ldots, a) & \text{value constructors of arity } n \\\\\\ | & f^n & \text{built-in values (primitives) of a. } n\\\\\\ | & {\texttt{let }} x = a {\texttt{ in }} a & \text{name bindings (local definitions)}\\\\\\ | & {\texttt{match }} a {\texttt{ with} \ \ \ \ \ \ \ } & \\\\\\ & p {\texttt{->}} a \text{{\texttt{ \textbar }}} \ldots {\texttt{ \textbar }} p {\texttt{->}} a & \text{pattern matching}\\\\\\ p := & x & \text{pattern variables}\\\\\\ | & (p, \ldots, p) & \text{tuple patterns}\\\\\\ | & C^0 & \text{variant patterns of arity } 0\\\\\\ | & C^n (p, \ldots, p) & \text{variant patterns of arity } n \end{matrix}
Arity means how many arguments something requires; (and for tuples, the length of a tuple).
To simplify presentation, we will use a primitive
fix
to define a limited form of let rec
:
{\texttt{let rec }} f {\texttt{ }} x = e_{1} {\texttt{ in }} e_{2} \equiv {\texttt{let }} f = {\texttt{fix (fun }} f {\texttt{ }} x {\texttt{->}} e_{1} {\texttt{) in }} e_{2}
Expressions evaluate (i.e. compute) to values:
\begin{matrix} v := & {\texttt{fun }} x {\texttt{->}} a & \text{(defined) functions}\\\\\\ | & C^n (v_{1}, \ldots, v_{n}) & \text{constructed values}\\\\\\ | & f^n v_{1} \ldots v_{k} & k < n \text{ partially applied primitives} \end{matrix}
To substitute a value v for a variable x in expression a we write a [x := v] – it behaves as if every occurrence of x in a was rewritten by v.
Reduction (i.e. computation) proceeds as follows: first we give redexes
\begin{matrix} \left( {\texttt{fun }} x {\texttt{->}} a \right) v & \rightsquigarrow & a [x := v]\\\\\\ {\texttt{let }} x = v {\texttt{ in }} a & \rightsquigarrow & a [x := v]\\\\\\ f^n v_{1} \ldots v_{n} & \rightsquigarrow & f (v_{1}, \ldots, v_{n})\\\\\\ {\texttt{match }} v {\texttt{ with} } x {\texttt{->}} a {\texttt{ \textbar }} \ldots & \rightsquigarrow & a [x := v]\\\\\\ {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n}) {\texttt{ with}} & & \\\\\\ C_{2}^n (p_{1}, \ldots, p_{k}) {\texttt{->}} a {\texttt{ \textbar }} \operatorname{pm} & \rightsquigarrow & {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n})\\\\\\ & & {\texttt{with} } \operatorname{pm}\\\\\\ {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n}) {\texttt{ with}} & & \\\\\\ C_{1}^n (x_{1}, \ldots, x_{n}) {\texttt{->}} a {\texttt{ \textbar }} \ldots & \rightsquigarrow & a [x_{1} \:= v_{1} ; \ldots ; x_{n} := v_{n}] \end{matrix}
If n = 0, C_{1}^n (v_{1}, \ldots, v_{n}) stands for C^0_{1}, etc. By f (v_{1}, \ldots, v_{n}) we denote the actual value resulting from computing the primitive. We omit the more complex cases of pattern matching.
Rule variables: x matches any expression/pattern variable; a, a_{1}, \ldots, a_{n} match any expression; v, v_{1}, \ldots, v_{n} match any value. Substitute them so that the left-hand-side of a rule is your expression, then the right-hand-side is the reduced expression.
The remaining rules evaluate the arguments in arbitrary order,
but keep the order in which let
…in
and
match
…with
is evaluated.
If a_{i} \rightsquigarrow a_{i}', then:
\begin{matrix} a_{1} a_{2} & \rightsquigarrow & a_{1}' a_{2}\\\\\\ a_{1} a_{2} & \rightsquigarrow & a_{1} a_{2}'\\\\\\ C^n (a_{1}, \ldots, a_{i}, \ldots, a_{n}) & \rightsquigarrow & C^n (a_{1}, \ldots, a_{i}', \ldots, a_{n})\\\\\\ {\texttt{let }} x = a_{1} {\texttt{ in }} a_{2} & \rightsquigarrow & {\texttt{let }} x = a_{1}' {\texttt{ in }} a_{2}\\\\\\ {\texttt{match }} a_{1} {\texttt{ with} } \operatorname{pm} & \rightsquigarrow & {\texttt{match }} a_{1}' {\texttt{ with} } \operatorname{pm} \end{matrix}
Finally, we give the rule for the primitive fix
– it
is a binary primitive:
\begin{matrix} {\texttt{fix}}^2 v_{1} v_{2} & \rightsquigarrow & v_{1} \left( {\texttt{fix}}^2 v_{1} \right) v_{2} \end{matrix}
Because fix
is binary, \left(
{\texttt{fix}}^2 v_{1} \right) is already a value so it will not
be further computed until it is applied inside of v_{1}.
Compute some programs using the rules by hand.
Go through the examples from the Lec3.ml
file in the
toplevel.
3.00 * x + 2.00 * y + x * x * y
eval_1_2 <--
eval_1_2 <-- x * x * y
eval_1_2 <-- y2.
eval_1_2 -->
eval_1_2 <-- x * x
eval_1_2 <-- x1.
eval_1_2 -->
eval_1_2 <-- x1.
eval_1_2 --> 1.
eval_1_2 --> 2.
eval_1_2 --> 3.00 * x + 2.00 * y
eval_1_2 <-- 2.00 * y
eval_1_2 <--
eval_1_2 <-- y2.
eval_1_2 --> 2.00
eval_1_2 <-- 2.
eval_1_2 --> 4.
eval_1_2 --> 3.00 * x
eval_1_2 <--
eval_1_2 <-- x1.
eval_1_2 --> 3.00
eval_1_2 <-- 3.
eval_1_2 --> 3.
eval_1_2 --> 7.
eval_1_2 --> 9.
eval_1_2 --> float = 9. - :
let rec unfold n = if n <= 0 then [] else n :: unfold (n-1);;
# val unfold : int -> int list = <fun>
100000;;
# unfold int list =
- : 100000; 99999; 99998; 99997; 99996; 99995; 99994; 99993; …]
[1000000;;
# unfold Stack overflow during evaluation (looping recursion?).
let rec unfold_tcall acc n =
# if n <= 0 then acc else unfold_tcall (n::acc) (n-1);;
val unfold_tcall : int list -> int -> int list = <fun>
100000;;
# unfold_tcall [] int list =
- : 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; …]
[1000000;;
# unfold_tcall [] int list =
- : 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; …] [
We can postpone doing the actual work till the last moment:
let rec depth tree k = match tree with
0
| Tip -> k
| Node(_,left,right) ->fun dleft ->
depth left (fun dright ->
depth right (1 + (max dleft dright))))
k (
let depth tree = depth tree (fun d -> d)
By “traverse a tree” below we mean: write a function that takes a tree and returns a list of values in the nodes of the tree.
btree -> int list
) that
traverses a binary tree: in prefix order – first the value stored in a
node, then values in all nodes to the left, then values in all nodes to
the right;btree_deriv_at
.simplify: expression -> expression
that simplifies the expression a bit, so that for example the result of
simplify (deriv exp dv)
looks more like what a human
would get computing the derivative of exp
with respect to
dv
.
simplify_once
function that performs a single
step of the simplification, and wrap it using a general
fixpoint
function that performs an operation until a
fixed point is reached: given f and x, it
computes f^n (x) such that f^n (x) = f^{n + 1} (x).Functional Programming
Computation
Exercise 1: By “traverse a tree” below we mean: write a function that takes a tree and returns a list of values in the nodes of the tree.
*btree -> int list*
) that traverses a binary tree:
in prefix order – first the value stored in a node, then values in all
nodes to the left, then values in all nodes to the right;Exercise 2: Turn the function from ex. 1 point 1 or 2 into continuation passing style.
Exercise 3: Do the homework from the end of last
week slides: write btree_deriv_at
.
Exercise 4: Write a function
simplify: expression -> expression
that simplifies the
expression a bit, so that for example the result of
simplify (deriv exp dv)
looks more like what a human would
get computing the derivative of exp
with respect to
dv
:
Write a simplify_once
function that performs a
single step of the simplification, and wrap it using a general
fixpoint
function that performs an operation until a
fixed point* is reached: given f
and x, it computes f^n (x) such that f^n (x) = f^{n + 1} (x).*
Exercise 5: Write two sorting algorithms, working on lists: merge sort and quicksort.
Programming in untyped \lambda-calculus.
Introduction to Lambda Calculus Henk Barendregt, Erik Barendsen
Lecture Notes on the Lambda Calculus Peter Selinger
Let’s compute some larger, recursive program.Recall that we use fix
instead of let rec to simplify rules for recursion. Also remember our
syntactic conventions: fun x y -> e
stands for
fun x -> (fun y -> e)
, etc.
let rec fix f x = f (fix f) xPreparations.type intlist = Nil | Cons of int * intlistWe will evaluate (reduce) the following expression.let length = fix (fun f l -> match l with | Nil -> 0 | Cons (x, xs) -> 1 + f xs) inlength (Cons (1, (Cons (2, Nil))))
let length = fix (fun f l -> match l with | Nil -> 0 | Cons (x, xs) -> 1 + f xs) inlength (Cons (1, (Cons (2, Nil))))
\begin{matrix} {\texttt{let }} x = v {\texttt{ in }} a & \Downarrow & a [x := v] \end{matrix}
fix (fun f l -> match l with | Nil -> 0 | Cons (x, xs) -> 1 + f xs) (Cons (1, (Cons (2, Nil))))
\begin{matrix} {\texttt{fix}}^2 v_{1} v_{2} & \Downarrow & v_{1} \left( {\texttt{fix}}^2 v_{1} \right) v_{2} \end{matrix}
\begin{matrix} {\texttt{fix}}^2 v_{1} v_{2} & \Downarrow & v_{1} \left( {\texttt{fix}}^2 v_{1} \right) v_{2} \end{matrix}
(fun f l -> match l with | Nil -> 0 | Cons (x, xs) -> 1 + f xs) (fix (fun f l -> match l with | Nil -> 0 | Cons (x, xs) -> 1 + f xs)) (Cons (1, (Cons (2, Nil))))
\begin{matrix} \left( {\texttt{fun }} x {\texttt{->}} a \right) v & \rightsquigarrow & a [x := v]\\\\\\ a_{1} a_{2} & \Downarrow & a_{1}' a_{2} \end{matrix}
\begin{matrix} \left( {\texttt{fun }} x {\texttt{->}} a \right) v & \rightsquigarrow & a [x := v]\\\\\\ a_{1} a_{2} & \Downarrow & a_{1}' a_{2} \end{matrix}
(fun l -> match l with | Nil -> 0 | Cons (x, xs) -> 1 + (fix
(fun f l -> match l with | Nil -> 0
| Cons (x, xs) -> 1 + f xs)) xs) (Cons (1, (Cons (2, Nil))))
\begin{matrix} \left( {\texttt{fun }} x {\texttt{->}} a \right) v & \Downarrow & a [x := v] \end{matrix}
\begin{matrix} \left( {\texttt{fun }} x {\texttt{->}} a \right) v & \Downarrow & a [x := v] \end{matrix}
(match Cons (1, (Cons (2, Nil))) with | Nil -> 0 | Cons (x, xs)
-> 1 + (fix (fun f l -> match l with | Nil -> 0
| Cons (x, xs) -> 1 + f xs)) xs)
\begin{matrix} {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n}) {\texttt{ with}} & & \\\\\\ C_{2}^n (p_{1}, \ldots, p_{k}) {\texttt{->}} a {\texttt{ \textbar }} \operatorname{pm} & \Downarrow & {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n})\\\\\\ & & {\texttt{with} } \operatorname{pm} \end{matrix}
\begin{matrix} {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n}) {\texttt{ with}} & & \\\\\\ C_{2}^n (p_{1}, \ldots, p_{k}) {\texttt{->}} a {\texttt{ \textbar }} \operatorname{pm} & \Downarrow & {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n})\\\\\\ & & {\texttt{with} } \operatorname{pm} \end{matrix}
(match Cons (1, (Cons (2, Nil))) with | Cons (x, xs) -> 1 + (fix (fun f l -> match l with | Nil -> 0 | Cons (x, xs) -> 1 + f xs)) xs)
\begin{matrix} {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n}) {\texttt{ with}} & & \\\\\\ C_{1}^n (x_{1}, \ldots, x_{n}) {\texttt{->}} a {\texttt{ \textbar }} \ldots & \Downarrow & a [x_{1} := v_{1} ; \ldots ; x_{n} := v_{n}] \end{matrix}
\begin{matrix} {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n}) {\texttt{ with}} & & \\\\\\ C_{1}^n (x_{1}, \ldots, x_{n}) {\texttt{->}} a {\texttt{ \textbar }} \ldots & \Downarrow & a [x_{1} := v_{1} ; \ldots ; x_{n} := v_{n}] \end{matrix}
1 + (fix (fun f l -> match l with | Nil -> 0 | Cons (x, xs) -> 1 + f xs)) (Cons (2, Nil))
\begin{matrix} {\texttt{fix}}^2 v_{1} v_{2} & \rightsquigarrow & v_{1} \left( {\texttt{fix}}^2 v_{1} \right) v_{2}\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
\begin{matrix} {\texttt{fix}}^2 v_{1} v_{2} & \rightsquigarrow & v_{1} \left( {\texttt{fix}}^2 v_{1} \right) v_{2}\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
1 + (fun f l -> match l with | Nil -> 0 | Cons (x, xs) -> 1 + f xs)) (fix (fun f l -> match l with | Nil -> 0 | Cons (x, xs) -> 1 + f xs)) (Cons (2, Nil))
\begin{matrix} \left( {\texttt{fun }} x {\texttt{->}} a \right) v & \rightsquigarrow & a [x := v]\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
\begin{matrix} \left( {\texttt{fun }} x {\texttt{->}} a \right) v & \rightsquigarrow & a [x := v]\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
1 + (fun l -> match l with | Nil -> 0 | Cons (x, xs) -> 1 +
(fix (fun f l -> match l with
| Nil -> 0 | Cons (x, xs) -> 1 + f xs)) xs))
(Cons (2, Nil))
\begin{matrix} \left( {\texttt{fun }} x {\texttt{->}} a \right) v & \rightsquigarrow & a [x := v]\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
\begin{matrix} \left( {\texttt{fun }} x {\texttt{->}} a \right) v & \rightsquigarrow & a [x := v]\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
1 + (match Cons (2, Nil) with | Nil -> 0 | Cons (x, xs) -> 1 + (fix (fun f l -> match l with | Nil -> 0 | Cons (x, xs) -> 1 + f xs)) xs))
\begin{matrix} {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n}) {\texttt{ with}} & & \\\\\\ C_{2}^n (p_{1}, \ldots, p_{k}) {\texttt{->}} a {\texttt{ \textbar }} \operatorname{pm} & \rightsquigarrow & {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n})\\\\\\ & & {\texttt{with} } \operatorname{pm}\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
\begin{matrix} {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n}) {\texttt{ with}} & & \\\\\\ C_{2}^n (p_{1}, \ldots, p_{k}) {\texttt{->}} a {\texttt{ \textbar }} \operatorname{pm} & \rightsquigarrow & {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n})\\\\\\ & & {\texttt{with} } \operatorname{pm}\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
1 + (match Cons (2, Nil) with | Cons (x, xs) -> 1 + (fix (fun f l -> match l with | Nil -> 0 | Cons (x, xs) -> 1 + f xs)) xs)
\begin{matrix} {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n}) {\texttt{ with}} & & \\\\\\ C_{1}^n (x_{1}, \ldots, x_{n}) {\texttt{->}} a {\texttt{ \textbar }} \ldots & \Downarrow & a [x_{1} := v_{1} ; \ldots ; x_{n} := v_{n}]\\\\\\ & & \end{matrix}
\begin{matrix} {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n}) {\texttt{ with}} & & \\\\\\ C_{1}^n (x_{1}, \ldots, x_{n}) {\texttt{->}} a {\texttt{ \textbar }} \ldots & \rightsquigarrow & a [x_{1} \:= v_{1} ; \ldots ; x_{n} := v_{n}]\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
1 + (1 + (fix (fun f l -> match l with | Nil -> 0 | Cons (x, xs) -> 1 + f xs)) Nil)
\begin{matrix} {\texttt{fix}}^2 v_{1} v_{2} & \rightsquigarrow & v_{1} \left( {\texttt{fix}}^2 v_{1} \right) v_{2}\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}'\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
\begin{matrix} {\texttt{fix}}^2 v_{1} v_{2} & \rightsquigarrow & v_{1} \left( {\texttt{fix}}^2 v_{1} \right) v_{2}\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}'\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
1 + (1 + (fun f l -> match l with | Nil -> 0 | Cons (x, xs)
-> 1 + f xs) (fix (fun f l ->
match l with | Nil -> 0 | Cons (x, xs) -> 1 + f xs)) Nil)
\begin{matrix} \left( {\texttt{fun }} x {\texttt{->}} a \right) v & \rightsquigarrow & a [x := v]\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}'\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
\begin{matrix} \left( {\texttt{fun }} x {\texttt{->}} a \right) v & \rightsquigarrow & a [x := v]\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}'\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
1 + (1 + (fun l -> match l with | Nil -> 0 | Cons (x, xs) ->
1 + (fix (fun f l ->
match l with | Nil -> 0 | Cons (x, xs) -> 1 + f xs)) xs) Nil)
\begin{matrix} \left( {\texttt{fun }} x {\texttt{->}} a \right) v & \rightsquigarrow & a [x := v]\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}'\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
\begin{matrix} \left( {\texttt{fun }} x {\texttt{->}} a \right) v & \rightsquigarrow & a [x := v]\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}'\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
1 + (1 + (match Nil with | Nil -> 0 | Cons (x, xs) -> 1 + (fix
(fun f l -> match l with
| Nil -> 0 | Cons (x, xs) -> 1 + f xs)) xs))
\begin{matrix} {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n}) {\texttt{ with}} & & \\\\\\ C_{1}^n (x_{1}, \ldots, x_{n}) {\texttt{->}} a {\texttt{ \textbar }} \ldots & \rightsquigarrow & a [x_{1} \:= v_{1} ; \ldots ; x_{n} := v_{n}]\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}'\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
\begin{matrix} {\texttt{match }} C_{1}^n (v_{1}, \ldots, v_{n}) {\texttt{ with}} & & \\\\\\ C_{1}^n (x_{1}, \ldots, x_{n}) {\texttt{->}} a {\texttt{ \textbar }} \ldots & \rightsquigarrow & a [x_{1} \:= v_{1} ; \ldots ; x_{n} := v_{n}]\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}'\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
1 + (1 + 0)
\begin{matrix} f^n v_{1} \ldots v_{n} & \rightsquigarrow & f (v_{1}, \ldots, v_{n})\\\\\\ a_{1} a_{2} & \Downarrow & a_{1} a_{2}' \end{matrix}
1 + 1
\begin{matrix} f^n v_{1} \ldots v_{n} & \Downarrow & f (v_{1}, \ldots, v_{n}) \end{matrix}
2
First, let’s forget about types.
Next, let’s introduce a shortcut:
fun x->a
, \lambda x y.a
for fun x y->a
, etc.Let’s forget about all other constructions, only fun and variables.
The real \lambda-calculus has a more general reduction:
\begin{matrix} \left( {\texttt{fun }} x {\texttt{->}} a_{1} \right) a_{2} & \rightsquigarrow & a_{1} [x := a_{2}] \end{matrix}
(called \beta-reduction) and uses bound variable renaming (called \alpha-conversion), or some other trick, to avoid variable capture. But let’s not over-complicate things.
c_true
=\lambda x
y.x and c_false
=\lambda x
y.y.c_and
=\lambda x y.x y
{\texttt{c\_false}}. Check that it works!
c_and c_true c_true
=
c_true
,otherwise c_and a b
=
c_false
.let ctrue = fun x y -> x‘‘True’’ is projection on the first argument.let cfalse = fun x y -> yAnd ‘‘false’’ on the second argument.let cand = fun x y -> x y cfalseIf one is false, then return false.let encodebool b = if b then ctrue else cfalselet decodebool c = c true falseTest the functions in the toplevel.
c_or
and c_not
yourself!let ifthenelse = fun b -> bBooleans select the argument!
Remember to play with the functions in the toplevel.
let cpair m n = fun x -> x m nWe couple thingslet cfirst = fun p -> p ctrueby passing them together.let csecond = fun p -> p cfalseCheck that it works!
let encodepair encfst encsnd (a, b) = cpair (encfst a) (encsnd b)let decodepair defst desnd c = c (fun x y -> defst x, desnd y)let decodeboolpair c = decodepair decodebool decodebool c
We can define larger tuples in the same manner:
let ctriple l m n = fun x -> x l m n
c_false
.let pn0 = fun x -> xStart with the identity function.let pnsucc n = cpair cfalse nStack another pair.let pnpred = fun x -> x cfalse[Explain these functions.]let pniszero = fun x -> x ctrue
We program in untyped lambda calculus as an exercise, and we need encoding / decoding to verify our exercises, so using “magic” for encoding / decoding is “fair game”.
let rec encodepnat n =We use Obj.magic
to forget types.
if n <= 0 then Obj.magic pn0 else
pnsucc (Obj.magic (encodepnat (n-1)))Disregarding types,let rec
decodepnat pn =these functions are straightforward! if decodebool
(pniszero pn) then 0 else 1 + decodepnat (pnpred (Obj.magic pn))
power f n
? We will use its
variant for a different representation of numbers:let cn0 = fun f x -> xThe same as c_false
.let cn1 =
fun f x -> f xBehaves like identity.let cn2 = fun f x -> f (f
x)let cn3 = fun f x -> f (f (f x))
let cnsucc = fun n f x -> f (n f x)
let rec encodecnat n f = if n <=
0 then (fun x -> x) else f -| encodecnat (n-1) flet decodecnat n = n
((+) 1) 0let cn7 f x = encodecnat 7 f xWe need to \eta-expand these definitionslet cn13 f
x = encodecnat 13 f xfor type-system reasons.(Because OCaml allows
side-effects.)let cnadd = fun n m f x -> n f (m f x)Put
n
of f
in front.let cnmult = fun n m f -> n
(m f)Repeat n
timesputting m
of f
in front.let cnprev n =
fun f x ->This is the ‘‘Church numeral signature’‘. nThe only thing
we have is an n
-step loop. (fun g v -> v (g f))We need
sth that operates on f
. (fun z->x)We need to ignore the
innermost step.
(fun z->z)We’ve build a ‘‘machine’’ not results – start the
machine.
cn_is_zero
left as an exercise.
decodecnat (cn_prev cn3)
\Downarrow
(cn_prev cn3) ((+) 1) 0
\Downarrow
(fun f x -> cn3 (fun g v -> v (g f)) (fun z->x)
(fun z->z)) ((+) 1) 0
\Downarrow
((fun f x -> f (f (f x))) (fun g v -> v (g ((+) 1))) (fun z->0) (fun z->z))
\Downarrow
((fun g v -> v (g ((+) 1))) ((fun g v -> v (g ((+) 1))) ((fun g v -> v (g ((+) 1))) (fun z->0)))) (fun z->z))
\Downarrow
((fun z->z) (((fun g v -> v (g ((+) 1))) ((fun g v -> v (g ((+) 1))) (fun z->0)))) ((+) 1)))
\Downarrow
(fun g v -> v (g ((+) 1))) ((fun g v -> v (g ((+) 1))) (fun z->0)) ((+) 1)
\Downarrow
((+) 1) ((fun g v -> v (g ((+) 1))) (fun z->0) ((+) 1))
\Downarrow
((+) 1) (((+) 1) ((fun z->0) ((+) 1)))
\Downarrow
((+) 1) (((+) 1) (0))
\Downarrow
((+) 1) 1
\Downarrow
2
Turing’s fixpoint combinator: \Theta = (\lambda x y.y (x x y)) (\lambda x y.y (x x y))
\begin{matrix} N & = & \Theta F\\\\\\ & = & (\lambda x y.y (x x y)) (\lambda x y.y (x x y)) F\\\\\\ & =_{\rightarrow \rightarrow} & F ((\lambda x y.y (x x y)) (\lambda x y.y (x x y)) F)\\\\\\ & = & F (\Theta F) = F N \end{matrix}
Curry’s fixpoint combinator: \boldsymbol{Y}= \lambda f. (\lambda x.f (x x)) (\lambda x.f (x x))
\begin{matrix} N & = & \boldsymbol{Y}F\\\\\\ & = & (\lambda f. (\lambda x.f (x x)) (\lambda x.f (x x))) F\\\\\\ & =_{\rightarrow} & (\lambda x.F (x x)) (\lambda x.F (x x))\\\\\\ & =_{\rightarrow} & F ((\lambda x.F (x x)) (\lambda x.F (x x)))\\\\\\ & =_{\leftarrow} & F ((\lambda f. (\lambda x.f (x x)) (\lambda x.f (x x))) F)\\\\\\ & = & F (\boldsymbol{Y}F) = F N \end{matrix}
Call-by-value fixpoint combinator: \lambda f' . (\lambda f x.f' (f f) x) (\lambda f x.f' (f f) x)
\begin{matrix} N & = & \operatorname{fix}F\\\\\\ & = & (\lambda f' . (\lambda f x.f' (f f) x) (\lambda f x.f' (f f) x)) F\\\\\\ & =_{\rightarrow} & (\lambda f x.F (f f) x) (\lambda f x.F (f f) x)\\\\\\ & =_{\rightarrow} & \lambda x.F ((\lambda f x.F (f f) x) (\lambda f x.F (f f) x)) x\\\\\\ & =_{\leftarrow} & \lambda x.F ((\lambda f' . (\lambda f x.f' (f f) x) (\lambda f x.f' (f f) x)) F) x\\\\\\ & = & \lambda x.F (\operatorname{fix}F) x = \lambda x.F N x\\\\\\ & =_{\eta} & F N \end{matrix}
The \lambda-terms we have seen above are fixpoint combinators – means inside \lambda-calculus to perform recursion.
What is the problem with the first two combinators?
\begin{matrix} \Theta F & \rightsquigarrow \rightsquigarrow & F ((\lambda x y.y (x x y)) (\lambda x y.y (x x y)) F)\\\\\\ & \rightsquigarrow \rightsquigarrow & F (F ((\lambda x y.y (x x y)) (\lambda x y.y (x x y)) F))\\\\\\ & \rightsquigarrow \rightsquigarrow & F (F (F ((\lambda x y.y (x x y)) (\lambda x y.y (x x y)) F)))\\\\\\ & \rightsquigarrow \rightsquigarrow & \ldots \end{matrix}
Recall the distinction between expressions and values from the previous lecture Computation.
The reduction rule for \lambda-calculus is just meant to determine which expressions are considered “equal” – it is highly non-deterministic, while on a computer, computation needs to go one way or another.
Using the general reduction rule of \lambda-calculus, for a recursive definition, it is always possible to find an infinite reduction sequence (which means that you couldn’t complain when a nasty \lambda-calculus compiler generates infinite loops for all recursive definitions).
Therefore, we need more specific rules. For example, most languages use \left( {\texttt{fun }} x {\texttt{->}} a \right) v \rightsquigarrow a [x := v], which is called call-by-value, or eager computation (because the program eagerly computes the arguments before starting to compute the function). (It’s exactly the rule we introduced in Computation lecture.)
What happens with call-by-value fixpoint combinator?
\begin{matrix} \operatorname{fix}F & \rightsquigarrow & (\lambda f x.F (f f) x) (\lambda f x.F (f f) x)\\\\\\ & \rightsquigarrow & \lambda x.F ((\lambda f x.F (f f) x) (\lambda f x.F (f f) x)) x \end{matrix}
Voila – if we use \left( {\texttt{fun }} x {\texttt{->}} a \right) v \rightsquigarrow a [x := v] as the rulerather than \left( {\texttt{fun }} x {\texttt{->}} a_{1} \right) a_{2} \rightsquigarrow a_{1} [x := a_{2}], the computation stops. Let’s compute the function on some input:
\begin{matrix} \operatorname{fix}F v & \rightsquigarrow & (\lambda f x.F (f f) x) (\lambda f x.F (f f) x) v\\\\\\ & \rightsquigarrow & (\lambda x.F ((\lambda f x.F (f f) x) (\lambda f x.F (f f) x)) x) v\\\\\\ & \rightsquigarrow & F ((\lambda f x.F (f f) x) (\lambda f x.F (f f) x)) v\\\\\\ & \rightsquigarrow & F (\lambda x.F ((\lambda f x.F (f f) x) (\lambda f x.F (f f) x)) x) v\\\\\\ & \rightsquigarrow & \text{depends on } F \end{matrix}
Why the name fixpoint? If you look at our derivations, you’ll see that they show what in math can be written as x = f (x). Such values x are called fixpoints of f. An arithmetic function can have several fixpoints, for example f (x) = x^2 (which xes are fixpoints?) or no fixpoints, for example f (x) = x + 1.
When you define a function (or another object) by recursion, it has very similar meaning: there is a name that is on both sides of =.
In \lambda-calculus, there are functions like \Theta and \boldsymbol{Y}, that take any function as an argument, and return its fixpoint.
We turn a specification of a recursive object into a definition, by solving it with respect to the recurring name: deriving x = f (x) where x is the recurring name. We then have x =\operatorname{fix} (f).
Let’s walk through it for the factorial function (we omit the
prefix cn_
– could be pn_
if pn1
was used instead of cn1
– for numeric functions, and we
shorten if_then_else
into if_t_e
):
\begin{matrix} {\texttt{fact}} n & = & {\texttt{if\_t\_e}} \left( {\texttt{is\_zero}} n \right) {\texttt{cn1}} \left( {\texttt{mult}} n \left( {\texttt{fact}} \left( {\texttt{pred}} n \right) \right) \right)\\\\\\ {\texttt{fact}} & = & \lambda n. {\texttt{if\_t\_e}} \left( {\texttt{is\_zero}} n \right) {\texttt{cn1}} \left( {\texttt{mult}} n \left( {\texttt{fact}} \left( {\texttt{pred}} n \right) \right) \right)\\\\\\ {\texttt{fact}} & = & \left( \lambda f n. {\texttt{if\_t\_e}} \left( {\texttt{is\_zero}} n \right) {\texttt{cn1}} \left( {\texttt{mult}} n \left( f \left( {\texttt{pred}} n \right) \right) \right) \right) {\texttt{fact}}\\\\\\ {\texttt{fact}} & = & \operatorname{fix} \left( \lambda f n. {\texttt{if\_t\_e}} \left( {\texttt{is\_zero}} n \right) {\texttt{cn1}} \left( {\texttt{mult}} n \left( f \left( {\texttt{pred}} n \right) \right) \right) \right) \end{matrix}
The last specification is a valid definition: we just give a name to a (ground, a.k.a. closed) expression.
We have seen how fix works already!
fact cn2
.What does fix (fun x -> cn_succ x)
mean?
A list is either empty, which we often call Empty
or
Nil
, or it consists of an element followed by another list
(called “tail”), the other case often called Cons
.
Define nil
= \lambda x
y.y and cons
H T = \lambda
x y.x H T.
Add numbers stored inside a list:
\begin{matrix} {\texttt{addlist}} l & = & l \left( \lambda h t. {\texttt{cn\_add}} h \left( {\texttt{addlist}} t \right) \right) {\texttt{cn0}} \end{matrix}
To make a proper definition, we need to apply \operatorname{fix} to the solution of above equation.
\begin{matrix} {\texttt{addlist}} & = & \operatorname{fix} \left( \lambda f l.l \left( \lambda h t. {\texttt{cn\_add}} h (f t) \right) {\texttt{cn0}} \right) \end{matrix}
For trees, let’s use a different form of binary trees than so far: instead of keeping elements in inner nodes, we will keep elements in leaves.
Define leaf
n = \lambda x
y.x n and node
L R =
\lambda x y.y L R.
Add numbers stored inside a tree:
\begin{matrix} {\texttt{addtree}} t & = & t (\lambda n.n) \left( \lambda l r. {\texttt{cn\_add}} \left( {\texttt{addtree}} l \right) \left( {\texttt{addtree}} r \right) \right) \end{matrix}
and, in solved form:
\begin{matrix} {\texttt{addtree}} & = & \operatorname{fix} \left( \lambda f t.t (\lambda n.n) \left( \lambda l r. {\texttt{cn\_add}} (f l) (f r) \right) \right) \end{matrix}
let nil = fun x y -> ylet cons h t = fun x y -> x h tlet
addlist l =
fix (fun f l -> l (fun h t -> cnadd h (f t)) cn0)
l;;decodecnat
(addlist (cons cn1 (cons cn2 (cons cn7 nil))));;let leaf n = fun x y
-> x nlet node l r = fun x y -> y l rlet addtree t = fix (fun f t
-> t (fun n -> n) (fun l r -> cnadd (f l) (f r)) )
t;;decodecnat (addtree (node (node (leaf cn3) (leaf cn7)) (leaf
cn1)));;
Observe a regularity: when we encode a variant type with n variants, for each variant we define a function that takes n arguments.
If the kth variant C_{k} has m_{k} parameters, then the function c_{k} that encodes it will have the form:
C_{k} (v_{1}, \ldots, v_{m_{k}}) \sim c_{k} v_{1} \ldots v_{m_{k}} = \lambda x_{1} \ldots x_{n} .x_{k} v_{1} \ldots v_{m_{k}}
The encoded variants serve as a shallow pattern matching with guaranteed exhaustiveness: kth argument corresponds to kth branch of pattern matching.
let pnadd m n = fix (fun f m n -> ifthenelse (pniszero m) n (pnsucc (f (pnpred m) n)) ) m n;;decodepnat (pnadd pn3 pn3);;
Stack overflow during evaluation (looping recursion?).
f
corresponds to recursively calling
pn_add
. Therefore,(pnsucc (f (pnpred m) n)) will be called
regardless of what(pniszero m) returns!addlist
and addtree
work?addlist
and addtree
work because their
recursive calls are “guarded” by corresponding fun. What is inside of
fun is not computed immediately, only when the function is applied to
argument(s).if_then_else
, is
to guard their arguments with fun x
->, where
x
is not used, and apply the result of
if_then_else
to some dummy value.
unit
in \lambda-calculus.let pnadd m n = fix (fun f m n -> (ifthenelse (pniszero m) (fun x -> n) (fun x -> pnsucc (f (pnpred m) n))) id ) m n;;decodepnat (pnadd pn3 pn3);;decodepnat (pnadd pn3 pn7);;
Define (implement) and verify: 1. c_or
and
c_not
; 1. exponentiation for Church numerals; 1. is-zero
predicate for Church numerals; 1. even-number predicate for Church
numerals; 1. multiplication for pair-encoded natural numbers; 1.
factorial n! for pair-encoded natural
numbers. 1. Construct \lambda-terms
m_{0}, m_{1}, \ldots such that for all
n one has:
\begin{matrix} m_{0} & = & x \\\\\\ m_{n + 1} & = & m_{n + 2} m_{n} \end{matrix}
(where equality is after performing \beta-reductions). 1. Define (implement) and
verify a function computing: the length of a list (in Church numerals);
1. cn_max
– maximum of two Church numerals; 1. the depth of
a tree (in Church numerals). 1. Representing side-effects as an
explicitly “passed around” state value, write combinators that represent
the imperative constructs: 1. for…to… 1. for…downto… 1. while…do… 1.
do…while… 1. repeat…until…
Rather than writing a \lambda-term
using the encodings that we’ve learnt, just implement the functions in
OCaml / F#, using built-in int and bool types. You can use let rec
instead of fix. * For example, in exercise (a), write a function let rec
for_to f beg_i end_i s
=… where f
takes
arguments i
ranging from beg_i
to
end_i
, state s
at given step, and returns
state s
at next step; the for_to
function
returns the state after the last step. * And in exercise (c), write a
function let rec while_do p f s
=… where both
p
and f
take state s
at given
step, and if p s
returns true, then f s
is
computed to obtain state at next step; the while_do
function returns the state after the last step.
Do not use the imperative features of OCaml and F#, we will not even cover them in this course!
Although we will not cover them, it is instructive to see the implementation using the imperative features, to better understand what is actually required of a solution to the last exercise.
Functional Programming
Functions
Exercise 1: Define (implement) and test on a couple of examples functions corresponding to / computing:
*c_or*
and *c_not*
;*cn_max*
– maximum of two Church
numerals;Exercise 2: Representing side-effects as an explicitly “passed around” state value, write (higher-order) functions that represent the imperative constructs:
Rather than writing a \lambda-term using the encodings that we’ve learnt, just implement the functions in OCaml / F#, using built-in int and bool types. You can use let rec instead of fix.
*for_to f beg_i end_i s*
=… where
*f*
takes arguments *i*
ranging
from *beg_i*
to *end_i*
,
state *s*
at given step, and returns state
*s*
at next step; the *for_to*
function returns the state after the last step.*while_do p f s*
=… where both *p*
and *f*
take state *s*
at given step, and if *p s*
returns true,
then *f s*
is computed to obtain state at next
step; the *while_do*
function returns the state
after the last step.Do not use the imperative features of OCaml and F#, we will not even cover them in this course!
Despite we will not cover them, it is instructive to see the implementation using the imperative features, to better understand what is actually required of a solution to this exercise.
Parametric types. Abstract Data Types.
Example: maps using red-black trees.
If you see any error on the slides, let me know!
We have seen the rules that govern the assignment of types to expressions, but how does OCaml guess what types to use, and when no correct types exist? It solves equations.
Inside:
let f = List.hd;;
# val f : 'a list -> 'a
'a
is a parameter: it can become any type.
Mathematically we write: f : \forall \alpha .
\alpha \operatorname{list} \rightarrow \alpha – the quantified
type is called a type scheme.
Inside:
let x = ref [];;
# val x : 'a list ref
'_a
is an unknown. It stands for a particular type like
float or (int -> int), OCaml just doesn’t yet know the type.
OCaml only reports unknowns like '_a
in inferred
types for reasons not relevant to functional programming. When unknowns
appear in inferred type against our expectations, \eta-expansion may help: writing let f x
= expr x instead of let f = expr – for example:
let f = List.append [];;
# val f : 'a list -> 'a list = <fun>
let f l = List.append [] l;;
# val f : 'a list -> 'a list = <fun>
Polymorphic functions shine when used with polymorphic data types. In:
type ’a mylist = Empty | Cons of ’a * ’a mylist
we define lists that can store elements of any type 'a
.
Now:
let tail l = match l with | Empty -> invalidarg "tail" | Cons
# val tail : 'a mylist -> 'a mylist (, tl) -> tl;;
is a polymorphic function: works for lists with elements of any type.
A parametric type like ’a mylist is not itself a data type but a family of data types: bool mylist, int mylist etc. are different types.
In OCaml, the syntax is a bit confusing: type parameters precede type name. For example:
type (’a, ’b) choice = Left of ’a | Right of ’b
has two parameters. Mathematically we would write \operatorname{choice} (\alpha, \beta).
Functions do not have to be polymorphic:
let getint c = match c with
#
| Left i -> iif b then 1 else 0;;
| Right b -> val getint : (int, bool) choice -> int
In F#, we provide parameters (when more than one) after type name:
type choice<'a,'
b> = Left of 'a
|
Right of
’b
In Haskell, we provide type parameters similarly to function arguments:
data Choice a b = Left a | Right b
A statement that an expression has a type in an environment is called a type judgement. For environment \Gamma = \lbrace x : \forall \alpha _{1} \ldots \alpha_{n} . \tau_{x} ; \ldots \rbrace, expression e and type \tau we write
\[ e : \]
We will derive the equations in one go using \llbracket \cdot \rrbracket, to be solved later. Besides equations we will need to manage introduced variables, using existential quantification.
For local definitions we require to remember what constraints should hold when the definition is used. Therefore we extend type schemes in the environment to: \Gamma = \lbrace x : \forall \beta_{1} \ldots \beta_{m} [\exists \alpha_{1} \ldots \alpha_{n} .D] . \tau_{x} ; \ldots \rbrace where D are equations – keeping the variables \alpha_{1} \ldots \alpha _{n} introduced while deriving D in front.
\begin{matrix} \llbracket \Gamma \vdash x : \tau \rrbracket & = & \exists \overline{\beta'} \bar{\alpha}' . (D [\bar{\beta} \bar{\alpha} := \overline{\beta'} \bar{\alpha}'] \wedge \tau_{x} [\bar{\beta} \bar{\alpha} := \overline{\beta'} \bar{\alpha}'] \dot{=} \tau)\\\\\\ & & \text{where } \Gamma (x) = \forall \bar{\beta} [\exists \bar{\alpha} .D] . \tau_{x}, \overline{\beta'} \bar{\alpha}' \#\operatorname{FV} (\Gamma, \tau)\\\\\\ & & \\\\\\ \llbracket \Gamma \vdash \boldsymbol{\operatorname{fun}} x {\texttt{->}} e : \tau \rrbracket & = & \exists \alpha _{1} \alpha_{2} . (\llbracket \Gamma \lbrace x : \alpha_{1} \rbrace \vdash e : \alpha_{2} \rrbracket \wedge \alpha_{1} \rightarrow \alpha _{2} \dot{=} \tau),\\\\\\ & & \text{where } \alpha_{1} \alpha_{2} \#\operatorname{FV} (\Gamma, \tau)\\\\\\ & & \\\\\\ \llbracket \Gamma \vdash e_{1} e_{2} : \tau \rrbracket & = & \exists \alpha . (\llbracket \Gamma \vdash e_{1} : \alpha \rightarrow \tau \rrbracket \wedge \llbracket \Gamma \vdash e_{2} : \alpha \rrbracket), \alpha \#\operatorname{FV} (\Gamma, \tau)\\\\\\ & & \\\\\\ \llbracket \Gamma \vdash K e_{1} \ldots e_{n} : \tau \rrbracket & = & \exists \bar{\alpha}' . (\wedge_{i} \llbracket \Gamma \vdash e_{i} : \tau _{i} [\bar{\alpha} := \bar{\alpha}'] \rrbracket \wedge \varepsilon (\bar{\alpha}') \dot{=} \tau),\\\\\\ & & \text{w. } K \,:\, \forall \bar{\alpha} . \tau_{1} \times \ldots \times \tau_{n} \rightarrow \varepsilon (\bar{\alpha}), \bar{\alpha}' \#\operatorname{FV} (\Gamma, \tau)\\\\\\ & & \\\\\\ \llbracket \Gamma \vdash e : \tau \rrbracket & = & (\exists \beta .C) \wedge \llbracket \Gamma \lbrace x : \forall \beta [C] . \beta \rbrace \vdash e_{2} : \tau \rrbracket\\\\\\ e = \boldsymbol{\operatorname{let}} x = e_{1} \boldsymbol{\operatorname{in}} e_{2} & & \text{where } C = \llbracket \Gamma \vdash e_{1} : \beta \rrbracket\\\\\\ & & \\\\\\ \llbracket \Gamma \vdash e : \tau \rrbracket & = & (\exists \beta .C) \wedge \llbracket \Gamma \lbrace x : \forall \beta [C] . \beta \rbrace \vdash e_{2} : \tau \rrbracket\\\\\\ e = \boldsymbol{\operatorname{letrec}} x = e_{1} \boldsymbol{\operatorname{in}} e_{2} & & \text{where } C = \llbracket \Gamma \lbrace x : \beta \rbrace \vdash e_{1} : \beta \rrbracket\\\\\\ & & \\\\\\ \llbracket \Gamma \vdash e : \tau \rrbracket & = & \exists \alpha_{v} . \llbracket \Gamma \vdash e_{v} : \alpha_{v} \rrbracket \wedge_{i} \llbracket \Gamma \vdash p_{i} .e_{i} : \alpha_{v} \rightarrow \tau \rrbracket,\\\\\\ e = \boldsymbol{\operatorname{match}} e_{v} \boldsymbol{\operatorname{with}} \bar{c} & & \alpha_{v} \#\operatorname{FV} (\Gamma, \tau)\\\\\\ \bar{c} = p_{1} .e_{1} | \ldots |p_{n} .e_{n} & & \\\\\\ & & \\\\\\ \llbracket \Gamma, \Sigma \vdash p.e : \tau_{1} \rightarrow \tau_{2} \rrbracket & = & \llbracket \Sigma \vdash p \downarrow \tau_{1} \rrbracket \wedge \exists \bar{\beta} . \llbracket \Gamma \Gamma' \vdash e : \tau_{2} \rrbracket\\\\\\ & & \text{where } \exists \bar{\beta} \Gamma' \text{ is } \llbracket \Sigma \vdash p \uparrow \tau_{1} \rrbracket, \bar{\beta} \#\operatorname{FV} (\Gamma, \tau_{2})\\\\\\ & & \\\\\\ \llbracket \Sigma \vdash p \downarrow \tau_{1} \rrbracket & & \text{derives constraints on type of matched value}\\\\\\ & & \\\\\\ \llbracket \Sigma \vdash p \uparrow \tau_{1} \rrbracket & & \text{derives environment for pattern variables} \end{matrix}
f
polymorphically in its definition.
f
upfront: let rec f : ’a. ’a -> ’a list = …type (’x, ’o) alterning =| Stop| One of ’x * (’o, ’x) alterninglet rec tolist : ’x ’o ’a. (’x->’a) -> (’o->’a) -> (’x, ’o) alterning -> ’a list = fun x2a o2a -> function | Stop -> [] | One (x, rest) -> x2a x::tolist o2a x2a restlet tochoicelist alt = tolist (fun x->Left x) (fun o->Right o) altlet it = tochoicelist (One (1, One (“o”, One (2, One (“oo”, Stop)))))
type ’a seq = Nil | Zero of (’a * ’a) seq | One of ’a * (’a * ’a)
seqWe store a list of elements in exponentially increasing chunks.let
example = One (0, One ((1,2), Zero (One ((((3,4),(5,6)),
((7,8),(9,10))), Nil))))let rec cons : ’a. ’a -> ’a seq -> ’a seq
= fun x -> functionAppending an element to the datastructure is like
| Nil -> One (x, Nil)adding one to a binary number: 1+0=1 | Zero ps
-> One (x, ps)1+…0=…1 | One (y, ps) -> Zero (cons (x,y)
ps)1+…1=[…+1]0let rec lookup : ’a. int -> ’a seq -> ’a = fun i s
-> match i, s withRather than returning None : 'a option
| , Nil -> raise Notfoundwe raise exception, for convenience. | 0,
One (x, ) -> x | i, One (, ps) -> lookup (i-1) (Zero ps) | i, Zero
ps ->Random-Access lookup works let x, y = lookup (i / 2) ps inin
logarithmic time – much faster than if i mod 2 = 0 then x else yin
standard lists.
Maybe
.uses , |
, or |
uses , type parameters |
, , |
Module.value
.t
.module type MAP = sig type (’a, ’b) t val empty : (’a, ’b) t val member : ’a -> (’a, ’b) t -> bool val add : ’a -> ’b -> (’a, ’b) t -> (’a, ’b) t val remove : ’a -> (’a, ’b) t -> (’a, ’b) t val find : ’a -> (’a, ’b) t -> ’bendmodule ListMap : MAP = struct type (’a, ’b) t = (’a * ’b) list let empty = [] let member = List.memassoc let add k v m = (k, v)::m let remove = List.removeassoc let find = List.assocend
Let’s now build an implementation of maps from the ground up. The most straightforward implementation… might not be what you expected:
module TrivialMap : MAP = struct type ('a, 'b) t = | Empty | Add of 'a
of 'a \* ('a, 'b) t let empty = Empty
\* 'b \* ('a, 'b) t | Remove let rec member k m = match m with | Empty -> false | Add
when k = k2 -> true | Remove (k2, ) when k = k2 -> false
(k2, , )
| Add (, , m2) -> member k m2 | Remove (, m2) -> member k m2 let add k v m = Add (k, v, m) let remove k m = Remove (k, m) let rec find k
match m with | Empty -> raise Not_found | Add (k2, v, )
m = when k = k2 -> v | Remove (k2, ) when k = k2 -> raise Notfound
end | Add (, , m2) -> find k m2 | Remove (, m2) -> find k m2
Here is an implementation based on association lists, i.e. on lists of key-value pairs.
module MyListMap : MAP = struct type ('a, 'b) t = Empty | Add of 'a \* 'b \*
let empty = Empty let rec member k m = match m with |
('a, 'b) t false | Add (k2, , ) when k = k2 -> true | Add (, ,
Empty -> let rec add k v m = match m with |
m2) -> member k m2 when k = k2 -> Add (k,
Empty -> Add (k, v, Empty) | Add (k2, , m)
v, m) | Add (k2, v2, m) -> Add (k2, v2, add k v m)
let rec remove k m = match m with | Empty -> Empty | Add
when k = k2 -> m | Add (k2, v, m) -> Add (k2, v, remove
(k2, , m) let rec find k m = match m with | Empty -> raise Error
k m) when k = k2 -> v | Add (, , m2) -> find k m2 end | Add (k2, v, )
module BTreeMap : MAP = struct type ('a, 'b) t = Empty | T of ('a, 'b) t \*
let empty = Empty let rec member k m =‘‘Divide and
'a \* 'b \* ('a, 'b) t match m with | Empty -> false
conquer'' search through the tree. when k = k2 -> true | T (m1, k2, , ) when k <
| T (, k2, , ) let rec add k v
k2 -> member k m1 | T (, , , m2) -> member k m2 in the same way as `member` match m withbut copies
m =Searches the tree
every node along the way. | Empty -> T (Empty, k, v, Empty) | T when k = k2 -> T (m1, k, v, m2) | T (m1, k2, v2, m2)
(m1, k2, , m2) when k < k2 -> T (add k v m1, k2, v2, m2) | T (m1, k2, v2,
m2) -> T (m1, k2, v2, add k v m2)
let rec splitrightmost m = (* A helper
function, it does not belong *)
match m with (* to the ‘‘exported'' signature. *)
raise Notfound | T (Empty, k, v, Empty) -> k, v,
| Empty ->
EmptyWe remove one element, | T (m1, k, v, m2) ->the one that is on let rk, rv, rm = splitrightmost m2 in rk, rv,
the bottom right.
T (m1, k, v, rm)
let rec remove k m = match m with | Empty -> Empty | T (m1,
when k = k2 -> m1 | T (Empty, k2, , m2) when k =
k2, , Empty) when k = k2 -> let rk, rv, rm
k2 -> m2 | T (m1, k2, , m2) in T (rm, rk, rv, m2) | T (m1, k2, v, m2) when
= splitrightmost m1
k < k2 -> T (remove k m1, k2, v, m2) | T (m1, k2, v, m2) -> let rec find k m = match m with |
T (m1, k2, v, remove k m2) raise Notfound | T (, k2, v, ) when k = k2 -> v | T
Empty -> when k < k2 -> find k m1 | T (, , , m2) -> find
(m1, k2, , ) end k m2
Based on Wikipedia http://en.wikipedia.org/wiki/Red-black_tree, Chris Okasaki’s “Functional Data Structures” and Matt Might’s excellent blog post http://matt.might.net/articles/red-black-delete/.
How can we have perfectly balanced trees without worrying about having 2^k - 1 elements? 2-3-4 trees can store from 1 to 3 elements in each node and have 2 to 4 subtrees correspondingly. Lots of freedom!
To insert “25” into (“.” stand for leaves, ignored later)
we descend right, but it is a full node, so we move the middle up and split the remaining elements:
Now there is a place between 24 and 29: next to 29
To represent 2-3-4 tree as a binary tree with one element per node, we color the middle element of a 4-node, or the first element of 2-/3-node, black and make it the parent of its neighbor elements, and make them parents of the original subtrees. Turning this:
Red-black_tree_B-tree.png
into this Red-Black tree:
Red-black_tree_example.png
type color = R | Btype 'a t = E | T of color \* 'a t \* 'a \* 'a tlet empty =
rec member x m = match m withLike in unbalanced binary search tree. |
Elet false | T (, , y, ) when x = y -> true | T (, a, y, ) when
Empty ->
x < y -> member x a | T (, , , b) -> member x blet balance =
functionRestoring the invariants. | B,T (R,T (R,a,x,b),y,c),z,dOn next
figure: left, | B,T (R,a,x,T (R,b,y,c)),z,dtop, | B,a,x,T (R,T
(R,b,y,c),z,d)bottom, | B,a,x,T (R,b,y,T (R,c,z,d))right, -> T (R,T
(B,a,x,b),y,T (B,c,z,d))center tree. | color,a,x,b -> T (color,a,x,b)We for now.
allow red-red violation
let insert x s = let rec ins = functionLike in unbalanced binary search tree,
| E -> T (R,E,x,E)but fix violation above created node. | T as s -> if x<y then balance (color,ins a,y,b)
(color,a,y,b) else if x>y then balance (color,a,y,ins b) else s in
match ins s with (* We could still have red-red violation at root, *)
(* fixed by coloring it black. *)
| T (,a,y,b) -> T (B,a,y,b) failwith "insert: impossible" | E ->
Derive the equations and solve them to find the type for:
let cadr l = List.hd (List.tl l) in cadr (1::2::[]), cadr (true::false::[])
in environ. \Gamma = \left\lbrace \text{{\textcolor{green}{List}}{\textcolor{blue}{.}}{\textcolor{brown}{hd}}} : \forall \alpha . \alpha \operatorname{list} \rightarrow \alpha ; \text{{\textcolor{green}{List}}{\textcolor{blue}{.}}{\textcolor{brown}{tl}}} : \forall \alpha . \alpha \operatorname{list} \rightarrow \alpha \operatorname{list} \right\rbrace. You can take “shortcuts” if it is too many equations to write down.
What does it mean that an implementation has junk (as an algebraic structure for a given signature)? Is it bad?
Define a monomorphic algebraic specification (other than, but similar to, \operatorname{nat}_{p} or \operatorname{string}_{p}, some useful data type).
Discuss an example of a (monomorphic) algebraic specification where it would be useful to drop some axioms (giving up monomorphicity) to allow more efficient implementations.
Does the example ListMap meet the requirements of the algebraic
specification for maps? Hint: here is the definition of
List.removeassoc; compare a x
equals 0 if and only if
a
= x
.
let rec removeassoc x = function | [] -> [] | (a, b as pair) :: l ->
if compare a x = 0 then l else pair :: removeassoc x l
Trick question: what is the computational complexity of ListMap or TrivialMap?
* The implementation MyListMap is inefficient: it performs a lot of copying and is not tail-recursive. Optimize it (without changing the type definition).
Add (and specify) \operatorname{isEmpty}: (\alpha, \beta) \operatorname{map} \rightarrow \operatorname{bool} to the example algebraic specification of maps without increasing the burden on its implementations (i.e. without affecting implementations of other operations). Hint: equational reasoning might be not enough; consider an equivalence relation \approx meaning “have the same keys”, defined and used just in the axioms of the specification.
Design an algebraic specification and write a signature for first-in-first-out queues. Provide two implementations: one straightforward using a list, and another one using two lists: one for freshly added elements providing efficient queueing of new elements, and “reversed” one for efficient popping of old elements.
Design an algebraic specification and write a signature for sets. Provide two implementations: one straightforward using a list, and another one using a map into the unit type.
(Ex. 2.2 in Chris Okasaki “Purely Functional Data Structures”) In
the worst case, member
performs approximately 2 d comparisons, where d is the depth of the tree. Rewrite
member
to take no mare than d +
1 comparisons by keeping track of a candidate element that
might be equal to the query element (say, the last element for
which < returned false) and checking
for equality only when you hit the bottom of the tree.
(Ex. 3.10 in Chris Okasaki “Purely Functional Data Structures”)
The balance
function currently performs several unnecessary
tests: when e.g. ins
recurses on the left child, there are
no violations on the right child.
balance
into lbalance
and
rbalance
that test for violations of left resp. right child
only. Replace calls to balance
appropriately.ins
so that it never tests the color of nodes not
on the search path.* Implement maps (i.e. write a module for the map signature)
based on AVL trees. See
http://en.wikipedia.org/wiki/AVL_tree
.
Functional Programming
Type Inference
Abstract Data Types
Exercise 1: Derive the equations and solve them to find the type for:
let cadr l = List.hd (List.tl l) in cadr (1::2::[]), cadr (true::false::[])
in environment \Gamma = \left\lbrace \text{{\textcolor{green}{List}}{\textcolor{blue}{.}}{\textcolor{brown}{hd}}} : \forall \alpha . \alpha \operatorname{list} \rightarrow \alpha ; \text{{\textcolor{green}{List}}{\textcolor{blue}{.}}{\textcolor{brown}{tl}}} : \forall \alpha . \alpha \operatorname{list} \rightarrow \alpha \operatorname{list} \right\rbrace. You can take “shortcuts” if it is too many equations to write down.
Exercise 2: Terms t_{1}, t_{2}, \ldots \in T (\Sigma, X) are built out of variables x, y, \ldots \in X and function symbols f, g, \ldots \in \Sigma the way you build values out of functions:
In OCaml, we can define terms as: type term = V of string | T of string term list5mm, where for example V(“x”) is a variable x and T(“f”, [V(“x”); V(“y”)]) is the term f (x, y).*
By substitutions* \sigma, \rho, \ldots we mean finite sets of variable, term pairs which we can write as \lbrace x_{1} \mapsto t_{1}, \ldots, x_{k} \mapsto t_{k} \rbrace or [x_{1} := t_{1} ; \ldots ; x_{k} := t_{k}], but also functions from terms to terms \sigma : T (\Sigma, X) \rightarrow T (\Sigma, X) related to the pairs as follows: if \sigma = \lbrace x_{1} \mapsto t_{1}, \ldots, x_{k} \mapsto t_{k} \rbrace, then*
In OCaml, we can define substitutions \sigma as: type subst = (string term) list, together with a function apply : subst -> term -> term which computes \sigma (\cdot).*
We say that a substitution \sigma is more general* than all substitutions \rho \circ \sigma, where (\rho \circ \sigma) (x) = \rho (\sigma (x)). In type inference, we are interested in most general solutions: the less general type judgement \text{{\textcolor{green}{List}}{\textcolor{blue}{.}}{\textcolor{brown}{hd}}} : \operatorname{int}\operatorname{list} \rightarrow \operatorname{int}, although valid, is less useful than \text{{\textcolor{green}{List}}{\textcolor{blue}{.}}{\textcolor{brown}{hd}}} : \forall \alpha . \alpha \operatorname{list} \rightarrow \alpha because it limits the usage of List.hd.*
A unification problem* is a finite set of equations S = \lbrace s_{1} =^? t_{1}, \ldots, s_{n} =^? t_{n} \rbrace which we can also write as s_{1} \dot{=} t_{1} \wedge \ldots \wedge s_{n} \dot{=} t_{n}. A solution, or unifier of S, is a substitution \sigma such that \sigma (s_{i}) = \sigma (t_{i}) for i = 1, \ldots, n. A most general unifier, for short MGU, is a most general such substitution.*
A substitution is idempotent* when \sigma = \sigma \circ \sigma. If \sigma = \lbrace x_{1} \mapsto t_{1}, \ldots, x_{k} \mapsto t_{k} \rbrace, then \sigma is idempotent exactly when no t_{i} contains any of the variables \lbrace x_{1}, \ldots, x_{n} \rbrace; i.e. \lbrace x_{1}, \ldots, x_{n} \rbrace \cap \operatorname{Vars} (t_{1}, \ldots, t_{n}) = \varnothing.*
Exercise 3:
Exercise 4:
Does the example ListMap meet the
requirements of the algebraic specification for maps? Hint: here is the
definition of
List.removeassoc;*compare a x*
equals 0 if and only if
*a*
=*x*
.
let rec removeassoc x = function | [] -> [] | (a, b as pair) :: l -> if compare a x = 0 then l else pair :: removeassoc x l
Trick question: what is the computational complexity of ListMap or TrivialMap?
** The implementation* MyListMap is inefficient: it performs a lot of copying and is not tail-recursive. Optimize it (without changing the type definition).
Add (and specify) \operatorname{isEmpty}: (\alpha, \beta) \operatorname{map} \rightarrow \operatorname{bool} to the example algebraic specification of maps without increasing the burden on its implementations (i.e. without affecting implementations of other operations). Hint: equational reasoning might be not enough; consider an equivalence relation \approx meaning “have the same keys”, defined and used just in the axioms of the specification.
Exercise 5: Design an algebraic specification and write a signature for first-in-first-out queues. Provide two implementations: one straightforward using a list, and another one using two lists: one for freshly added elements providing efficient queueing of new elements, and “reversed” one for efficient popping of old elements.
Exercise 6: Design an algebraic specification and write a signature for sets. Provide two implementations: one straightforward using a list, and another one using a map into the unit type.
Exercise 7:
*member*
performs
approximately 2 d comparisons,
where d is the depth of the
tree. Rewrite *member*
to take no mare than
d + 1 comparisons by keeping track
of a candidate element that might be equal to the
query element (say, the last element for which < returned false) and checking for
equality only when you hit the bottom of the tree.*balance*
function currently
performs several unnecessary tests: when e.g.* *ins*
recurses on the left child, there are no violations on the right
child.
*balance*
into
*lbalance*
and *rbalance*
that
test for violations of left resp. right child only. Replace calls
to *balance*
appropriately.*ins*
so that it never tests the color of
nodes not on the search path.Mapping and folding.Backtracking using lists. Constraint solving.
Martin Odersky ‘‘Functional Programming Fundamentals’’ Lectures 2, 5 and 6
Bits of Ralf Laemmel ‘‘Going Bananas’’
Graham Hutton ‘‘Programming in Haskell’’ Chapter 11 ‘‘Countdown Problem’’
Tomasz Wierzbicki ‘‘Honey Islands Puzzle Solver’’
If you see any error on the slides, let me know!
map
and fold_right
: recursive function
examples, abstracting over gets the higher-order functions.fold_left
.filter
.
fold_right
.map
and fold
for trees and other data
structures.concat_map
.map_reduce
higher-order
function.
map_reduce
to
fold
(?) to search for a set of words
(conjunctive query).concat_fold
functions.How to print a comma-separated list of integers? In module
String
:
val concat : string -> string list -> string
First convert numbers into strings:
let rec stringsofints = function | [] -> [] | hd::tl -> stringofint hd :: stringsofints tllet commasepints = String.concat “,” -| stringsofints
How to get strings sorted from shortest to longest? First find the length:
let rec stringslengths = function | [] -> [] | hd::tl -> (String.length hd, hd) :: stringslengths tllet bysize = List.sort compare -| stringslengths
1.589482.889124.91267032676283.016123164439747.15635335361822.783288133350976.098012303214712.127116682100812.139816774705652.127116682100811.483645323455482.296451250165373.70616-0.561076.6695164704326-0.4552354808837159.37886955946554-0.6034032279402048.15119394099749-1.323075142214584.36233298055298-1.19607421616616-8.062594.51897-5.437905146183364.62480156105305-2.538050668077794.56130109802884-3.5117244344493.79929554173833-7.575754067998413.92629646778674-7.914421.15344-4.506565021828281.30161066278608-2.453383384045510.984108347665035-3.659892181505490.391437359439079-7.575754067998410.4549378224632891.483652.27528-0.506035851303082-3.630258632094194.34117-1.217240.255969704987432-3.63025863209419-7.639253.88396-7.66042135203069-2.1485811615293-3.659890.370271-6.55974665961106-2.12741434052123-8.16843-2.31792-5.94590885037703-2.21208162455351-4.16789588569917-2.27558208757772-4.76056687392512-2.88941989681175-7.78742227807911-2.86825307580368-2.72855-3.79959-0.506035851303082-3.630258632094190.742806588173039-4.053595052255590.213636062971293-4.60393239846541-2.79205252017463-4.41343100939278-5.840072.78329-2.919053446223053.01612316443974-1.05637319751292.78328813335097-1.691377827754992.14828350310888-5.395571504167222.23295078714116-5.33207-0.476402-0.992872734488689-0.2647340918110861.75881399656039-0.5399027649159940.785140230189178-1.21724103717423-4.52773184283635-1.19607421616616-5.459072.19062-5.62840653525599-3.71492591612647-4.52773-1.23841-5.45907196719143-3.778426379150680cm
Now use the generic function:
let commasepints = String.concat “,” -| listmap stringofintlet bysize
=
List.sort compare -| listmap (fun s->String.length s, s)
How to sum elements of a list? |
How to multiply elements in a list? |
Generic solution: |
Caution: list_fold f base l = List.fold_right f l base. |
-6.968285.20805-5.592439476121185.48321537240376-4.237762931604715.22921352030692-4.576432067733834.72120981611324-6.354445032411694.70004299510517-6.693111.18635-4.682266172774181.24985117078979-2.756085461039821.1016834237333-3.306422807249640.509012435507342-6.354445032411690.466678793491203-2.269253.7687-0.8722383913216043.7687028707501-0.1102328350310893.45120055562905-0.5759028972086263.04903095647572-2.692584998015613.11253141949993-2.37508-0.549329-0.512402434184416-0.3799940468315911.73128059267099-0.591662256912291.05394232041275-1.16316642413018-2.03641354676544-1.12083278211404-6.92595-2.36968-4.02609472152401-2.11567336949332-0.999239317370023-2.34850840058209-1.48607620055563-2.87767892578383-6.07927635930679-2.87767892578383-2.26925-3.914851.49844556158222-3.809019050138913.40345945230851-4.063020902235752.93778939013097-4.52869096441328-1.27440799047493-4.61335824844556-6.354454.70004-6.10044318031486-2.2003406535256-3.306420.509012-5.10853954194466-2.14605530848409-2.734923.11253-1.38460084405009-3.806950349498371.05394-1.184330.19997872622222-3.78626681481166-3.560423.4512-3.200588702209293.133698240508-3.073587776160873.64170194470168-3.58159-0.760997-2.90425320809631-0.54932861489615-2.75608546103982-0.866830930017198-4.70343-4.21119-4.08959518454822-4.10535454425189-4.1742624685805-4.48635732239714-3.242923.11253-4.3859306786612-3.87251951316312-3.26132-1.28056-4.23776293160471-3.95718679719540cm
map alters the contents of data | fold computes a value using | ||||
without changing the structure: | the structure as a scaffolding: | ||||
fold
tail-recursive?Let’s investigate some tail-recursive functions. (Not hidden as helpers.)
acc |
hd |
|
tot |
hd tl |
|
|
|
-5.909944.94728-3.729759227411035.01078184945099-1.486076200555634.88378092340257-2.099914009789654.33344357719275-5.338437624024344.26994311416854-5.867611.47592-2.756085461039821.666424130175950.7152731842836351.49708956211139-0.4489019711602060.925585394893504-4.936268024871010.925585394893504-6.01578-2.79978-2.69258499801561-2.56694007143802-0.152566477047228-2.79977510252679-0.427735150152137-3.30777880672047-4.57643206773383-3.35011244873661-3.750933.06343-2.121080830797723.29626934779733-0.5970697182166953.04226749570049-2.226914935838072.55543061251488-4.02609-1.1911-2.45974996692684-0.852427569784363-1.21090752745072-1.25459716893769-2.43858314591877-1.63559994708295-4.5341-4.40845-2.37508268289456-4.28145257309168-0.343067866119857-4.4084534991401-1.02040613837809-4.93762402434184-3.83559333245138-4.93762402434184-0.1102333.296271.498445561582223.423270273845752.874288927106763.232768884773122.175783833840452.555430612514880.1437690170657492.57659743352295-0.491236-0.9582623.61512766238921-0.8100939277682237.04415266569652-1.064095779865065.71064294218812-1.720267231115230.524771795211007-1.677933589099090.376604-4.302622.21811747585659-4.239118931075543.61512766238921-4.429620320148173.17062442121974-4.916457203333770.799940468315915-4.95879084534991-5.338444.29111-6.07927635930679-2.16477047228469-5.69827358116153-2.69394099748644-4.957430.967919-5.48660537108083-1.97426908321207-5.44427172906469-2.6516073554703-2.226912.55543-2.03641354676544-3.62528112184151-2.37508268289456-4.28145257309168-2.41742-1.61443-2.69258499801561-3.79461568990607-2.80072853017803-4.283284085867292.895463.253945.64714247916391-2.397605503373463.72096176742955-4.429620320148174.73754-1.786764.14429818759095-3.413612911760813.50929355734886-4.323786215107820.5036052.70363.00128985315518-4.49312078317238-0.0678992-1.50862.89545574811483-4.598954888212731.752452.70361.62544648763064-4.450787141156242.47212-1.57213.04362349517132-2.461105966397676.17631-1.50863.04362349517132-2.461105966397673.04362-2.461112.02761608678397-4.366119857123960cm
With fold_left
, it is easier to hide the
accumulator. The average
example is a bit more tricky than
list_rev
.
let listrev l = foldleft (fun t h->h::t) [] llet average = foldleft (fun (sum,tot) e->sum +. e, 1. +. tot) (0.,0.)
The function names and order of arguments for
List.fold_right
/ List.fold_left
are due
to:
fold_right f
makes f
right
associative, like list constructor ::
List.foldright f [a1; …; an] b is f a1 (f a2 (… (f an b) …)).
fold_left f
makes f
left
associative, like function application
List.foldleft f a [b1; …; bn] is f (… (f (f a b1) b2) …) bn.
The “backward” structure of fold_left
computation:
List filtering, already rather generic (a polymorphic higher-order function)
let listfilter p l = List.foldright (fun h t->if p h then h::t else t) l []
Tail-recursive map returning elements in reverse order:
let listrevmap f l = List.foldleft (fun t h->f h::t) [] l
map
and fold
for trees and other structuresMapping binary trees is straightforward:
type ’a btree = Empty | Node of ’a * ’a btree * ’a btree let rec btmap f = function | Empty -> Empty | Node (e, l, r) -> Node (f e, btmap f l, btmap f r) let test = Node (3, Node (5, Empty, Empty), Node (7, Empty, Empty))let = btmap ((+) 1) test
map
and fold
we consider in this
section preserve / respect the structure of the data, they do
not correspond to map
and fold
of
abstract data type containers, which are like
List.rev_map
and List.fold_left
over container
elements listed in arbitrary order.
List.map
and
List.fold_right
to other structures.fold
in most general form needs to process the
element together with partial results for the subtrees.
let rec btfold f base = function | Empty -> base | Node (e, l,
Examples:
let sumels = btfold (fun i l r -> i + l + r) 0let depth t = btfold
(fun
l r -> 1 + max l r) 1 t
map
and fold
for more complex structuresTo have a data structure to work with, we recall expressions from lecture 3.
type expression = Const of float | Var of string | Sum of expression * expression (* e1 + e2 ) | Diff of expression expression (* e1 - e2 ) | Prod of expression expression (* e1 * e2 ) | Quot of expression expression (* e1 / e2 *)
Multitude of cases make the datatype harder to work with. Fortunately, or-patterns help a bit:
let rec vars = function | Const -> [] | Var x -> [x] | Sum (a,b) | Diff (a,b) | Prod (a,b) | Quot (a,b) -> vars a @ vars b
Mapping and folding needs to be specialized for each case. We pack the behaviors into a record.
type expressionmap = { mapconst : float -> expression; mapvar :
string -> expression; mapsum : expression -> expression ->
expression; mapdiff : expression -> expression ->
expression;
mapprod : expression -> expression -> expression; mapquot :
expression -> expression -> expression;}Note how
expression
from above is substituted by 'a
below, explain why?type ’a expressionfold = {
foldconst : float -> ’a; foldvar : string -> ’a; foldsum : ’a
-> ’a -> ’a; folddiff : ’a -> ’a -> ’a; foldprod : ’a ->
’a -> ’a; foldquot : ’a -> ’a -> ’a;}
Next we define standard behaviors for map
and
fold
, which can be tailored to needs for particular
case.
let identitymap = { mapconst = (fun c -> Const c); mapvar = (fun x
-> Var x); mapsum = (fun a b -> Sum (a, b)); mapdiff = (fun a b
-> Diff (a, b)); mapprod = (fun a b -> Prod (a, b)); mapquot =
(fun a b -> Quot (a, b));}let makefold op base = { foldconst = (fun
-> base); foldvar = (fun -> base); foldsum = op; folddiff =
op;
foldprod = op; foldquot = op;}
The actual map
and fold
functions are
straightforward:
let rec exprmap emap = function | Const c -> emap.mapconst c | Var x -> emap.mapvar x | Sum (a,b) -> emap.mapsum (exprmap emap a) (exprmap emap b) | Diff (a,b) -> emap.mapdiff (exprmap emap a) (exprmap emap b) | Prod (a,b) -> emap.mapprod (exprmap emap a) (exprmap emap b) | Quot (a,b) -> emap.mapquot (exprmap emap a) (exprmap emap b)let rec exprfold efold = function | Const c -> efold.foldconst c | Var x -> efold.foldvar x | Sum (a,b) -> efold.foldsum (exprfold efold a) (exprfold efold b) | Diff (a,b) -> efold.folddiff (exprfold efold a) (exprfold efold b) | Prod (a,b) -> efold.foldprod (exprfold efold a) (exprfold efold b) | Quot (a,b) -> efold.foldquot (exprfold efold a) (exprfold efold b)
Now examples. We use {record with field=value
} syntax
which copies record
but puts value
instead of
record.field
in the result.
let primevars = exprmap {identitymap with mapvar = fun x -> Var
(x”’“)}let subst s = let apply x = try List.assoc x s with Notfound
-> Var x in exprmap {identitymap with mapvar = apply}let vars =
exprfold {(makefold (@) []) with foldvar = fun x-> [x]}let size =
exprfold (makefold (fun a b->1+a+b) 1)let eval env = exprfold {
foldconst = id;
foldvar = (fun x -> List.assoc x env); foldsum = (+.); folddiff =
(-.);
foldprod = ( *.); foldquot = (/.);}
In 1977/78, John Backus designed FP, the first function-level programming language. Over the next decade it evolved into the FL language.
For functionl-level programming style, we need functionals/combinators, like these from OCaml Batteries: let const x = xlet ( |- ) f g x = g (f x)let ( -| ) f g x = f (g x)let flip f x y = f y xlet ( *** ) f g = fun (x,y) -> (f x, g y)let ( &&& ) f g = fun x -> (f x, g x)let first f x = fst (f x)let second f x = snd (f x)let curry f x y = f (x,y)let uncurry f (x,y) = f x y
The flow of computation can be seen as a circuit where the results of nodes-functions are connected to further nodes as inputs.
We can represent the cross-sections of the circuit as tuples of intermediate values.
let print2 c i = let a = Char.escaped c in let b = stringofint i in a b
|
|
-9.51-4.01.0-9.4935-0.00601931-4.00.0Char.escaped
-41string_of_int
-4.05-5.47857e-050.513.50.50.5395720333377430.0151475062839uncurry (^)
3.50.57.50.510.50.50cm
Since we usually work by passing arguments one at a time rather
than in tuples, we need uncurry
to access multi-argument
functions, and we pack the result with curry
.
Another option to remove explicit use of function parameters,
rather than to pack intermediate values as tuples, is to use function
composition, flip
, and the so called S
combinator:
let s x y z = x z (y z)
to bring a particular argument of a function to “front”, and pass it a result of another function. Example: a filter-map function
let func2 f g l = List.filter f (List.map g (l))Definition of function composition.let func2 f g = (-|) (List.filter f) (List.map g)let func2 f = (-|) (List.filter f) -| List.mapCompositionagain, below without the infix notation.let func2 f = (-|) ((-|) (List.filter f)) List.maplet func2 f = flip (-|) List.map ((-|) (List.filter f))let func2 f = (((|-) List.map) -| ((-|) -| List.filter)) flet func2 = (|-) List.map -| ((-|) -| List.filter)
Mathematics has notation for sum over an interval: \sum_{n = a}^b f (n).
In OCaml, we do not have a universal addition operator:
let rec isumfromto f a b = if a > b then 0 else f a + isumfromto f (a+1) blet rec fsumfromto f a b = if a > b then 0. else f a +. fsumfromto f (a+1) blet pi2over6 = fsumfromto (fun i->1. /. floatofint (i*i)) 1 5000
It is natural to generalize:
let rec opfromto op base f a b = if a > b then base else op (f a) (opfromto op base f (a+1) b)
Let’s collect the results of a multifunction (i.e. a set-valued function) for a set of arguments, in math notation:
f (A) = \bigcup_{p \in A} f (p)
It is a useful operation over lists with union
translated as append
:
let rec concatmap f = function | [] -> [] | a::l -> f a @ concatmap f l
and more efficiently:
let concatmap f l = let rec cmapf accu = function | [] -> accu | a::l -> cmapf (List.revappend (f a) accu) l in List.rev (cmapf [] l)
let rec subseqs l = match l with | [] -> [[]] | x::xs ->
let pxs = subseqs xs in List.map (fun px -> x::px) pxs @ pxs
Tail-recursively:
let rec rmapappend f accu = function | [] -> accu | a::l -> rmapappend f (f a :: accu) l
let rec subseqs l = match l with | [] -> [[]] | x::xs ->
let pxs = subseqs xs in rmapappend (fun px -> x::px) pxs pxs
In-class work: Return a list of all possible ways of splitting a list into two non-empty parts.
Homework:
Find all permutations of a list.
Find all ways of choosing without repetition from a list.
group_by
and map_reduce
It is often useful to organize values by some property.
First we collect an elements from an association list by key.
let collect l = match List.sort (fun x y -> compare (fst x) (fst
y)) l with | [] -> []Start with associations sorted by key. | (k0,
v0)::tl -> let k0, vs, l = List.foldleft (fun (k0, vs, l) (kn, vn)
->Collect values for the current key if k0 = kn then k0, vn::vs,
l
and when the key changes else kn, [vn], (k0,List.rev
vs)::l)stack the collected values. (k0, [v0], []) tl inWhat do we gain
by reversing?
List.rev ((k0,List.rev vs)::l)
Now we can group by an arbitrary property:
let groupby p l = collect (List.map (fun e->p e, e) l)
But we want to process the results, like with an aggregate operation in SQL. The aggregation operation is called reduction.
let aggregateby p red base l = let ags = groupby p l in List.map (fun (k,vs)->k, List.foldright red vs base) ags
We can use the feed-forward operator: let ( |> ) x f = f x
let aggregateby p redf base l = groupby p l |> List.map (fun (k,vs)->k, List.foldright redf vs base)
Often it is easier to extract the property over which we aggregate
upfront. Since we first map the elements into the extracted key-value
pairs, we call the operation map_reduce
:
let mapreduce mapf redf base l = List.map mapf l |> collect |> List.map (fun (k,vs)->k, List.foldright redf vs base)
map_reduce
/concat_reduce
examplesSometimes we have multiple sources of information rather than records.
let concatreduce mapf redf base l = concatmap mapf l |> collect |> List.map (fun (k,vs)->k, List.foldright redf vs base)
Compute the merged histogram of several documents:
let histogram documents = let mapf doc = Str.split (Str.regexp “[
t.,;]+”) doc |> List.map (fun
word
->word
,1) in concatreduce mapf (+) 0
documents
Now compute the inverted index of several documents (which come with identifiers or addresses).
let cons hd tl = hd::tllet invertedindex documents = let mapf (addr,
doc) =
Str.split (Str.regexp “[ t.,;]+”) doc |> List.map (fun
word->word,addr) in concatreduce mapf cons [] documents
And now… a “search engine”:
let search index words = match List.map (flip List.assoc index) words with | [] -> [] | idx::idcs -> List.foldleft intersect idx idcs
where intersect
computes intersection of sets
represented as lists.
let revcollect l = match List.sort (fun x y -> compare (fst x)
(fst y)) l with | [] -> [] | (k0, v0)::tl -> let k0, vs, l =
List.foldleft
(fun (k0, vs, l) (kn, vn) -> if k0 = kn then k0, vn::vs, l
else kn, [vn], (k0, vs)::l) (k0, [v0], []) tl in List.rev ((k0,
vs)::l)
let trconcatreduce mapf redf base l = concatmap mapf l |>
revcollect
|> List.revmap (fun (k,vs)->k, List.foldleft redf base vs)
let rcons tl hd = hd::tllet invertedindex documents = let mapf (addr, doc) = … in trconcatreduce mapf rcons [] documents
let intersect xs ys =Sets as sorted lists. let rec
aux acc = function
| [], | , [] -> acc | (x::xs’ as xs), (y::ys’ as ys) -> let c =
compare x y in if c = 0 then aux (x::acc) (xs’, ys’) else if c < 0
then aux acc (xs’, ys) else aux acc (xs, ys’) in List.rev (aux [] (xs,
ys))
let readlines file = let input = openin file in let rec read lines
=The Scanf library uses continuation passing. try Scanf.fscanf input
“%[]” (fun x -> read (x :: lines)) with Endoffile -> lines
in
List.rev (read [])
let indexed l =Index elements by their positions. Array.oflist l |> Array.mapi (fun i e->i,e) |> Array.tolist
let searchengine lines = let lines = indexed lines in let index =
invertedindex lines in fun words -> let ans = search index words
in
List.map (flip List.assoc lines) ans
let searchbible = searchengine (readlines “./bible-kjv.txt”)let
testresult =
searchbible [“Abraham”; “sons”; “wife”]
option
typeOperate on an optional value:
let mapoption f = function | None -> None | Some e -> f e
Map an operation over a list and filter-out cases when it does not succeed:
let rec mapsome f = function | [] -> [] | e::l -> match f e
with
| None -> mapsome f l | Some r -> r :: mapsome f
lTail-recurively:
let mapsome f l = let rec mapsf accu = function | [] -> accu | a::l -> mapsf (match f a with None -> accu | Some r -> r::accu) l in List.rev (mapsf [] l)
Using a given set of numbers and arithmetic operators +, -, *, /, construct an expression with a given value.
All numbers, including intermediate results, must be positive integers.
Each of the source numbers can be used at most once when constructing the expression.
Example:
There are 780 solutions for this example.
Changing the target to 831 gives an example that has no solutions.
Operators:
type op = Add | Sub | Mul | Div
Apply an operator:
let apply op x y = match op with | Add -> x + y | Sub -> x - y | Mul -> x * y | Div -> x / y
Decide if the result of applying an operator to two positive integers is another positive integer:
let valid op x y = match op with | Add -> true | Sub -> x > y | Mul -> true | Div -> x mod y = 0
Expressions:
type expr = Val of int | App of op * expr * expr
Return the overall value of an expression, provided that it is a positive integer:
let rec eval = function | Val n -> if n > 0 then Some n else
None
| App (o,l,r) -> eval l |> mapoption (fun x -> eval r |>
mapoption (fun y -> if valid o x y then Some (apply o x y)
else None))
Homework: Return a list of all possible ways of
choosing zero or more elements from a list –
choices
.
Return a list of all the values in an expression:
let rec values = function | Val n -> [n] | App (,l,r) -> values l @ values r
Decide if an expression is a solution for a given list of source numbers and a target number:
let solution e ns n = listdiff (values e) ns = [] && isunique (values e) && eval e = Some n
Return a list of all possible ways of splitting a list into two non-empty parts:
let split l = let rec aux lhs acc = function | [] | [] -> [] | [y; z] -> (List.rev (y::lhs), [z])::acc | hd::rhs -> let lhs = hd::lhs in aux lhs ((List.rev lhs, rhs)::acc) rhs in aux [] [] l
We introduce an operator to work on multiple sources of data, producing even more data for the next stage of computation:
let ( |-> ) x f = concatmap f x
Return a list of all possible expressions whose values are precisely a given list of numbers:
let combine l r =Combine two expressions using each operator.
List.map (fun o->App (o,l,r)) [Add; Sub; Mul; Div]let rec exprs =
function | [] -> [] | [n] -> [Val n] | ns -> split ns |->
(fun (ls,rs) ->For each split ls,rs of numbers, exprs ls |-> (fun
l ->for each expression l
over ls
exprs rs
|-> (fun r ->and expression r
over rs
combine l r)))produce all l ? r
expressions.
Return a list of all possible expressions that solve an instance of the countdown problem:
let guard n = List.filter (fun e -> eval e = Some n)
let solutions ns n = choices ns |-> (fun ns’ -> exprs ns’ |> guard n)
Another way to express this:
let guard p e = if p e then [e] else []
let solutions ns n = choices ns |-> (fun ns’ -> exprs ns’ |-> guard (fun e -> eval e = Some n))
We seek to define a function that fuses together the generation and evaluation of expressions:
(e, eval e)
– so only valid subexpressions are ever
generated.let combine’ (l,x) (r,y) = [Add; Sub; Mul; Div] |> List.filter
(fun o->valid o x y) |> List.map (fun o->App (o,l,r), apply o x
y)let rec results = function | [] -> [] | [n] -> if n > 0 then
[Val n, n] else [] | ns -> split ns |-> (fun (ls,rs) ->
results ls |-> (fun lx -> results rs |-> (fun ry ->
combine’ lx ry)))
Once the result is generated its value is already computed, we only check if it equals the target.
let solutions’ ns n = choices ns |-> (fun ns’ -> results ns’ |> List.filter (fun (e,m)-> m=n) |> List.map fst)We discard the memorized values.
Strengthening the valid predicate to take account of commutativity and identity properties:
let valid op x y = match op with | Add -> x <= y | Sub -> x > y | Mul -> x <= y && x <> 1 && y <> 1 | Div -> x mod y = 0 && y <> 1
Now recompile combine’, results and solutions’.
num_islands
disconnected components, each with
island_size
cells.Task: 3 islands x 3Solution:
type cell = int * intWe address cells using ‘‘cartesian’’
coordinatesmodule CellSet =and store them in either lists or sets.
Set.Make (struct type t = cell let compare = compare end)type task =
{For board ‘‘size’’ N, the honeycomb
coordinates boardsize : int;range from (- 2 N,
- N) to 2 N, N.
numislands : int;Required number of islands islandsize : int;and
required number of cells in an island. emptycells : CellSet.t;The cells
that are initially without honey.}
let cellsetoflist l =List into set, inverse of CellSet.elements
List.foldright CellSet.add l CellSet.empty
x,y
-0.902203-0.291672x+2,y
2.23049-0.376339x+1,y+1
0.410142.35418x-1,y+1
-2.637882.33301x-2,y
-4.20423-0.418673x-1,y-1
-2.65905-3.08569x+1,y-1
0.431307-3.191530cm
let neighbors n eaten (x,y) = List.filter (insideboard n eaten) [x-1,y-1; x+1,y-1; x+2,y; x+1,y+1; x-1,y+1; x-2,y]
0,0-0.373032-0.1543520,2-0.3730323.041840,-2-0.394199-3.541041,10.5159741.496664,03.33116-0.239023,12.505661.496662,21.510813.063-2,0-2.23571-0.1543520cm
let even x = x mod 2 = 0
let insideboard n eaten (x, y) = even x = even y && abs y <= n && abs x + abs y <= 2*n && not (CellSet.mem (x,y) eaten)
let honeycells n eaten = fromto (-2n) (2n)|->(fun x -> fromto (-n) n |-> (fun y -> guard (insideboard n eaten) (x, y)))
We separately generate colored polygons:
let drawhoneycomb \simw \simh task eaten = let i2f = floatofint in let nx = i2f (4 * task.boardsize + 2) in let ny = i2f (2 * task.boardsize + 2) in let radius = min (i2f w /. nx) (i2f h /. ny) in let x0 = w / 2 in let y0 = h / 2 in let dx = (sqrt 3. /. 2.) . radius +. 1. inThe distance between let dy = (3. /. 2.) . radius +. 2. in(x, y) and (x + 1, y + 1). let drawcell (x,y) = Array.init 7We draw a closed polygon by placing 6 points (fun i ->evenly spaced on a circumcircle. let phi = floatofint i . pi /. 3. in x0 + intoffloat (radius . sin phi +. floatofint x . dx), y0 + intoffloat (radius . cos phi +. floatofint y *. dy)) in let honey = honeycells task.boardsize (CellSet.union task.emptycells (cellsetoflist eaten)) |> List.map (fun p->drawcell p, (255, 255, 0)) in let eaten = List.map (fun p->drawcell p, (50, 0, 50)) eaten in let oldempty = List.map (fun p->drawcell p, (0, 0, 0)) (CellSet.elements task.emptycells) in honey @ eaten @ oldempty
We can draw the polygons to an SVG image:
let drawtosvg file \simw \simh ?title ?desc curves = let f = openout file in Printf.fprintf f “<!DOCTYPE svg PUBLIC”-//W3C//DTD SVG 1.1//EN” “http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd”>%!”But we also want to draw on a screen window – we need to link the
Graphics
library. In the interactive toplevel:
##load “graphics.cma”;;
When compiling we just provide graphics.cma
to the
command.
let drawtoscreen \simw \simh curves = Graphics.opengraph (”
“stringofint w”x”stringofint h); Graphics.setcolor (Graphics.rgb 50 50
0);We draw a brown background. Graphics.fillrect 0 0 (Graphics.sizex ())
(Graphics.sizey ()); List.iter (fun (points, (r,g,b)) ->
Graphics.setcolor (Graphics.rgb r g b); Graphics.fillpoly points)
curves; if Graphics.readkey () = 'q'
We wait so that
solutions can be seen then failwith “User interrupted finding
solutions.”;as they’re computed. Graphics.closegraph ()
We walk through each island counting its cells, depth-first: having visited everything possible in one direction, we check whether something remains in another direction.
Correctness means there are numislands
components each
with islandsize
cells. We start by computing the cells to
walk on: honey
.
let checkcorrect n islandsize numislands emptycells = let honey = honeycells n emptycells in
We keep track of already visited cells and islands. When an unvisited cell is there after walking around an island, it must belong to a different island.
let rec checkboard beenislands unvisited visited = match unvisited
with
| [] -> beenislands = numislands | cell::remaining when CellSet.mem
cell visited ->
checkboard been_islands remaining visited
Keep
looking.
| cell::remaining (* when not visited *) -> let (beensize, unvisited,
visited) = checkisland cell
Visit another island.(1,
remaining, CellSet.add cell visited) in beensize = islandsize &&
checkboard (beenislands+1) unvisited visited
When walking over an island, besides the unvisited
and
visited
cells, we need to remember been_size
–
number of cells in the island visited so far.
and checkisland current state = neighbors n emptycells current |>
List.foldleft
Walk into each direction and accumulate
visits.(fun (beensize, unvisited, visited as state) neighbor -> if
CellSet.mem neighbor visited then state else let unvisited = remove
neighbor unvisited in let visited = CellSet.add neighbor visited
in
let beensize = beensize + 1 in checkisland neighbor
(beensize, unvisited, visited)) state
inStart from the
current overall state (initial been_size
is 1).
Initially there are no islands already visited.
checkboard 0 honey emptycells
When there is only one possible result per step, we work through a list using List.foldright and List.foldleft functions.
What if there are multiple results? Recall that when we have multiple
sources of data and want to collect multiple results, we use
concat_map
:
-4.568261.32331-3.509921.34447-2.218751.32331-0.9699031.323310.3424391.30214-4.568261.32331-5.541936764122240.264965603915862-4.568261.32331-4.695263923799440.328466066940071-4.568261.32331-4.039092472549280.286132424923932-3.509921.34447-3.573422410371740.391966529964281-3.509921.34447-2.896084138113510.370799708956211-2.218751.32331-2.451580896944040.434300171980421-0.9699031.32331-1.604908056621250.413133350972351-0.9699031.32331-0.8640693213388010.3919665299642811.316111.386811.316111.386810.405939939145390.4554669929884911.316111.386811.083278211403620.476633813996561.316111.386811.8029501256780.4131333509723511.316111.386812.586122502976580.54013427702077-5.541940.264966-5.541940.264966-4.695260.328466-4.039090.286132-3.573420.391967-2.896080.3708-2.451580.4343-1.604910.413133-0.8640690.3919670.405940.4554671.083280.4766341.802950.4131332.586120.540134-5.774770.624802-6.007606826299780.56130109802884-6.02877364730784-0.0525367112051859-5.73243815319487-0.116037174229395-4.017930.794136-3.890924725492790.56130109802884-3.933258367508930.0109637518190237-4.22959386162191-0.116037174229395-3.763920.878803-3.679256515412090.0532973938351634-3.44642148432332-0.031369890197116-2.874920.89997-2.769083212065090.688302024077259-2.76908321206509-0.0102030691890462-2.98075142214579-0.031369890197116-2.557420.89997-2.557415001984390.0956310358513031-2.45158089694404-0.0737035322132557-2.091740.815303-2.007077655774570.688302024077259-2.070578118798780.0744642148432332-2.23991268686334-0.031369890197116-1.668410.878803-1.816576266701940.794136129117608-1.837743087710010.0532973938351634-1.62607487762932-0.137203995237465-0.6312340.878803-0.4830665431935440.794136129117608-0.5254001852096840.0956310358513031-0.821735679322662-0.0948703532213256-0.01739650.857637-0.1443974070644270.794136129117608-0.2078978700886360.264965603915862-0.1443974070644270.1379646778674430.1731050.7518020.0672708030162720.1591314988755130.469440.8788030.300105834105040.9634706971821670.278939013096970.05329739383516340.4271067601534590.05329739383516342.649620.9423042.882457997089560.8999702341579572.84012435507342-0.0313698901971162.48028839793623-0.0525367112051859-5.541940.264966-5.54193676412224-0.539373594390792-4.695260.328466-4.71643074480751-0.560540415398862-3.573420.391967-3.55225558936367-0.539373594390792-2.896080.3708-2.89608413811351-0.539373594390792-1.604910.413133-1.62607487762932-0.560540415398862-0.8640690.391967-0.864069321338801-0.5817072364069320.405940.4554670.38477311813732-0.5605404153988621.083280.4766341.06211139039556-0.4970399523746531.802950.4131331.78178330466993-0.4758731313665832.586120.5401342.5014552189443-0.497039952374653-5.54194-0.539374-4.71643-0.56054-3.55226-0.539374-2.89608-0.539374-1.62607-0.56054-0.864069-0.5817070.384773-0.560541.06211-0.497041.78178-0.4758732.50146-0.49704-5.541941.55614-5.859439079243291.47147440137584-5.859439079243291.11163844423866-5.626604048154520.9846375181902372.120451.534972.416787934912031.492641222383912.416787934912031.196305728270942.205119724831331.13280526524673-5.98644-0.306539-6.28277549940468-0.348872205318164-6.24044185738854-0.687541341447281-5.9229395422675-0.856875909511842.96713-0.2218713.30579441725096-0.3700390263262343.30579441725096-0.6452076994311422.88245799708956-0.85687590951184concat_map
-11.06650.984638f xs =
-10.34680.264966List.map f xs
3.707961.04814|> List.concat
3.87730.0744642
We shortened concat_map
calls using “work |-> (fun
a_result -> …)” scheme. Here we need to collect results once per
step.
let rec concatfold f a = function | [] -> [a] | x::xs -> f x a |-> (fun a’ -> concatfold f a’ xs)
We turn the code for testing a solution into one that generates a correct solution.
eaten
.The generating function has the same signature as the testing function:
let findtoeat n islandsize numislands emptycells = let honey = honeycells n emptycells in
Since we return lists of solutions, if we are done with current
solution eaten
we return [eaten]
, and if we
are in a “dead corner” we return [].
let rec findboard beenislands unvisited visited eaten = match
unvisited with | [] -> if beenislands = numislands then [eaten] else
[] | cell::remaining when CellSet.mem cell visited -> findboard
beenislands remaining visited eaten | cell::remaining (* when not
visited *) -> findisland cell (1, remaining, CellSet.add cell
visited, eaten) |->Concatenate solutions for each way of eating cells
around and island. (fun (beensize, unvisited, visited, eaten)
->
if beensize = islandsize then findboard (beenislands+1)
unvisited visited eaten else [])
We step into each neighbor of a current cell of the island, and either eat it or walk further.
and findisland current state = neighbors n emptycells current |>
concatfold
Instead of fold_left
since multiple
results.(fun neighbor
(beensize, unvisited, visited, eaten as state) -> if CellSet.mem
neighbor visited then [state] else let unvisited = remove neighbor
unvisited in let visited = CellSet.add neighbor visited in (beensize,
unvisited, visited,
neighbor::eaten):: (* solutions where neighbor is honey *)
findisland neighbor (beensize+1, unvisited, visited, eaten)) state
in
The initial partial solution is – nothing eaten yet.
checkboard 0 honey emptycells []
We can test it now:
let w = 800 and h = 800let ans0 = findtoeat testtask0.boardsize testtask0.islandsize testtask0.numislands testtask0.emptycellslet = drawtoscreen \simw \simh (drawhoneycomb \simw \simh testtask0 (List.hd ans0))
But in a more complex case, finding all solutions takes too long:
let ans1 = findtoeat testtask1.boardsize testtask1.islandsize testtask1.numislands testtask1.emptycellslet = drawtoscreen \simw \simh (drawhoneycomb \simw \simh testtask1 (List.hd ans1))
(See Lec6.ml
for definitions of test cases.)
type state = { beensize: int;Number of honey cells in current
island.
beenislands: int;Number of islands visited so far. unvisited: cell
list;Cells that need to be visited. visited: CellSet.t;Already visited.
eaten: cell list;Current solution candidate. moretoeat: int;Remaining
cells to eat for a complete solution.}
We define the basic operations on the state up-front. If you could keep them inlined, the code would remain more similar to the previous version.
let rec visitcell s = match s.unvisited with | [] -> None | c::remaining when CellSet.mem c s.visited -> visitcell {s with unvisited=remaining} | c::remaining (* when c not visited *) -> Some (c, {s with unvisited=remaining; visited = CellSet.add c s.visited})
let eatcell c s = {s with eaten = c::s.eaten; visited = CellSet.add c s.visited; moretoeat = s.moretoeat - 1}
let keepcell c s =Actually c
is not used… {s with
beensize = s.beensize + 1; visited = CellSet.add c s.visited}
let freshisland s =We increase been_size
at the start of
find_island
{s with beensize = 0;rather than before calling
it. beenislands = s.beenislands + 1}
let initstate unvisited moretoeat = { beensize =5mm 0; beenislands =
0;
unvisited; visited = CellSet.empty; eaten = []; moretoeat;}
We need a state to begin with:
let initstate unvisited moretoeat = { beensize = 0; beenislands =
0;
unvisited; visited = CellSet.empty; eaten = []; moretoeat;}
The “main loop” only changes because of the different handling of state.
let rec findboard s = match visitcell s with | None -> if
s.beenislands = numislands then [eaten] else [] | Some (cell, s)
->
findisland cell (freshisland s) |-> (fun s -> if s.beensize =
s.islandsize then findboard s else [])
In the “island loop” we only try actions that make sense:
and findisland current s = let s = keepcell current s in neighbors n
emptycells current |> concatfold (fun neighbor s ->
if CellSet.mem neighbor s.visited then [s] else let chooseeat =Guard
against actions that would fail. if s.moretoeat = 0 then [] else
[eatcell neighbor s] and choosekeep = if s.beensize >= islandsize
then [] else findisland neighbor s in chooseeat @ choosekeep) s in
Finally, we compute the required length of eaten
and
start searching.
let cellstoeat = List.length honey - islandsize * numislands in
findboard (initstate honey cellstoeat)
Puzzles can be presented by providing the general form of solutions, and additional requirements that the solutions must meet.
For many puzzles, the general form of solutions for a given problem can be decomposed into a fixed number of variables.
There is a general and often efficient scheme of solving constraint-based problems. Finite Domain Constraint Programming algorithm:
This general algorithm can be simplified. For example, in step (2.c), instead of splitting into two equal-sized parts, we can partition into a singleton and remainder, or partition “all the way” into several singletons.
The above definition of finite domain constraint solving algorithm is sketchy. Questions?
We will not discuss a complete implementation example, but you can exploit ideas from the algorithm in your homework.
Recall how we generated all subsequences of a list. Find (i.e. generate) all:
Using folding for the expression
data type, compute
the degree of the corresponding polynomial. See http://en.wikipedia.org/wiki/Degree_of_a_polynomial.
Implement simplification of expressions using mapping for the
expression
data type.
Express in terms of fold_left
or
fold_right
:
concat_fold
, as used in the solution of Honey
Islands puzzle:
encode [‘a;‘a;‘a;‘a;‘b;‘c;‘c;‘a;‘a;‘d] = [4,‘a; 1,‘b; 2,‘c; 2,‘a; 1,‘d]
list_diff
that
computes the difference of sets represented as sorted lists.is_unique
in the provided code takes quadratic time –
optimize it.Write functions compose
and perform
that take a list of functions and return their composition, i.e. a
function compose [f1; …; fn] = x ↦ f1 (… (fn x)…)
and
perform [f1; …; fn] = x ↦ fn (… (f1 x)…)
.
Write a solver for the Tents Puzzle http://www.mathsisfun.com/games/tents-puzzle.html.
* Robot Squad. We are given a map of terrain with empty spaces and walls, and lidar readings for multiple robots, 8 readings of the distance to wall or another robot, for each robot. Robots are equipped with compasses, the lidar readings are in directions E, NE, N, NW, W, SW, S, SE. Determine the possible positions of robots.
* Write a solver for the Plinx Puzzle http://www.mathsisfun.com/games/plinx-puzzle.html. It does not need to always return correct solutions but it should correctly solve the initial levels from the game.
Lazy evaluation. Stream processing.
M. Douglas McIlroy ‘‘Power Series, Power Serious’’
Oleg Kiselyov, Simon Peyton-Jones, Amr Sabry ‘‘Lazy v. Yield: Incremental, Linear Pretty-Printing’’
If you see any error on the slides, let me know!
Evaluation strategy is the order in which expressions are computed.
Recall our problems with using flow control expressions
like if_then_else
in examples from \lambda-calculus lecture.
There are many technical terms describing various strategies. Wikipedia:
Strict evaluationArguments are always evaluated completely before function is applied. Non-strict evaluationArguments are not evaluated unless they are actually used in the evaluation of the function body. Eager evaluationAn expression is evaluated as soon as it gets bound to a variable. Lazy evaluationNon-strict evaluation which avoids repeating computation. Call-by-valueThe argument expression is evaluated, and the resulting value is bound to the corresponding variable in the function (frequently by copying the value into a new memory region). Call-by-referenceA function receives an implicit reference to a variable used as argument, rather than a copy of its value.
Almost all languages do not compute inside the body of un-applied function, but with curried functions you can pre-compute data before all arguments are provided.
search_bible
example.In eager / call-by-value languages we can simulate call-by-name by taking a function to compute the value as an argument instead of the value directly.
unit
type with a single value ()
specifically for use as throw-away arguments.ML languages have built-in support for lazy evaluation.
Haskell has built-in support for eager evaluation.
Call-by-name is useful not only for implementing flow control
but also for arguments of value constructors, i.e. for data structures.
Streams are lists with call-by-name tails.
type ’a stream = SNil | SCons of ’a * (unit -> ’a stream)
Reading from a stream into a list.
let rec stake n = function | SCons (a, s) when n > 0 -> a::(stake (n-1) (s ())) | -> []
Streams can easily be infinite.
let rec sones = SCons (1, fun () -> sones)let rec sfrom n = SCons (n, fun () ->sfrom (n+1))
Streams admit list-like operations.
let rec smap f = function | SNil -> SNil | SCons (a, s) -> SCons (f a, fun () -> smap f (s ()))let rec szip = function | SNil, SNil -> SNil | SCons (a1, s1), SCons (a2, s2) -> SCons ((a1, a2), fun () -> szip (s1 (), s2 ())) | -> raise (Invalidargument “szip”)
Streams can provide scaffolding for recursive algorithms:
let rec sfib = SCons (1, fun () -> smap (fun (a,b)-> a+b) (szip (sfib, SCons (1, fun () -> sfib))))
-3.45314-0.974534-3.85530493451515-0.297195396216431-3.262633946289190.020306918904617-3.89764-0.254862-3.876471755523220.464810160074084-3.241467125281120.930480222251621-2.33129-1.0592-2.60646249503903-0.276028575208361-2.267793358909910.0414737399126869-1.46345-0.9957-1.73862283370816-0.276028575208361-1.442287339595180.0626405609207567-1.73862-0.254862-2.013791506813070.676478370154782-1.548121444635531.03631432729197-2.62763-0.276029-2.796963884111650.507143802090224-2.373627463950261.0151475062839+-3.7833-0.582834+-2.55958-0.567776+-1.69025-0.5390640cm
Streams are less functional than could be expected in context of input-output effects.
let filestream name = let ch = openin name in let rec chreadline ()
=
try SCons (inputline ch, chreadline) with Endoffile -> SNil in
chreadline ()
OCaml Batteries use a stream type enum
for
interfacing between various sequence-like data types.
Lazy evaluation is more general than call-by-need as any value can be lazy, not only a function parameter.
A lazy value is a value that “holds” an expression until its result is needed, and from then on it “holds” the result.
In OCaml, we build lazy values explicitly. In Haskell, all values are lazy but functions can have call-by-value parameters which “need” the argument.
To create a lazy value: lazy expr – where expr
is
the suspended computation.
Two ways to use a lazy value, be careful when the result is computed!
Lazy lists:
type ’a llist = LNil | LCons of ’a * ’a llist Lazy.t
Reading from a lazy list into a list:
let rec ltake n = function | LCons (a, lazy l) when n > 0 -> a::(ltake (n-1) l) | -> []
Lazy lists can easily be infinite:
let rec lones = LCons (1, lazy lones)let rec lfrom n = LCons (n, lazy (lfrom (n+1)))
Read once, access multiple times:
let filellist name = let ch = openin name in let rec chreadline ()
=
try LCons (inputline ch, lazy (chreadline ())) with Endoffile -> LNil
in chreadline ()
let rec lzip = function | LNil, LNil -> LNil | LCons (a1, ll1), LCons (a2, ll2) -> LCons ((a1, a2), lazy ( lzip (Lazy.force ll1, Lazy.force ll2))) | -> raise (Invalidargument “lzip”)
let rec lmap f = function | LNil -> LNil | LCons (a, ll) -> LCons (f a, lazy (lmap f (Lazy.force ll)))
let posnums = lfrom 1let rec lfact = LCons (1, lazy (lmap (fun (a,b)-> a*b) (lzip (lfact, posnums))))
-3.24858-1.02259-3.5449133483265-0.239416589495965-3.290911496229660.0569189046170128-2.40191-1.00142-2.65590686598756-0.281750231512105-2.380738192882660.0145852626008731-1.5129-0.980255-1.70339992062442-0.218249768487895-1.512898531551790.0145852626008731-0.327557-1.02259-0.772059796269348-0.0489152004233364-0.3063897340918110.1839198306654320.878952-1.022590.392115359174494-0.2394165894959650.6037835692551920.0780857256250827-3.56608-0.260583-3.629580632358780.691923534859108-3.33324513824581.03059267098823-2.67707-0.260583-2.69824050800370.734257176875248-2.42307183489881.0517594919963-1.7034-0.21825-1.872734488688980.670756713851039-1.555232173567931.0517594919963-0.77206-0.0489152-0.9413943643339070.903591744939807-0.6238920492128591.094093134012440.392115-0.2394170.2862812541341450.6284230718348990.6249503902632621.0517594919963-2.60922-0.546619-1.66302-0.503353-0.632667-0.50820.504955-0.500198*-3.48713460768869-0.5100825754830590cm
Differential equations idea due to Henning Thielemann. Just an example.
Expression P (x) = \sum_{i = 0}^n a_{i} x^i defines a polynomial for n < \infty and a power series for n = \infty.
If we define
let rec lfoldright f l base = match l with | LNil -> base | LCons (a, lazy l) -> f a (lfoldright f l base)
then we can compute polynomials
let horner x l = lfoldright (fun c sum -> c +. x *. sum) l 0.
But it will not work for infinite power series!
If the power series converges for x > 1, then when the elements a_{n} get small, the remaining sum \sum_{i = n}^{\infty} a_{i} x^i is also small.
lfold_right
falls into an infinite loop on infinite
lists. We need call-by-name / call-by-need semantics for the argument
function f
.
let rec lazyfoldr f l base = match l with | LNil -> base | LCons (a, ll) -> f a (lazy (lazyfoldr f (Lazy.force ll) base))
We need a stopping condition in the Horner algorithm step:
let lhorner x l =This is a bit of a hack, let upd c sum =we hope to ‘‘hit’’ the interval (0, \varepsilon]. if c = 0. || absfloat c > epsilonfloat then c +. x *. Lazy.force sum else 0. in lazyfoldr upd l 0.
let invfact = lmap (fun n -> 1. /. floatofint n) lfactlet e = lhorner 1. invfact
\frac{\mathrm{d} \sin x}{\mathrm{d} x} = \cos x, \frac{\mathrm{d} \cos x}{\mathrm{d} x} = - \sin x, \sin 0 = 0, \cos 0 = 1.
We will solve the corresponding integral equations. Why?
We cannot define the integral by direct recursion like this:
let rec sin = integrate (ofint 0) cosUnary op. let (\sim-:) =and cos = integrate (ofint 1) \sim-:sin lmap (fun x-> \sim-.x)
unfortunately fails:
Error: This kind of expression is not allowed as right-hand side of ‘let rec'
integrate
to
call-by-need does not help, because OCaml cannot represent the values
that x
and y
refer to.We need to inline a bit of integrate
so that OCaml
knows how to start building the recursive structure.
let integ xs = lmap (uncurry (/.)) (lzip (xs, posnums))let rec sin = LCons (ofint 0, lazy (integ cos))and cos = LCons (ofint 1, lazy (integ \sim-:sin))
The complete example would look much more elegant in Haskell.
Although this approach is not limited to linear equations, equations like Lotka-Volterra or Lorentz are not “solvable” – computed coefficients quickly grow instead of quickly falling…
Drawing functions are like in previous lecture, but with open curves.
let plot1D f \simw \simscale \simtbeg \simtend = let dt = (tend -. tbeg) /. ofint w in Array.init w (fun i -> let y = lhorner (dt . ofint i) f in i, to_int (scale . y))
Putting it all together reveals drastic numerical errors for large x.
let graph = let scale = ofint h /. ofint 8 in [plot1D sin \simw \simh0:(h/2) \simscale \simtbeg:(ofint 0) \simtend:(ofint 15),
(250,250,0); plot1D cos \simw \simh0:(h/2) \simscale
\simtbeg:(ofint 0) \simtend:(ofint 15), (250,0,250)]let () =
drawtoscreen \simw \simh graph
For infinite precision on rational numbers we use the
nums
library.
Generate a sequence of approximations to the power series limit at x.
let infhorner x l = let upd c sum = LCons (c, lazy (lmap (fun apx -> c+.x*.apx) (Lazy.force sum))) in lazyfoldr upd l (LCons (ofint 0, lazy LNil))
Find where the series converges – as far as a given test is concerned.
let rec exact f = functionWe arbitrarily decide that convergence is | LNil -> assert falsewhen three consecutive results are the same. | LCons (x0, lazy (LCons (x1, lazy (LCons (x2, ))))) when f x0 = f x1 && f x0 = f x2 -> f x0 | LCons (, lazy tl) -> exact f tl
Draw the pixels of the graph at exact coordinates.
let plot1D f \simw \simh0 \simscale \simtbeg \simtend = let dt = (tend -. tbeg) /. ofint w in let eval = exact (fun y-> toint (scale . y)) in Array.init w (fun i -> let y = infhorner (tbeg +. dt . ofint i) f in i, h0 + eval y)
Success! If a power series had every third term contributing we
would have to check three terms in the function exact
…
lhorner
test for
f x0 = f x1 && not x0 =. x1
Example n_chain
: nuclear chain reaction–A decays
into B decays into C
let nchain \simnA0 \simnB0 \simlA \simlB = let rec nA = LCons (nA0, lazy (integ (\sim-.lA :. nA))) and nB = LCons (nB0, lazy (integ (\sim-.lB :. nB +: lA *:. nA))) in nA, nB
Without delayed computation, the ability to define data structures with referential cycles is very limited.
Double-linked lists contain such cycles between any two nodes even if they are not cyclic when following only forward or backward links.
-5.517280.0390925-4.670607884640830.102592935573489-4.564773779600480.0602592935573488-4.1626-0.00324117-4.88227609472152-0.214909379547559-5.05161066278608-0.10907527450721-2.215260.0179257-1.516751554438420.229593861621908-1.410917449398070.123759756581558-0.987581-0.00324117-1.68608612250298-0.15140891652335-1.8130870485514-0.06674163249107020.9809330.01792571.615937954755920.1660933985976981.827606164836620.01792565154120922.20861-0.06674161.57360431273978-0.2784098425717691.40426974467522-0.151408916523354.198290.03909254.812127926974470.1872602196057684.981462495039030.1237597565815585.44713-0.0244084.74862746395026-0.172575737531424.5792928958857-0.151408916523350cm
We need to “break” the cycles by making some links lazy.
type ’a dllist = DLNil | DLCons of ’a dllist Lazy.t * ’a * ’a dllist
let rec dldrop n l = match l with | DLCons (, x, xs) when n>0 -> dldrop (n-1) xs | -> l
let dllistoflist l = let rec dllist prev l = match l with | [] -> DLNil | x::xs -> let rec cell = lazy (DLCons (prev, x, dllist cell xs)) in Lazy.force cell in dllist (lazy DLNil) l
let rec dltake n l = match l with | DLCons (, x, xs) when n>0 -> x::dltake (n-1) xs | -> []
let rec dlbackwards n l = match l with | DLCons (lazy xs, x, ) when n>0 -> x::dlbackwards (n-1) xs | -> []
The stream type used a throwaway argument to make a suspension
type ’a stream = SNil | SCons of ’a * (unit -> ’a stream)
What if we take a real argument?
type (’a, ’b) iostream = EOS | More of ’b * (’a -> (’a, ’b) iostream)
A stream that for a single input value produces an output value.
type ’a istream = (unit, ’a) iostreamInput stream produces output when “asked”.
type ’a ostream = (’a, unit) iostreamOutput stream consumes provided input.
We can compose streams: directing output of one to input of another.
let rec compose sf sg = match sg with | EOS -> EOSNo more output.| More (z, g) -> match sf withNo more | EOS -> More (z, fun -> EOS)input ‘‘processing power’’. | More (y, f) -> let update x = compose (f x) (g y) in More (z, update)
We need a more flexible input-output stream definition.
After Haskell, we call the data structure pipe
.
type (’a, ’b) pipe = EOP| Yield of ’b * (’a, ’b) pipe
For
incremental streams change to lazy.|Await of ’a -> (’a, ’b)
pipe
Again, we can have producing output only input pipes and consuming input only output pipes.
type ’a ipipe = (unit, ’a) pipetype voidtype ’a opipe = (’a, void) pipe
void
rather than unit
, and why only
for opipe
?Composition of pipes is like “concatenating them in space” or connecting boxes:
let rec compose pf pg = match pg with | EOP -> EOPDone producing
results. | Yield (z, pg’) -> Yield (z, compose pf pg’)Ready result. |
Await g -> match pf with | EOP -> EOPEnd of input. | Yield (y,
pf’) -> compose pf’ (g y)Compute next result. | Await f ->
let update x = compose (f x) pg in Await updateWait for more input.
let (>->) pf pg = compose pf pg
Appending pipes means “concatenating them in time” or adding more fuel to a box:
let rec append pf pg = match pf with | EOP -> pg
When
pf
runs out, use pg
.|Yield (z, pf’) ->
Yield (z, append pf’ pg) | Await f ->If pf
awaits input,
continue when it comes. let update x = append (f x) pg in Await
update
Append a list of ready results in front of a pipe.
let rec yieldall l tail = match l with | [] -> tail | x::xs -> Yield (x, yieldall xs tail)
Iterate a pipe (not functional).
let rec iterate f : ’a opipe = Await (fun x -> let () = f x in iterate
Print hierarchically organized document with a limited line width.
type doc = Text of string | Line | Cat of doc * doc | Group of doc
let (++) d1 d2 = Cat (d1, Cat (Line, d2))let (!) s = Text slet
testdoc =
Group (!“Document” ++ Group (!“First part” ++ !“Second part”))
let () = printendline (pretty 30 testdoc);;
#
DocumentFirst part Second partlet () = printendline (pretty 20 testdoc);;
#
DocumentFirst partSecond partlet () = printendline (pretty 60 testdoc);;
# Document First part Second part
Straightforward solution:
let pretty w d =Allowed width of line w
. let rec width =
functionTotal length of subdocument. | Text z -> String.length z |
Line -> 1 | Cat (d1, d2) -> width d1 + width d2 | Group d ->
width d in
let rec format f r = functionRemaining space r
. | Text z
-> z, r - String.length z | Line when f -> ” “, r-1If
not f
then line breaks. | Line ->”“, w | Cat (d1, d2)
-> let s1, r = format f r d1 in let s2, r = format f r d2 in s1 s2,
r
If following group fits, then without line breaks.| Group
d -> format (f || width d <=
Working with a stream of nodes.
type (’a, ’b) doce =Annotated nodes, special for group beginning. TE of ’a
Normalize a subdocument – remove empty groups.
let rec norm = function | Group d -> norm d | Text “” -> None | Cat (Text ““, d) -> norm d | d -> Some d
Generate the stream by infix traversal.
let rec gen = function | Text z -> Yield (TE ((),z), EOP) | Line -> Yield (LE (), EOP) | Cat (d1, d2) -> append (gen d1) (gen d2) | Group d -> match norm d with | None -> EOP | Some d -> Yield (GBeg (), append (gen d) (Yield (GEnd (), EOP)))
Compute lengths of document prefixes, i.e. the position of each node counting by characters from the beginning of document.
let rec docpos curpos = Await (functionWe input from a
doc_e
pipe | TE (, z) -> Yield (TE (curpos, z),and
output doc_e
annotated with position. docpos (curpos +
String.length z)) | LE ->Spice and line breaks increase position by
1. Yield (LE curpos, docpos (curpos + 1)) | GBeg ->Groups do not
increase position. Yield (GBeg curpos, docpos curpos) | GEnd -> Yield
(GEnd curpos, docpos curpos))
let docpos = docpos 0The whole document starts at 0.
Put the end position of the group into the group beginning marker, so that we can know whether to break it into multiple lines.
let rec grends grstack = Await (function | TE | LE as e -> (match
grstack with | [] -> Yield (e, grends [])We can yield only when |
gr::grs -> grends ((e::gr)::grs))no group is waiting. | GBeg ->
grends ([]::grstack)Wait for end of group. | GEnd endp -> match
grstack withEnd the group on top of stack. | [] -> failwith “grends:
unmatched group end marker” | [gr] ->Top group – we can yield
now.
yieldall (GBeg endp::List.rev (GEnd endp::gr)) (grends [])
| gr::par::grs ->Remember in parent group instead. let par = GEnd
endp::gr @ [GBeg endp] @ par in grends (par::grs))Could use
catenable lists above.
That’s waiting too long! We can stop waiting when the width of a group exceeds line limit. GBeg will not store end of group when it is irrelevant.
let rec grends w grstack = let flush tail =When the stack exceeds
width w
, yieldall
flush it – yield everything
in it.(revconcatmap \simprep:(GBeg
Toofar) snd grstack) tail inAbove: concatenate in rev. with
prep
before each part. Await (function | TE (curp, ) | LE
curp as e -> (match grstack withRemember beginning of groups in the
stack.
| [] -> Yield (e, grends w []) | (begp, ):: when curp-begp > w
-> flush (Yield (e, grends w [])) | (begp, gr)::grs -> grends w
((begp, e::gr)::grs)) | GBeg begp -> grends w ((begp, [])::grstack) |
GEnd endp as e -> match grstack withNo longer fail when the stack is
empty – | [] -> Yield (e, grends w [])could have been flushed. |
(begp, ):: when endp-begp > w -> flush (Yield (e, grends w [])) |
[, gr] ->If width not exceeded, yieldall
work as before
optimization.(GBeg (Pos endp)::List.rev (GEnd endp::gr)) (grends w []) |
(, gr)::(parbegp, par)::grs ->
let par = GEnd endp::gr @ [GBeg (Pos endp)] @ par in grends w ((parbegp,
par)::grs))
Initial stack is empty:
let grends w = grends w []
Finally we produce the resulting stream of strings.
let rec format w (inline, endlpos as st) =State: the stack of Await
(function‘‘group fits in line’’; position where end of line would be. |
TE (, z) -> Yield (z, format w st) | LE p when List.hd inline
->
Yield (” “, format w st)After return, line has w
free
space. | LE p -> Yield (”“, format w (inline, p+w)) | GBeg Toofar
->Group with end too far is not inline. format w (false::inline,
endlpos) | GBeg (Pos p) ->Group is inline if it ends soon enough.
format w ((p<=endlpos)::inline, endlpos) | GEnd -> format w
(List.tl inline, endlpos))
let format w = format w ([false], w)Break lines outside of groups.
Put the pipes together:
let prettyprint w doc =
-7.32332-0.0176611-6.8788199497288-0.0176610662786083-4.10597-0.0176611-3.64029633549411-0.0176610662786083-0.1477710.003505750.2755655509988090.003505754729461573.76809-0.01766114.233761079507870.003505754729461570cm
Factorize format
so that various line breaking
styles can be plugged in.
let rec breaks w (inline, endlpos as st) = Await (function | TE as e
-> Yield (e, breaks w st) | LE p when List.hd inline -> Yield (TE
(p, ” “), breaks w st) | LE p as e -> Yield (e, breaks w (inline,
p+w)) | GBeg Toofar as e -> Yield (e, breaks w (false::inline,
endlpos)) | GBeg (Pos p) as e -> Yield (e, breaks w
((p<=endlpos)::inline, endlpos)) | GEnd as e -> Yield (e, breaks w
(List.tl inline, endlpos)))let breaks w = breaks w ([false], w)
let rec emit = Await (function | TE (, z) -> Yield (z, emit) | LE
-> Yield (”n”, emit) | GBeg | GEnd -> emit)let prettyprint w doc =
gen doc >-> docpos >-> grends w >-> breaks w >->
emit >-> iterate printstring
Tests.
let (++) d1 d2 = Cat (d1, Cat (Line, d2))let (!) s = Text slet
testdoc =
Group (!“Document” ++ Group (!“First part” ++ !“Second part”))let
printedoc prp prep = function | TE (p,z) -> prp p; printendline
(“:”z) | LE p -> prp p; printendline “: endline” | GBeg ep -> prep
ep; printendline “: GBeg” | GEnd p -> prp p; printendline “: GEnd”let
noop () = ()let printpos = function | Pos p -> printint p | Toofar
-> printstring “Too far”let = gen testdoc >-> iterate
(printedoc noop noop)let = gen testdoc >-> docpos >->
iterate (printedoc printint printint)let = gen testdoc >-> docpos
>-> grends 20 >-> iterate (printedoc printint printpos)let =
gen testdoc >-> docpos >-> grends 30 >-> iterate
(printedoc printint printpos)let = gen testdoc >-> docpos
>-> grends 60 >-> iterate (printedoc printint printpos)let =
prettyprint 20 testdoclet = prettyprint 30 testdoclet = prettyprint 60
testdoc
Functional Programming
Streams and lazy evaluation
Exercise 1: My first impulse was to define lazy list functions as here:
let rec wrong_lzip = function | LNil, LNil -> LNil | LCons (a1, lazy l1), LCons (a2, lazy l2) -> LCons ((a1, a2), lazy (wrong_lzip (l1, l2))) | -> raise (Invalidargument “lzip”)let rec wrong_lmap f = function | LNil -> LNil | LCons (a, lazy l) -> LCons (f a, lazy (wrong_lmap f l))
What is wrong with these definitions – for which edge cases they do not work as intended?
Exercise 2: Cyclic lazy lists:
Implement a function
*cycle : 'a list -> 'a llist*
that creates a lazy
list with elements from standard list, and the whole list as the tail
after the last element from the input list.
*[a1; a2; …; aN]
\mapsto
… |
||||
-1.407730.189096-0.9632226484984790.210262600873131-0.2012170.1890960.2644529699695730.2102626008731310.751290.1890961.068792168276230.1890957798650621.788460.1890962.190633681703930.2102626008731312.19063368170393-0.191906998280196-2.0-0.2-2.0003968778939-0.001405609207567140cm*
Your function cycle
can either return
LNil
or fail for an empty list as argument. 1.
Note that *inv_fact*
from the lecture defines
the power series for the \exp
(\cdot) function (\exp (x) =
e^x). Using *cycle*
and
*inv_fact*
, define the power series for \sin (\cdot) and \cos (\cdot), and draw their graphs
using helper functions from the lecture script
*Lec7.ml*
.
Exercise 3: * Modify one of the puzzle solving programs (either from the previous lecture or from your previous homework) to work with lazy lists. Implement the necessary higher-order lazy list functions. Check that indeed displaying only the first solution when there are multiple solutions in the result takes shorter than computing solutions by the original program.
Exercise 4: Hamming’s problem. Generate in increasing order the numbers of the form 2^{a_{1}} 3^{a_{2}} 5^{a_{3}} \ldots p_{k}^{a_{k}}, that is numbers not divisible by prime numbers greater than the kth prime number.
to* http://en.wikipedia.org/wiki/Regular_number.
Starter code is available in the middle of the lecture script
Lec7.ml
:let rec lfilter f = function | LNil -> LNil |
LCons (n, ll) -> if f n then LCons (n, lazy (lfilter f (Lazy.force
ll))) else lfilter f (Lazy.force ll)let primes = let rec sieve =
function LCons(p,nf) -> LCons(p, lazy (sieve (sift p (Lazy.force
nf)))) | LNil -> failwith “Impossible! Internal error.” and sift p =
lfilter (function n -> n mod p <> 0)in sieve (lfrom 2)let times
ll n = lmap (fun i -> i n) ll;;let rec merge xs ys = match xs,
ys with | LCons (x, lazy xr), LCons (y, lazy yr) -> if x < y then
LCons (x, lazy (merge xr ys)) else if x > y then LCons (y, lazy
(merge xs yr)) else LCons (x, lazy (merge xr yr)) | r, LNil | LNil, r
-> rlet hamming k = let pr = ltake k primes in let rec h = LCons (1,
lazy (
Exercise 5: Modify format
and/or
breaks
to use just a single number instead of a stack of
booleans to keep track of what groups should be inlined.
Exercise 6: Add indentation to the
pretty-printer for groups: if a group does not fit in a single line, its
consecutive lines are indented by a given amount tab
of
spaces deeper than its parent group lines would be. For comparison,
let’s do several implementations.
*pretty*
.*pretty*
by modifying the *format*
function.*pretty*
by modifying the *breaks*
function. Recover the positions of elements – the number of
characters from the beginning of the document – by keeping track of the
growing offset.Exercise 7: Write a pipe that takes document elements annotated with linear position, and produces document elements annotated with (line, column) coordinates.
Write another pipe that takes so annotated elements and adds a
line number indicator in front of each line. Do not update the column
coordinate. Test the pipes by plugging them before the emit
pipe.
1: first line
2: second line, etc.
Exercise 8: Write a pipe that consumes document
elements doc_e
and yields the toplevel subdocuments
doc
which would generate the corresponding elements.
You can modify the definition of documents to allow annotations,
so that the element annotations are preserved (gen
should
ignore annotations to keep things simple):type ’a doc = Text of ’a
string | Line of ’a | Cat of doc * doc | Group of ’a * doc*
Exercise 9: * Design and implement a way to duplicate arrows outgoing from a pipe-box, that would memoize the stream, i.e. not recompute everything “upstream” for the composition of pipes. Such duplicated arrows would behave nicely with pipes reading from files.
*Does not recompute g nor f. | Reads once and passes all content to f and g. |
-5.769180.217059-4.245171318957530.217059134806191-3.800670.238226-2.44599153327160.746229660007938-3.821830.259393-2.4459915332716-0.2909445693874851.088870.2170593.988722053181640.7250628389998681.046530.1958923.96755523217357-0.3332782114036250cm*
List comprehensions. Basic monads; transformers. Probabilistic Programming.Lightweight cooperative threads.
Some examples from Tomasz Wierzbicki. Jeff Newbern ‘‘All About Monads’‘.M. Erwig, S. Kollmansberger ‘‘Probabilistic Functional Programming in Haskell’‘.Jerome Vouillon ‘‘Lwt: a Cooperative Thread Library’’.
If you see any error on the slides, let me know!
Recall the awkward syntax we used in the Countdown Problem example:
Brute-force generation:
let combine l r = List.map (fun o->App (o,l,r)) [Add; Sub; Mul; Div]let rec exprs = function | [] -> [] | [n] -> [Val n] | ns -> split ns |-> (fun (ls,rs) -> exprs ls |-> (fun l -> exprs rs |-> (fun r -> combine l r)))
Genarate-and-test scheme:
let guard p e = if p e then [e] else []let solutions ns n = choices ns |-> (fun ns’ -> exprs ns’ |-> guard (fun e -> eval e = Some n))
Recall that we introduced the operator
let ( |-> ) x f = concatmap f x
We can do better with list comprehensions syntax extension.
#load “dynlink.cma”;;#load “camlp4o.cma”;;#load “Camlp4Parsers/Camlp4ListComprehension.cmo”;;
let test = [i * 2 | i <- fromto 2 22; i mod 3 = 0]
What it means:
[expr | ] can be translated as [expr]
[expr | v <- generator; more] can be translated as
generator
|-> (fun v -> translation of [expr |
more])
[expr | condition
; more] can be translated
as
if condition then translation of [expr
| more]
else []
Revisiting the Countdown Problem code snippets:
Brute-force generation:
let rec exprs = function | [] -> [] | [n] -> [Val n] | ns -> [App (o,l,r) | (ls,rs) <- split ns; l <- exprs ls; r <- exprs rs; o <- [Add; Sub; Mul; Div]]
Genarate-and-test scheme:
let solutions ns n = [e | ns’ <- choices ns; e <- exprs ns’; eval e = Some n]
Subsequences using list comprehensions (with garbage):
let rec subseqs l = match l with | [] -> [[]] | x::xs -> [ys | px <- subseqs xs; ys <- [px; x::px]]
Computing permutations using list comprehensions:
via insertion
let rec insert x = function | [] -> [[x]] | y::ys’ as ys
->
(x::ys) :: [y::zs | zs <- insert x ys’]let rec insperms = function |
[] -> [[]] | x::xs -> [zs | ys <- insperms xs; zs <- insert
ys]
via selection
let rec select = function | [x] -> [x,[]] | x::xs -> (x,xs) :: [ y, x::ys | y,ys <- select xs]let rec selperms = function | [] -> [[]] | xs -> [x::ys | x,xs’ <- select xs; ys <- selperms xs’]
We need to install the syntax extension pa_monad
pa_monad.cmo or pa_monad400.cmo
(for
OCaml 4.0) file from the course page,camlp4-extras
let rec exprs = function | [] -> [] | [n] -> [Val n] | ns
->
perform with (|->) in (ls,rs) <– split ns; l <– exprs ls; r
<– exprs rs; o <– [Add; Sub; Mul; Div];
[App (o,l,r)]
The perform syntax does not seem to support guards…
let solutions ns n = perform with (|->) in ns’ <– choices
ns;
e <– exprs ns’; eval e = Some n; e
eval e = Some n; Error: This expression has type bool but an
expression was expected of type ’a list
So it wants a list… What can we do?
We can decide whether to return anything
let solutions ns n = perform with (|->) in ns’ <– choices
ns;
e <– exprs ns’; if eval e = Some n then [e] else []
But what if we want to check earlier…
General “guard check” function
let guard p = if p then [()] else []
let solutions ns n = perform with (|->) in ns’ <– choices
ns;
e <– exprs ns’; guard (eval e = Some n); [e]
A polymorphic type 'a monad
(or
'a Monad.t
, etc.) that supports at least two
operations:
bind : 'a monad -> ('a -> 'b monad) -> 'b monad
return : 'a -> 'a monad
= is infix syntax for
bind
: let (>>=) a b = bind a b
With bind
in scope, we do not need the with clause
in perform
let bind a b = concatmap b alet return x = [x] let solutions ns n
=
perform ns’ <– choices ns; e <– exprs ns’; guard (eval e = Some
n); return e
Why guard
looks this way?
let fail = []let guard p = if p then return () else fail
Throwing away the binding argument is a common practice, with infix syntax >> in Haskell, and supported in do-notation and perform.
Everything is a monad?
Different flavors of monads?
Can guard
be defined for any monad?
perform syntax in depth:
exp |
||
[] |
||
but uses b instead of bind |
||
and f instead of failwith |
||
during translation |
It can be useful to redefine: let failwith = fail (why?)
A parametric data type is a monad only if its bind
and return
operations meet axioms:
\begin{matrix} \operatorname{bind} (\operatorname{return}a) f & \approx & f a\\\\\\ \operatorname{bind}a (\lambda x.\operatorname{return}x) & \approx & a \\\\\\ \operatorname{bind} (\operatorname{bind}a (\lambda x.b)) (\lambda y.c) & \approx & \operatorname{bind}a (\lambda x.\operatorname{bind}b (\lambda y.c)) \end{matrix}
Check that the laws hold for our example monad
let bind a b = concatmap b alet return x = [x]
A monoid is a type with, at least, two operations
mzero : 'a monoid
mplus : 'a monoid -> 'a monoid -> 'a monoid
that meet the laws:
\begin{matrix} \operatorname{mplus}\operatorname{mzero}a & \approx & a \\\\\\ \operatorname{mplus}a\operatorname{mzero} & \approx & a \\\\\\ \operatorname{mplus}a (\operatorname{mplus}b c) & \approx & \operatorname{mplus} (\operatorname{mplus}a b) c \end{matrix}
We will define fail
as synonym for
mzero
and infix ++ for mplus
.
Fusing monads and monoids gives the most popular general flavor of monads which we call monad-plus after Haskell.
Monad-plus requires additional axioms that relate its “addition” and its “multiplication”.
\begin{matrix} \operatorname{bind}\operatorname{mzero}f & \approx & \operatorname{mzero}\\\\\\ \operatorname{bind}m (\lambda x.\operatorname{mzero}) & \approx & \operatorname{mzero} \end{matrix}
Using infix notation with \oplus
as mplus
, \boldsymbol{0}
as mzero
, \vartriangleright as bind
and
\boldsymbol{1} as return
,
we get monad-plus axioms
\begin{matrix} \boldsymbol{0} \oplus a & \approx & a \\\\\\ a \oplus \boldsymbol{0} & \approx & a \\\\\\ a \oplus (b \oplus c) & \approx & (a \oplus b) \oplus c\\\\\\ \boldsymbol{1}x \vartriangleright f & \approx & f x\\\\\\ a \vartriangleright \lambda x.\boldsymbol{1}x & \approx & a \\\\\\ (a \vartriangleright \lambda x.b) \vartriangleright \lambda y.c & \approx & a \vartriangleright (\lambda x.b \vartriangleright \lambda y.c)\\\\\\ \boldsymbol{0} \vartriangleright f & \approx & \boldsymbol{0}\\\\\\ a \vartriangleright (\lambda x.\boldsymbol{0}) & \approx & \boldsymbol{0} \end{matrix}
The list type has a natural monad and monoid structure
let mzero = [] let mplus = (@) let bind a b = concatmap b a let return a = [a]
We can define in any monad-plus
let fail = mzero let failwith = fail let (++) = mplus let (>>=) a b = bind a b let guard p = if p then return () else fail
We have seen mzero
, i.e. fail
in the
countdown problem. What about mplus
?
let findtoeat n islandsize numislands emptycells = let honey =
honeycells n emptycells in let rec findboard s = (* Printf.printf
“findboard: %sn” (statestr s); ) match visitcell s with | None ->
perform
guard (s.beenislands = numislands); return s.eaten | Some (cell, s)
-> perform s <– findisland cell (freshisland s);
guard (s.beensize = islandsize); findboard s and findisland current s =
let s = keepcell current s in neighbors n emptycells current
|> foldM (fun neighbor s -> if CellSet.mem neighbor s.visited then
return s else let chooseeat =
if s.moretoeat <= 0 then fail else return (eatcell neighbor s) and
choosekeep = if s.beensize >= islandsize then fail else findisland
neighbor s in mplus chooseeat choosekeep) s in let cellstoeat =
List.length honey - islandsize numislands in findboard (initstate
honey cellstoeat)
'a monad
rather than
parameterize with an additional type ('s, 'a) monad
.bind
and
return
).
Monads with access:
access : ’a monad -> ’a
Example: the lazy monad.
Monad-plus, non-deterministic computation:
mzero : 'a monad``mplus : 'a monad -> 'a monad -> 'a monad
Monads with environment or state – parameterized by type
store
:
get : store monadput : store -> unit monad
There is a “canonical” state monad. Similar monads: the writer monad
(with get
called listen
and put
called tell
); the reader monad, without put
,
but with get
(called ask
) and
local
:
local : (store -> store) -> ’a monad -> ’a monad
The exception / error monads – parameterized by type
excn
:
throw : excn -> ’a monadcatch : ’a monad -> (excn -> ’a monad) -> ’a monad
The continuation monad:
callCC : ((’a -> ’b monad) -> ’a monad) -> ’a monad
We will not cover it.
Probabilistic computation:
choose : float -> ’a monad -> ’a monad -> ’a monad
satisfying the laws with a \oplus _{p}
b for choose p a b
and pq for p*.q
, 0 \leqslant p, q \leqslant 1:
\begin{matrix} a \oplus _{0} b & \approx & b \\\\\\ a \oplus _{p} b & \approx & b \oplus _{1 - p} a\\\\\\ a \oplus _{p} (b \oplus _{q} c) & \approx & \left( a \oplus _{\frac{p}{p + q - pq}} b \right) \oplus _{p + q - pq} c\\\\\\ a \oplus _{p} a & \approx & a \end{matrix}
Parallel computation as monad with access and parallel bind:
parallel :’a monad-> ’b monad-> (’a -> ’b -> ’c monad) -> ’c monad
Example: lightweight threads.
I provide below much more information about the module system than we need, just for completeness. You can use it as reference.
Modules collect related type definitions and operations together.
Module “values” are introduced with struct … end – structures.
Module types are introduced with sig … end – signatures.
A source file source.ml
or Source.ml
defines a module Source.
A source file source.mli
or Source.mli
defines its type.
We can create the initial interface by entering the module in the
interactive toplevel or by command
ocamlc -i source.ml
In the “toplevel” – accurately, module level – modules are defined with module ModuleName = … or module ModuleName : MODULE_TYPE = … syntax, and module types with module type MODULETYPE = … syntax.
v_name
= … resp. let
v_name
: v_type = … syntax for values and type vtype = …
syntax for types.Locally in expressions, modules are defined with let module M = … in … syntax.
v_name
= … in … syntax for
values.The content of a module is made visible in the remainder of another module by open Module
The content of a module is made visible locally in an expression with let open Module in … syntax.
Content of a module is included into another module – i.e. made part of it – by include Module.
Module functions – functions from modules to modules – are called functors (not the Haskell ones!). The type of the parameter has to be given.
module Funct = functor (Arg : sig … end) -> struct … end
module Funct (Arg : sig … end) = struct … end
Functor application always uses parentheses: Funct (struct … end)
We can use named module type instead of signature and named module instead of structure above.
Argument structures can contain more definitions than required.
A signature MODULETYPE with type t_name = … is like MODULETYPE
but with t_name
made more specific.
We can also include signatures into other signatures, by include MODULETYPE.
t_name
with provided type.Modules, just as expressions, are not recursive or mutually recursive by default. Syntax for recursive modules:module rec ModuleName : MODULETYPE = … and …
We can recover the type – i.e. signature – of a module bymodule type of Module
Finally, we can pass around modules in normal functions.
module type T = sig val g : int -> int endlet f modv x = let module
# val modv : T) in M.g x;;
M = (
val f : (module T) -> int -> int = <fun>
let test = f (module struct let g i = i*i end : T);;
#
val test : int -> int = <fun>
'a monad
contains stuff of type 'a
'a monad
is a
special way to compute 'a
.
we can put something into the container with
return
we can operate on it, but the result needs to stay in the container
let lift f m = perform x <– m; return (f x) val lift : (’a -> ’b) -> ’a monad -> ’b monad
We can deactivate-unwrap the quarantine container but only when it is in another container so the quarantine is not broken
let join m = perform x <– m; x val join : (’a monad) monad -> ’a monad
run
operation that
exposes “what really happened behind the quarantine”.To compute the result, perform instructions, naming partial results.
Physical metaphor: assembly line
-5.762933.70556.069321338801433.72666688715439-5.72061.990996.217489085857921.990987564492666.069323.726678.926842174890862.7741599417912410.0486836883186-0.1045277153062576.217491.990996.386823653922481.736985712395826.76782643206773-0.08336089429818766.76783-0.0833609-5.29726154253208-0.1045277153062576.72549-1.84021-5.19142743749173-1.819040216959920.6717822.879992.3016271993652.879994046831597.720331.863998.12250297658420.7209783040084676.00582-0.9723674.69347797327689-0.9723673766371212.06879-0.9088670.735282444767826-0.908866913612912-5.762932.87999-4.619923270273852.87999404683159-4.42942-0.930034-5.53009657362085-0.930033734620982-9.763462.68949-8.324116946686072.26615623759757-7.308109538298720.9749801561053052.280462.371993.175100860066451.990988185174813.656303743881470.9749801561053053.88914-1.289873.31763460775235-2.263543458129383.29646778674428-2.83504762534727-4.26009-1.18404-4.64394551581309-1.82001279007382-4.72575737531419-2.72921352030692w
-7.42741.25543c
3.596731.32033c'
3.5293-2.47521c''
-4.93743-1.586216.280990.911486.76783-0.0833609-4.408260.91148-3.916971543196911.990989632039070.206112-2.89855-0.600035853371303-1.827196584929880cm
let assemblyLine w = perform c <– makeChopsticks w c’ <– polishChopsticks c c’’ <– wrapChopsticks c’ return c’’
Any expression can be spread over a monad, e.g. for \lambda-terms:
\begin{matrix} \llbracket N \rrbracket = & \operatorname{return}N & \text{(constant)}\\\\\\ \llbracket x \rrbracket = & \operatorname{return}x & \text{(variable)}\\\\\\ \llbracket \lambda x.a \rrbracket = & \operatorname{return} (\lambda x. \llbracket a \rrbracket) & \text{(function)}\\\\\\ \llbracket \operatorname{let}x = a\operatorname{in}b \rrbracket = & \operatorname{bind} \llbracket a \rrbracket (\lambda x. \llbracket b \rrbracket) & \text{(local definition)}\\\\\\ \llbracket a b \rrbracket = & \operatorname{bind} \llbracket a \rrbracket (\lambda v_{a} .\operatorname{bind} \llbracket b \rrbracket (\lambda v_{b} .v_{a} v_{b})) & \text{(application)} \end{matrix}
When an expression is spread over a monad, its computation can be monitored or affected without modifying the expression.
To implement a monad we need to provide the implementation type,
return
and bind
operations.
module type MONAD = sig type ’a t val return : ’a -> ’a t val bind : ’a t -> (’a -> ’b t) -> ’b tend
Alternatively we could start from return
,
lift
and join
operations.
Based on just these two operations, we can define a whole suite of general-purpose functions. We look at just a tiny selection.
module type MONADOPS = sig type ’a monad include MONAD with type ’a t
:= ’a monad val ( >>= ) :’a monad -> (’a -> ’b monad) ->
’b monad val foldM : (’a -> ’b -> ’a monad) -> ’a -> ’b list
-> ’a monad val whenM : bool -> unit monad -> unit monad
val lift : (’a -> ’b) -> ’a monad -> 'b monad
val
(>>|) : ’a monad -> (’a -> ’b) -> ’b monadval join : ’a
monad monad -> ’a monad val ( >=> ) : (’a ->’b monad) ->
(’b ->’c monad) -> ’a -> ’c monadend
Given a particular implementation, we define these functions.
module MonadOps (M : MONAD) = struct open M type ‘a monad = ’a t let run x = x let (>>=) a b = bind a b let rec foldM f a = function | [] -> return a | x::xs -> f a x >>= fun a’ -> foldM f a’ xs let whenM p s = if p then s else return () let lift f m = perform x <– m; return (f x) let (>>|) a b = lift b a let join m = perform x <– m; x let (>=>) f g = fun x -> f x >>= gend
We make the monad “safe” by keeping its type abstract. But
run
exposes “what really happened”.
module Monad (M : MONAD) :sig include MONADOPS val run : ’a monad -> ’a M.tend = struct include M include MonadOps(M)end
run
function does not do anything at all. Often
more useful functions are called run
but then they need to
be defined for each implementation separately. Our access
operation (see section on monad flavors) is often called
run
.The monad-plus class of monads has a lot of implementations. They
need to provide mzero
and mplus
.
module type MONADPLUS = sig include MONAD val mzero : ’a t val mplus : ’a t -> ’a t -> ’a tend
Monad-plus class also has its general-purpose functions:
module type MONADPLUSOPS = sig include MONADOPS val mzero : ’a monad val mplus : ’a monad -> ’a monad -> ’a monad val fail : ’a monad val (++) : ’a monad -> ’a monad -> ’a monad val guard : bool -> unit monad val msummap : (’a -> ’b monad) -> ’a list -> ’b monadend
We again separate the “implementation” and the “interface”.
module MonadPlusOps (M : MONADPLUS) = struct open M include
MonadOps(M)
let fail = mzero let (++) a b = mplus a b let guard p = if p then return
() else fail let msummap f l = List.foldright (fun a acc -> mplus
(f
module MonadPlus (M : MONADPLUS) :sig include MONADPLUSOPS val run : ’a monad -> ’a M.tend = struct include M include MonadPlusOps(M)end
We also need a class for computations with state.
module type STATE = sig type store type ’a t val get : store t val put : store -> unit tend
The purpose of this signature is inclusion in other signatures.
We do not define a class for monads with access since accessing means running the monad, not useful while in the monad.
Notation for laziness heavy? Try a monad! (Monads with access.)
module LazyM = Monad (struct type ’a t = ’a Lazy.t let bind a b = lazy (Lazy.force (b (Lazy.force a))) let return a = lazy aend)
let laccess m = Lazy.force (LazyM.run m)
Our resident list monad. (Monad-plus.)
module ListM = MonadPlus (struct type ’a t = ’a list let bind a b = concatmap b a let return a = [a] let mzero = [] let mplus = List.appendend)
module Countdown (M : MONADPLUSOPS) = struct open MOpen the module to make monad operations visible.
let rec insert x = functionAll choice-introducing operations | [] -> return [x]need to happen in the monad. | y::ys as xs -> return (x::xs) ++ perform xys <– insert x ys; return (y::xys)
let rec choices = function | [] -> return [] | x::xs -> perform
cxs <– choices xs;Choosing which numbers in what order
return cxs ++ insert x cxs
and now whether with or without
x
.
type op = Add | Sub | Mul | Div
let apply op x y = match op with | Add -> x + y | Sub -> x - y | Mul -> x * y | Div -> x / y
let valid op x y = match op with | Add -> x <= y | Sub -> x > y | Mul -> x <= y && x <> 1 && y <> 1 | Div -> x mod y = 0 && y <> 1
type expr = Val of int | App of op * expr * expr
let op2str = function | Add -> “+” | Sub -> “-” | Mul -> “*” | Div -> “/” let rec expr2str = functionWe will provide solutions as strings. | Val n -> stringofint n | App (op,l,r) ->“(”expr2str lop2str opexpr2str r”)”
let combine (l,x) (r,y) o = performTry out an operator. guard (valid o x y); return (App (o,l,r), apply o x y)
let split l =Another choice: which numbers go into which argument.
let rec aux lhs = function | [] | [] -> fail
Both
arguments need numbers.| [y; z] -> return (List.rev (y::lhs), [z]) |
hd::rhs ->
let lhs = hd::lhs in return (List.rev lhs, rhs) ++ aux lhs rhs in aux []
l
let rec results = functionBuild possible expressions once numbers |
[] -> fail
have been picked.| [n] -> perform guard (n
> 0); return (Val n, n) | ns -> perform (ls, rs) <– split ns;
lx <– results ls; ly <– results rs;Collect solutions using each
operator. msummap (combine lx ly) [Add; Sub; Mul; Div]
let solutions ns n = performSolve the problem: ns’ <– choices ns;pick numbers and their order, (e,m) <– results ns’;build possible expressions, guard (m=n);check if the expression gives target value, return (expr2str e)‘‘print’’ the solution.end
We will measure execution times:
#load “unix.cma”;;let time f = let tbeg = Unix.gettimeofday () in let res = f () in let tend = Unix.gettimeofday () in tend -. tbeg, res
Let’s check our generalized Countdown solver using original operations.
module ListCountdown = Countdown (ListM)let test1 () = ListM.run (ListCountdown.solutions [1;3;7;10;25;50] 765)let t1, sol1 = time test1
val t1 : float = 2.2856600284576416val sol1 : string list =
[“((25-(3+7))(1+50))”; ”(((25-3)-7)(1+50))”; …
What if we want only one solution? Laziness to the rescue!
type ’a llist = LNil | LCons of ’a * ’a llist Lazy.tlet rec ltake n =
function | LCons (a, lazy l) when n > 0 -> a::(ltake (n-1) l) |
-> []let rec lappend l1 l2 = match l1 with LNil -> l2 | LCons (hd,
tl) -> LCons (hd, lazy (lappend (Lazy.force tl) l2))let rec
lconcatmap f = function | LNil -> LNil | LCons (a, lazy l)
->
lappend (f a) (lconcatmap f l)
That is, another monad-plus.
module LListM = MonadPlus (struct type ’a t = ’a llist let bind a b = lconcatmap b a let return a = LCons (a, lazy LNil) let mzero = LNil let mplus = lappendend)
module LListCountdown = Countdown (LListM)let test2 () = LListM.run (LListCountdown.solutions [1;3;7;10;25;50] 765)
let t2a, sol2 = time test2;;val t2a : float = 2.51197600364685059val
# string llist = LCons ("((25-(3+7))*(1+50))", <lazy>) sol2 :
Not good, almost the same time to even get the lazy list!
let t2b, sol21 = time (fun () -> ltake 1 sol2);;val t2b : float
# 2.86102294921875e-06val sol21 : string list = ["((25-(3+7))*(1+50))"]#
= let t2c, sol29 = time (fun () -> ltake 10 sol2);;val t2c : float
9.059906005859375e-06val sol29 : string list = ["((25-(3+7))*(1+50))";
= "(((25-3)-7)*(1+50))"; …# let t2d, sol239 = time (fun () -> ltake
49 sol2);;val t2d : float = 4.00543212890625e-05val sol239 : string list =
"((25-(3+7))*(1+50))"; "(((25-3)-7)*(1+50))"; … [
Getting elements from the list shows they are almost already computed.
Wait! Perhaps we should not store all candidates when we are only interested in one.
module OptionM = MonadPlus (struct type ’a t = ’a option let bind a b
=
match a with None -> None | Some x -> b x let return a = Some
a
let mzero = None let mplus a b = match a with None -> b | Some ->
aend)
module OptCountdown = Countdown (OptionM)let test3 () = OptionM.run (OptCountdown.solutions [1;3;7;10;25;50] 765)
let t3, sol3 = time test3;;val t3 : float = 5.0067901611328125e-06val
# string option = None sol3 :
It very quickly computes… nothing. Why?
Maybe
monad in Haskell) good
for?Our lazy list type is not lazy enough.
a
++ b
or
msum_map
…, it computes the first candidate for each choice
path.We want the whole monad to be lazy: it’s called even lazy lists.
llist
are called odd lazy lists.type ’a lazylist = ’a lazylist Lazy.tand ’a lazylist = LazNil | LazCons of ’a * ’a lazylistlet rec laztake n = function | lazy (LazCons (a, l)) when n > 0 -> a::(laztake (n-1) l) | -> []let rec appendaux l1 l2 = match l1 with lazy LazNil -> Lazy.force l2 | lazy (LazCons (hd, tl)) -> LazCons (hd, lazy (appendaux tl l2))let lazappend l1 l2 = lazy (appendaux l1 l2)let rec concatmapaux f = function | lazy LazNil -> LazNil | lazy (LazCons (a, l)) -> appendaux (f a) (lazy (concatmapaux f l))let lazconcatmap f l = lazy (concatmapaux f l)
module LazyListM = MonadPlus (struct type ’a t = ’a lazylist let bind a b = lazconcatmap b a let return a = lazy (LazCons (a, lazy LazNil)) let mzero = lazy LazNil let mplus = lazappendend)
module LazyCountdown = Countdown (LazyListM)let test4 () = LazyListM.run (LazyCountdown.solutions [1;3;7;10;25;50] 765)
let t4a, sol4 = time test4;;val t4a : float = 2.86102294921875e-06val
# string lazylist = <lazy># let t4b, sol41 = time (fun
sol4 : 1 sol4);;val t4b : float = 0.367874860763549805val sol41 :
() -> laztake string list = ["((25-(3+7))*(1+50))"]# let t4c, sol49 = time (fun () ->
10 sol4);;val t4c : float = 0.234670877456665039val sol49 : string
laztake list = ["((25-(3+7))*(1+50))"; "(((25-3)-7)*(1+50))"; …# let t4d,
fun () -> laztake 49 sol4);;val t4d : float
sol439 = time (4.0594940185546875val sol439 : string list = ["((25-(3+7))*(1+50))";
= "(((25-3)-7)*(1+50))"; …
exn
, restoring some flexibility.throw
(called fail
there) and
catch
operations in its monad.module ExceptionM(Excn : sig type t end) : sig type excn = Excn.t
type ’a t = OK of ’a | Bad of excn include MONADOPS val run : ’a monad
-> ’a t
val throw : excn -> ’a monad val catch : ’a monad -> (excn ->
’a monad) -> ’a monadend = struct type excn = Excn.t
module M = struct type ’a t = OK of ’a | Bad of excn let return a = OK a let bind m b = match m with | OK a -> b a | Bad e -> Bad e end include M include MonadOps(M) let throw e = Bad e let catch m handler = match m with | OK -> m | Bad e -> handler eend
module StateM(Store : sig type t end) : sig type store =
Store.t
Pass the current store
value to get the
next value.type ‘a t = store -> ’a * store include MONADOPS include
STATE with type ’a t := ’a monad
and type store := store val run : ’a monad -> ’a tend = struct type
store = Store.t module M = struct type ’a t = store -> ’a *
store
let return a = fun s -> a, s
Keep the current value
unchanged.let bind m b = fun s -> let a, s’ = m s in b a s’ endTo
bind two steps, pass the value after first step to the second step.
include M include MonadOps(M) let get = fun s -> s,
s
Keep the value unchanged but put it in monad.let put s’ =
fun -> (), s’Change the value; a throwaway in monad.end
The state monad is useful to hide passing-around of a “current” value.
We will rename variables in \lambda-terms to get rid of possible name clashes.
type term =| Var of string| Lam of string * term| App of term * term
let (!) x = Var xlet (|->) x t = Lam (x, t)let (@) t1 t2 = App (t1, t2)let test = “x” |-> (“x” |-> !“y” @ !“x”) @ !“x”
module S = StateM(struct type t = int * (string * string) list end)open S
Without opening the module, we would write S.get
,
S.put
and perform with S in…
let rec alphaconv = function | Var x as v -> performFunction
from terms to StateM monad. (_, env) <– get;Seeing a variable does
not change state let v = try Var (List.assoc x env)but we need its new
name.
with Notfound -> v inFree variables don’t change name. return v | Lam
(x, t) -> performWe rename each bound variable. (fresh, env) <–
get;We need a fresh number. let x’ = x stringofint fresh in
put (fresh+1, (x, x’)::env);Remember new name, update number. t’ <–
alphaconv t; (fresh’, ) <– get;We need to restore names, put (fresh’,
env);but keep the number fresh. return (Lam (x’, t’)) | App (t1, t2)
-> perform t1 <– alphaconv t1;Passing around of names
t2 <– alphaconv t2;and the currently fresh number return (App (t1,
t2))is done by the monad.
val test : term = Lam (“x”, App (Lam (“x”, App (Var “y”, Var “x”)), Var “x”))# let = StateM.run (alphaconv test) (5, []);;- : term * (int * (string * string) list) =(Lam (“x5”, App (Lam (“x6”, App (Var “y”, Var “x6”)), Var “x5”)), (7, []))
If we separated the reader monad and the state monad, we would
avoid the lines: (fresh’, ) <– get;Restoring the ‘‘reader’’ part
env
put (fresh’, env);but preserving the ‘‘state’’ part
fresh
.
The elegant way is to define the monad locally:
let alphaconv t = let module S = StateM (struct type t = int * (string
Sometimes we need merits of multiple monads at the same time, e.g. monads AM and BM.
Straightforwad idea is to nest one monad within another:
But we want a monad that has operations of both AM and BM.
It turns out that the straightforward approach does not lead to operations with the meaning we want.
A monad transformer AT takes a monad BM and turns it into a monad AT(BM) which actually wraps around BM on both sides. AT(BM) has operations of both monads.
We will develop a monad transformer StateT which adds state to a
monad-plus. The resulting monad has all: return
,
bind
, mzero
, mplus
,
put
, get
and their supporting general-purpose
functions.
We need monad transformers in OCaml because “monads are contagious”: although we have built-in state and exceptions, we need to use monadic state and exceptions when we are inside a monad.
Things get interesting when we have several monad transformers, e.g. AT, BT, … We can compose them in various orders: AT(BT(CM)), BT(AT(CM)), … achieving different results.
The state monad, using (fun x -> …) a instead of let x = a in …
type ’a state = store -> (’a * store)
let return
(a : ’a) : ’a state = fun s -> (a, s)
let bind (u : ‘a state) (f : ’a -> ’b state) : ’b state = fun s -> (fun (a, s’) -> f a s’) (u s)
Monad M transformed to add state, in pseudo-code:
type ’a stateT(M) = store -> (’a * store) M(* notice this is not an (’a M) state *)
let return
(a : ’a) : ’a stateT(M) = fun s ->
M.return
(a, s)Rather than returning, M.return
let bind(u:‘a stateT(M))(f:’a->’b stateT(M)):’b stateT(M)= fun s -> M.bind (u s) (fun (a, s’) -> f a s’)Rather than let-binding, M.bind
module StateT (MP : MONADPLUSOPS) (Store : sig type t end) :
sigFunctor takes two modules – the second one type store =
Store.t
provides only the storage type.type ‘a t = store
-> (’a * store) MP.monad include MONADPLUSOPSExporting all the
monad-plus operations include STATE with type ’a t :=
'a monad
and state operations.and type store := store val
run : ’a monad -> 'a t
Expose ‘‘what happened’’ –
resulting states.val runT : ’a monad -> store -> ’a MP.monadend =
structRun the state transformer – get the resulting values. type store =
Store.t
module M = struct type ‘a t = store -> (’a * store) MP.monad let
return a = fun s -> MP.return (a, s) let bind m b = fun s ->
MP.bind (m s) (fun (a, s’) -> b a s’) let mzero = fun ->
MP.mzero
Lift the monad-plus operations.let mplus
ma mb = fun s -> MP.mplus (ma s) (mb s) end include M include
MonadPlusOps(M) let get = fun s -> MP.return (s, s)Instead of just
returning, let put s’ = fun -> MP.return ((), s’)MP.return. let runT
m s = MP.lift fst (m s)end
module HoneyIslands (M : MONADPLUSOPS) = struct type state = {For use
with list monad or lazy list monad. beensize: int; beenislands:
int;
unvisited: cell list; visited: CellSet.t; eaten: cell list;
moretoeat: int; } let initstate unvisited moretoeat = { beensize =
0;
beenislands = 0; unvisited; visited = CellSet.empty; eaten = [];
moretoeat; }
module BacktrackingM = StateT (M) (struct type t = state end) open BacktrackingM let rec visitcell () = performState update actions. s <– get; match s.unvisited with | [] -> return None | c::remaining when CellSet.mem c s.visited -> perform put {s with unvisited=remaining}; visitcell ()Throwaway argument because of recursion. See () | c::remaining ( when c not visited *) -> perform put {s with unvisited=remaining; visited = CellSet.add c s.visited}; return (Some c)This action returns a value.
let eatcell c = perform s <– get; put {s with eaten = c::s.eaten;
visited = CellSet.add c s.visited; moretoeat = s.moretoeat - 1}; return
()Remaining state update actions just affect the state. let keepcell c =
perform s <– get; put {s with
visited = CellSet.add c s.visited; beensize = s.beensize + 1};
return () let freshisland = perform s <– get; put {s with beensize =
0; beenislands = s.beenislands + 1}; return ()
let findtoeat n islandsize numislands emptycells = let honey =
honeycells n emptycells inOCaml does not realize that
'a monad
with state is actually a function – let rec
findboard () = performit’s an abstract type.(*)
cell <– visitcell (); match cell with | None -> perform s <–
get; guard (s.beenislands = numislands); return s.eaten | Some cell
-> perform
freshisland; findisland cell; s <– get;
guard (s.beensize = islandsize); findboard ()
and findisland current = perform keepcell current; neighbors
n emptycells current |> foldM
The partial answer sits
in the state – throwaway result.(fun () neighbor -> perform s <–
get; whenM (not (CellSet.mem neighbor s.visited))
(let chooseeat = perform guard (s.moretoeat > 0); eatcell
neighbor
and choosekeep = perform guard (s.beensize < islandsize); findisland
neighbor in
chooseeat ++ choosekeep)) () in
let cellstoeat = List.length honey - islandsize * numislands in
initstate honey cellstoeat |> runT (findboard ())endmodule HoneyL = HoneyIslands (ListM)let findtoeat a b c d = ListM.run (HoneyL.findtoeat a b c d)
The essential functions for the probability monad class are
choose
and distrib
– remaining functions could
be defined in terms of these but are provided by each instance for
efficiency.
Inside-monad operations:
choose : float -> ’a monad -> ’a monad -> ’a monad
choose p a b
represents an event or distribution which
is a with probability p and is b
with probability 1 - p.
val pick : (’a * float) list -> ’a t
A result from the provided distribution over values. The argument must be a probability distribution: positive values summing to 1.
val uniform : ’a list -> ’a monad
Uniform distribution over given values.
val flip : float -> bool monad
Equal to
choose 0.5 (return true) (return false)
.
val coin : bool monadEqual to flip 0.5
.
And some operations for getting out of the monad:
val prob : (’a -> bool) -> ’a monad -> float
Returns the probability that the predicate holds.
val distrib : ’a monad -> (’a * float) list
Returns the distribution of probabilities over the resulting values.
val access : ’a monad -> ’a
Samples a random result from the distribution – non-functional behavior.
We give two instances of the probability monad: exact distribution monad, and sampling monad, which can approximate distributions.
The monad instances indeed represent probability distributions:
collections of positive numbers that add up to 1 – although often
merge
rather than normalize
is used. If
pick
and choose
are used correctly.
module type PROBABILITY = sigProbability monad class. include
MONADOPS val choose : float -> ’a monad -> ’a monad -> ’a monad
val pick : (’a * float) list -> 'a monad
val uniform :
’a list -> ’a monadval coin : bool monad val flip : float -> bool
monad val prob : (’a -> bool) -> ’a monad -> float val distrib
: ’a monad -> (’a * float) list val access : ’a monad ->
’aend
let total dist =Helper functions. List.foldleft (fun a
(,b)->a+.b) 0. distlet merge dist =Merge repeating elements.
mapreduce (fun x->x) (+.) 0. distlet normalize dist = Normalize a
measure into a distribution.
let tot = total dist in if tot = 0. then dist else List.map (fun
(e,w)->e,w/.tot) distlet roulette dist =Roulette wheel from a
distribution/measure. let tot = total dist in let rec aux r = function
[] -> assert false | (e,w):: when w <= r -> e | (,w)::tl ->
aux (r-.w) tl in aux (Random.float tot) dist
module DistribM : PROBABILITY = struct module M = structExact
probability distribution – naive implementation. type ’a t = (’a *
float) list
let bind a b = merge``x
w.p. p and then y
w.p. q happens =[y, q*.p | (x,p) <- a; (y,q)
<- b x]y
results w.p. p
q. let return a = [a, 1.]Certainly a
. end include M
include MonadOps (M) let choose p a b = List.map (fun (e,w) -> e,
p.w) a @ List.map (fun (e,w) -> e, (1. -.p).w) b let pick
dist = dist
let uniform elems = normalize (List.map (fun
e->e,1.) elems)let coin = [true, 0.5; false, 0.5] let flip p = [true,
p; false, 1. -. p]
let prob p m = m |> List.filter (fun (e,) -> p e)All cases
where p
holds, |> List.map snd |> List.foldleft (+.)
0.add up.
let distrib m = m let access m = roulette mend
module SamplingM (S : sig val samples : int end) : PROBABILITY =
structParameterized by how many samples module M = structused to
approximate prob
or distrib
. type ’a t = unit
-> 'a
Randomized computation – each call a()let bind a b
() = b (a ()) () is an independent sample. let return a = fun () ->
a
Always a
.end include M include MonadOps (M)
let choose p a b () = if Random.float 1. <= p then a () else b () let
pick dist = fun () -> roulette dist
let uniform elems =
let n = List.length elems in fun () -> List.nth (Random.int n)
elemslet coin = Random.bool let flip p = choose p (return true) (return
false)
let prob p m = let count = ref 0 in for i = 1 to S.samples do
if p (m ()) then incr count done; floatofint !count /. floatofint
S.samples
let distrib m = let dist = ref [] in for i = 1 to
S.samples do dist := (m (), 1.) :: !dist done; normalize
(merge
!dist) let access m = m ()end
http://en.wikipedia.org/wiki/Monty_Hall_problem: > In search of a new car, the player picks a door, say 1. The game host > then opens one of the other doors, say 3, to reveal a goat and offers to > let the player pick door 2 instead of door 1.
module MontyHall (P : PROBABILITY) = struct open P type door = A
| B | C
let doors = [A; B; C]
let montywin switch = perform prize <– uniform doors;
chosen <– uniform doors; opened <– uniform (listdiff doors [prize;
chosen]); let final = if switch then List.hd
(listdiff doors [opened; chosen]) else chosen in return (final =
prize)end
module MontyExact = MontyHall (DistribM)module Sampling1000 = SamplingM (struct let samples = 1000 end)module MontySimul = MontyHall (Sampling1000)
let t1 = DistribM.distrib (MontyExact.montywin false);;val t1 : (bool *
# float) list = [(true, 0.333333333333333315); (false, 0.66666666666666663)]#
let t2 = DistribM.distrib (MontyExact.montywin true);;val t2 : (bool *
float) list = [(true, 0.66666666666666663); (false, 0.333333333333333315)]#
let t3 = Sampling1000.distrib (MontySimul.montywin false);;val t3 : (bool *
float) list = [(true, 0.313); (false, 0.687)]# let t4 = Sampling1000.distrib
true);;val t4 : (bool * float) list = [(true, 0.655);
(MontySimul.montywin false, 0.345)] (
Wouldn’t it be nice to have a monad-plus rather than a monad?
We could use guard
– conditional probabilities!
For the exact distribution monad it turns out very easy – we just need to allow intermediate distributions to be unnormalized (sum to less than 1).
For the sampling monad we use rejection sampling.
mplus
has no straightforward correct
implementation.We implemented PROBABILITY separately for educational purposes only, as COND_PROBAB introduced below supersedes it.
module type CONDPROBAB = sigClass for conditional probability
monad,
include PROBABILITYwhere guard cond
conditions on
cond
. include MONADPLUSOPS with type ’a monad := ’a
monadend
module DistribMP : CONDPROBAB = struct module MP = structThe
measures no longer restricted to type ’a t = (’a * float)
list
probability distributions: let bind a b = merge [y,
q*.p | (x,p) <- a; (y,q) <- b x] let return a = [a, 1.] let mzero
= []Measure equal 0 everywhere is OK. let mplus = List.append end
include MP include MonadPlusOps (MP) let choose p a b =It isn’t
a
w.p. p &
b
w.p. (1 - p) since
a
and b
List.map (fun (e,w) -> e, p.w)
a @are not
normalized!List.map (fun (e,w) -> e, (1. -.p).w) b let pick dist
= dist
let uniform elems = normalize (List.map (fun e->e,1.) elems) let
coin = [true, 0.5; false, 0.5] let flip p = [true, p; false, 1. -. p]
let prob p m = normalize m
Final normalization step.|>
List.filter (fun (e,) -> p e) |> List.map snd |> List.foldleft
(+.) 0. let distrib m = normalize m let access m = roulette
mend
We write the rejection sampler in mostly imperative style:
module SamplingMP (S : sig val samples : int end) : CONDPROBAB =
struct
exception RejectedFor rejecting current sample. module MP = structMonad
operations are exactly as for SamplingM type ’a t = unit -> ’a let
bind a b () = b (a ()) () let return a = fun () -> a
let
mzero = fun () -> raise Rejectedbut now we can fail
. let
mplus a b = fun () -> failwith “SamplingMP.mplus not implemented” end
include MP include MonadPlusOps (MP)
let choose p a b () =Inside-monad operations don’t change. if
Random.float 1. <= p then a () else b () let pick dist = fun () ->
roulette dist
let uniform elems = let n = List.length elems
in fun () -> List.nth elems (Random.int n) let coin = Random.bool let
flip p = choose p (return true) (return false)
let prob p m =Getting out of monad: handle rejected samples. let
count = ref 0 and tot = ref 0 in while !tot < S.samples doCount up to
the required trynumber of samples. if p (m ()) then incr count;m() can
fail. incr tot
But if we got here it hasn’t.with Rejected
-> ()Rejected, keep sampling. done; floatofint !count /. floatofint
S.samples
let distrib m = let dist = ref [] and tot = ref 0 in while !tot <
S.samples do try dist := (m (), 1.) :: !dist; incr tot
with Rejected -> () done; normalize (merge !dist) let rec access m =
try m () with Rejected -> access mend
We’re faced with a problem with the following dependency structure:
Burglary |
Earthquake |
|||||
Alarm |
||||||
John calls |
Mary calls |
-5.525383.15826-3.04886559068663.17942188120122-3.133532874718882.73491864003175-2.11753-0.376604-0.297178859637518-0.418937690170658-0.318345680645588-0.228436301098029-5.82172-4.9063-2.98536512766239-4.90630374388147-3.64153657891255-6.028145257309170.570661-4.863973.63984984786347-4.906303743881472.85667747056489-6.091645720333380.7399953.158263.682183489879613.094754597168943.512848921815052.50208360894298-3.346952.3791-1.790454363947570.2764614914471450.9764792.39298-0.6819827901385850.319857839429467-1.87545-1.04339-3.6026998120943-3.65669671517365-0.55984-1.063621.16004444583979-3.529329161908520cm
module Burglary (P : CONDPROBAB) = struct open P type
whathappened =
Safe | Burgl | Earthq | Burglnearthq
let check \simjohncalled \simmarycalled \simradio = perform
earthquake <– flip 0.002; guard (radio = None || radio = Some
earthquake); burglary <– flip 0.001; let alarmp = match burglary,
earthquake with | false, false -> 0.001 | false, true -> 0.29 |
true, false -> 0.94 | true, true -> 0.95 in alarm <– flip
alarmp;
let johnp = if alarm then 0.9 else 0.05 in johncalls <-- flip
johnp; guard (johncalls = johncalled); let maryp = if alarm then 0.7 else 0.01 in marycalls <– flip maryp; guard (marycalls = marycalled); match burglary, earthquake with | false, false -> return Safe | true, false -> return Burgl | false, true -> return Earthq | true, true -> return Burglnearthqend
module BurglaryExact = Burglary (DistribMP)module Sampling2000 = SamplingMP (struct let samples = 2000 end)module BurglarySimul = Burglary (Sampling2000)
let t1 = DistribMP.distrib (BurglaryExact.check $\sim$johncalled:true
# false $\sim$radio:None);; val t1 :
$\sim$marycalled:float) list =
(BurglaryExact.whathappened * 1.03476433660005444e-05);
[(BurglaryExact.Burglnearthq, 0.00452829235738691407);
(BurglaryExact.Earthq, 0.00511951049003530299);
(BurglaryExact.Burgl, 0.99034184950921178)]# let t2 = DistribMP.distrib
(BurglaryExact.Safe, true $\sim$marycalled:true
(BurglaryExact.check $\sim$johncalled:None);; val t2 : (BurglaryExact.whathappened * float) list =
$\sim$radio:0.00057437256500405794);
[(BurglaryExact.Burglnearthq, 0.175492465840075218);
(BurglaryExact.Earthq, 0.283597462799388911);
(BurglaryExact.Burgl, 0.540335698795532)]# let t3 = DistribMP.distrib
(BurglaryExact.Safe, true $\sim$marycalled:true
(BurglaryExact.check $\sim$johncalled:Some true));; val t3 : (BurglaryExact.whathappened * float)
$\sim$radio:(list = [(BurglaryExact.Burglnearthq, 0.0032622416021499262);
0.99673775839785006)]
(BurglaryExact.Earthq,
let t4 = Sampling2000.distrib (BurglarySimul.check $\sim$johncalled:true
# false $\sim$radio:None);; val t4 :
$\sim$marycalled:float) list = [(BurglarySimul.Earthq, 0.0035);
(BurglarySimul.whathappened * 0.0035); (BurglarySimul.Safe, 0.993)]# let t5 =
(BurglarySimul.Burgl, true
Sampling2000.distrib (BurglarySimul.check $\sim$johncalled:true $\sim$radio:None);; val t5 :
$\sim$marycalled:float) list =
(BurglarySimul.whathappened * 0.0005); (BurglarySimul.Earthq, 0.1715);
[(BurglarySimul.Burglnearthq, 0.2875); (BurglarySimul.Safe, 0.5405)]# let t6 =
(BurglarySimul.Burgl, true
Sampling2000.distrib (BurglarySimul.check $\sim$johncalled:true $\sim$radio:(Some true));; val t6 :
$\sim$marycalled:float) list =
(BurglarySimul.whathappened * 0.0015); (BurglarySimul.Earthq, 0.9985)] [(BurglarySimul.Burglnearthq,
bind
is inherently sequential: bind a (fun x ->
b) computes a
, and resumes computing b
only
once the result x
is known.
For concurrency we need to “suppress” this sequentiality. We introduce
parallel :’a monad-> ’b monad-> (’a -> ’b -> ’c monad) -> ’c monad
where parallel a b (fun x y -> c) does not wait for a
to be computed before it can start computing b
.
It can be that only accessing the value in the monad triggers the computation of the value, as we’ve seen in some monads.
'a monad
value
is the computed results.In former case, a “built-in” parallel
is necessary for
concurrency.
If the monad starts computing right away, as in the Lwt
library,
parallel \concat{e}{\rsub{a}} \concat{e}{\rsub{b}} c
is
equivalent to
perform let a = e_{a} in let b = e_{b} in x <– a; y <– b; c x y
run
or access
from within a monad.We still need to decide on when concurrency happens.
bind
is suspended and computation moves to other threads.
bind
before running
threads created since the bind
was suspended.suspend
is often called yield
but the meaning is more similar to Await
than
Yield
from lecture 7.suspend
or its
equivalent internally.The basic operations of a multithreading monad class.
module type THREADS = sig include MONAD val parallel : ’a t -> ’b t -> (’a -> ’b -> ’c t) -> ’c tend
Although in our implementation parallel
will be
redundant, it is a principled way to make sure subthreads of a thread
are run concurrently.
All within-monad operations.
module type THREADOPS = sig include MONADOPS include THREADS with type ’a t := ’a monad val parallelmap : ’a list -> (’a -> ’b monad) -> ’b list monad val (>||=) : ’a monad -> ’b monad -> (’a -> ’b -> ’c monad) -> ’c monad val (>||) : ’a monad -> ’b monad -> (unit -> ’c monad) -> ’c monadend
Outside-monad operations.
module type THREADSYS = sig include THREADS val access : ’a t ->
’a
val killthreads : unit -> unitend
Helper functions.
module ThreadOps (M : THREADS) = struct open M include MonadOps (M)
let parallelmap l f = List.foldright (fun a bs -> parallel (f a)
bs
(fun a bs -> return (a::bs))) l (return []) let (>||=) = parallel
let (>||) a b c = parallel a b (fun -> c ())end
Put an interface around an implementation.
module Threads (M : THREADSYS) :sig include THREADOPS val access : ’a
monad -> ’a val killthreads : unit -> unitend = struct include
M
include ThreadOps(M)end
Our implementation, following the Lwt paper.
module Cooperative = Threads(struct type ‘a state = | Return of
'a
The thread has returned.| Sleep of (’a -> unit)
list
When thread returns, wake up waiters.| Link of
'a t
A link to the actual thread.and ’a t = {mutable state :
’a state}State of the thread can change– it can return, or more waiters
can be added.let rec find t = match t.state withUnion-find style link
chasing. | Link t -> find t | -> t let jobs = Queue.create ()Work
queue – will storeunit -> unit procedures. let wakeup m a =Thread
m
has actually finished – let m = find m inupdating its
state. match m.state with | Return -> assert false | Sleep waiters
-> m.state <- Return a;Set the state, and only then
List.iter ((|>) a) waiters
wake up the waiters. | Link
-> assert false let return a = {state = Return a}let connect t t’
=t
was a placeholder for t'
. let t’ = find t’
in match t’.state with | Sleep waiters’ -> let t = find t in (match
t.state with | Sleep waiters ->If both sleep, collect their waiters
t.state <- Sleep (waiters’ @ waiters); t’.state <- Link
t
and link one to the other.| -> assert false) | Return x
-> wakeup t x
If t'
returned, wake up the
placeholder.| Link -> assert falselet rec bind a b = let a = find a
in let m = {state = Sleep []} inThe resulting monad.
(match a.state with | Return x ->If a
returned, we
suspend further work. let job () = connect m (b x) in(In exercise 11,
this should
Queue.push job jobs
only happen after
suspend
.)| Sleep waiters ->If a
sleeps, we
wait for it to return. let job x = connect m (b x) in
a.state <- Sleep (job::waiters) | Link -> assert false); m
let parallel a b c = performSince in our implementation x <– a;the
threads run as soon as they are created, y <– b;parallel
is redundant. c x y
let rec access m =Accessing not only
gets the result of m
, let m = find m inbut spins the thread
loop till m
terminates. match m.state with | Return x ->
x
No further work.| Sleep -> (try Queue.pop jobs
()Perform suspended work. with Queue.Empty ->
failwith “access: result not available”); access m | Link -> assert
false let killthreads () = Queue.clear jobsRemove pending work.end)
whenM
because else return ()the thread would be
created regardless of condition.endmodule TT = TTest (Cooperative)let test = Cooperative.killthreads (); let thread1 = TT.loop "A" 5 in let thread2 = TT.loop "B" 4 in Cooperative.access thread1; Cooperative.access thread2;;-- A(5)-- B(4)-- A(4)-- B(3)-- A(3)-- B(2)-- A(2)-- B(1)-- A(1)-- B(0)-- A(0)val test : unit = () #
Exercise 1.
Puzzle via Oleg Kiselyov.
“U2” has a concert that starts in 17 minutes and they must all cross a bridge to get there. All four men begin on the same side of the bridge. It is night. There is one flashlight. A maximum of two people can cross at one time. Any party who crosses, either 1 or 2 people, must have the flashlight with them. The flashlight must be walked back and forth, it cannot be thrown, etc.. Each band member walks at a different speed. A pair must walk together at the rate of the slower man’s pace:
For example: if Bono and Larry walk across first, 10 minutes have elapsed when they get to the other side of the bridge. If Larry then returns with the flashlight, a total of 20 minutes have passed and you have failed the mission.
Find all answers to the puzzle using a list comprehension. The comprehension will be a bit long but recursion is not needed.
Exercise 2.
Assume concat_map
as defined in lecture 6. What will the
following expresions return? Why?
Exercise 3.
Define bind
in terms of lift
and
join
.
Exercise 4.
Define a monad-plus implementation based on
binary trees, with constant-time mzero
and
mplus
. Starter code:type ’a tree = Empty | Leaf of ’a | T
of ’a t * ’a tmodule TreeM = MonadPlus (struct type ’a t = ’a tree let
bind a b = TODO let return a = TODO let mzero = TODO let mplus a b =
TODOend)
Exercise 5.
Show the monad-plus laws for one of:
Exercise 6.
Why the following monad-plus is not lazy enough?
Exercise 7.
Convert a “rectangular” list of lists of strings, representing a matrix with inner lists being rows, into a string, where elements are column-aligned. (Exercise not related to recent material.)
Exercise 8.
Recall the overly rich way to introduce monads – providing the freedom of additional parametermodule type MONAD = sig type (’s, ’a) t val return : ’a -> (’s, ’a) t val bind : (’s, ’a) t -> (’a -> (’s, ’b) t) -> (’s, ’b) tend
Recall the operations for the exception monad:val throw : excn -> ’a monadval catch : ’a monad -> (excn -> ’a monad) -> ’a monad
Exercise 9.
Implement the following constructs for all monads:
Explain how, when your implementation is instantiated with the StateM monad, we get the solution to exercise 2 from lecture 4.
Exercise 10.
A canonical example of a probabilistic model is that of a lawn whose
grass may be wet because it rained, because the sprinkler was on, or for
some other reason. Oleg Kiselyov builds on this example with variables
rain
, sprinkler
, and wet_grass
,
by adding variables cloudy
and wet_roof
. The
probability tables are:
\begin{eqnarray*} P (\operatorname{cloudy}) & = & 0.5 \\\\\\ P (\operatorname{rain}|\operatorname{cloudy}) & = & 0.8 \\\\\\ P (\operatorname{rain}|\operatorname{not}\operatorname{cloudy}) & = & 0.2 \\\\\\ P (\operatorname{sprinkler}|\operatorname{cloudy}) & = & 0.1 \\\\\\ P (\operatorname{sprinkler}|\operatorname{not}\operatorname{cloudy}) & = & \0.5 \\\\\\ P (\operatorname{wet}\operatorname{roof}|\operatorname{not}\operatorname{rain}) & = & 0 \\\\\\ P (\operatorname{wet}\operatorname{roof}|\operatorname{rain}) & = & 0.7 \\\\\\ P (\operatorname{wet}\operatorname{grass}|\operatorname{rain} \wedge \operatorname{not}\operatorname{sprinkler}) & = & 0.9 \\\\\\ P (\operatorname{wet}\operatorname{grass}|\operatorname{sprinkler} \wedge \operatorname{not}\operatorname{rain}) & = & 0.9 \end{eqnarray*}
We observe whether the grass is wet and whether the roof is wet. What is the probability that it rained?
Exercise 11.
Implement the coarse-grained concurrency model.
bind
to compute the resulting monad straight
away if the input monad has returned.suspend
to do what in the fine-grained model
was the effect of bind (return a) b
, i.e. suspend the work
although it could already be started.suspend
of type unit
monad, introduce a “dummy” monadic value Suspend
(besides
Return
and Sleep
), and define
bind suspend b
to do what bind (return ()) b
would formerly do.Compilation. Runtime. Optimization. Parsing.
Andrew W. Appel ‘‘Modern Compiler Implementation in ML’‘E. Chailloux, P. Manoury, B. Pagano ‘‘Developing Applications with OCaml’‘Jon D. Harrop ‘‘OCaml for Scientists’’Francois Pottier, Yann Regis-Gianas ‘‘Menhir Reference Manual’’
If you see any error on the slides, let me know!
OCaml has two primary compilers: the bytecode compiler
ocamlc
and the native code compiler
ocamlopt
.
OCaml has an interactive shell called toplevel (in other
languages, repl): ocaml
which is based on the
bytecode compiler.
ocamlnat
based on the native code
compiler but currently not part of the binary distribution.There are “third-party” compilers, most notably
js_of_ocaml
which translates OCaml bytecode into JavaScript
source.
Stages of compilation:
Programs:
ocaml |
toplevel loop |
ocamlrun |
bytecode interpreter (VM) |
camlp4 |
preprocessor (syntax extensions) |
ocamlc |
bytecode compiler |
ocamlopt |
native code compiler |
ocamlmktop |
new toplevel constructor |
ocamldep |
dependencies between modules |
ocamlbuild |
building projects tool |
ocamlbrowser |
graphical browsing of sources |
File extensions:
.ml |
OCaml source file |
.mli |
OCaml interface source file |
.cmi |
compiled interface |
.cmo |
bytecode-compiled file |
.cmx |
native-code-compiled file |
.cma |
bytecode-compiled library (several source files) |
.cmxa |
native-code-compiled library |
.cmt/.cmti/.annot |
type information for editors |
.c |
C source file |
.o |
C native-code-compiled file |
.a |
C native-code-compiled library |
Both compilers commands:
-a |
construct a runtime library |
-c |
compile without linking |
-o |
name_of_executable specify the name of the executable |
-linkall |
link with all libraries used |
-i |
display all compiled global declarations |
-pp |
command uses command as preprocessor |
-unsafe |
turn off index checking for arrays |
-v |
display the version of the compiler |
-w list |
choose among the list the level of warning message |
-impl file |
indicate that file is a Caml source (.ml) |
-intf file |
indicate that file is a Caml interface (.mli) |
-I directory |
add directory in the list of directories; prefix + for relative |
-g |
generate debugging information |
Warning levels:
A/a |
enable/disable all messages |
F/f |
partial application in a sequence |
P/p |
for incomplete pattern matching |
U/u |
for missing cases in pattern matching |
X/x |
enable/disable all other messages for hidden object |
M/m, V/v |
object-oriented related warnings |
Native compiler commands:
-compact |
optimize the produced code for space |
-S |
keeps the assembly code in a file |
-inline |
level set the aggressiveness of inlining |
Environment variable OCAMLRUNPARAM
:
b |
print detailed stack backtrace of runtime exceptions |
s/h/i |
size of the minor heap/major heap/size increment |
o/O |
major GC speed setting / heap compaction trigger setting |
Typical use, running prog
:
export OCAMLRUNPARAM='b'; ./prog
To have stack backtraces, compile with option
-g
.
Toplevel loop directives:
#quit;; |
exit |
#directory "dir";; |
add dir to the “search path”; + for rel. |
#cd "dir-name";; |
change directory |
#load "file-name";; |
load a bytecode .cmo/.cma file |
#load_rec "file-name";; |
load the files file-name depends on too |
#use "file-name";; |
read, compile and execute source phrases |
#instal_printer pr_nm;; |
register pr_nm to print values of a type |
#print_depth num;; |
how many nestings to print |
#print_length num;; |
how many nodes to print – the rest … |
#trace func;;/#untrace |
trace calls to func/stop tracing |
Traditionally the file containing a module would have a lowercase name, although the module name is always uppercase.
MyModule.ml
rather than myModule.ml
.We have a project with main module main.ml
and
helper modules sub1.ml
and sub2.ml
with
corresponding interfaces.
Native compilation by hand:
…:…/Lec9$ ocamlopt sub1.mli…:…/Lec9$ ocamlopt sub2.mli…:…/Lec9$ ocamlopt -c sub1.ml…:…/Lec9$ ocamlopt -c sub2.ml…:…/Lec9$ ocamlopt -c main.ml…:…/Lec9$ ocamlopt unix.cmxa sub1.cmx sub2.cmx main.cmx -o prog…:…/Lec9$ ./prog
Native compilation using make
:
PROG := prog
LIBS := unix
SOURCES := sub1.ml sub2.ml main.ml
INTERFACES := $(wildcard *.mli)
OBJS := $(patsubst %.ml,%.cmx,$(SOURCES))
LIBS := $(patsubst %,%.cmxa,$(LIBS))
$(PROG): $(OBJS)
ocamlopt -o $@ $(LIBS) $(OBJS)
clean: rm -rf $(PROG) *.o *.cmx *.cmi *~
%.cmx: %.ml
ocamlopt -c $*.ml
%.cmi: %.mli
ocamlopt -c $*.mli
depend: $(SOURCES) $(INTERFACES) ocamldep -native $(SOURCES) $(INTERFACES)
touch .depend; make depend; make
make
, after creating new source files
make depend
ocamlbuild
files with compiled code are created in _build
directory
Command: ocamlbuild -libs unix main.native
Resulting program is called main.native
(in
directory _build
, but with a link in the project
directory)
More arguments passed after comma, e.g.
ocamlbuild -libs nums,unix,graphics main.native
Passing parameters to the compiler with -cflags
,
e.g.:
ocamlbuild -cflags -I,+lablgtk,-rectypes hello.native
Adding a – at the end (followed with command-line arguments for the program) will compile and run the program:
ocamlbuild -libs unix main.native --
ocaml-mode
from the standard distributiontuareg-mode
https://forge.ocamlcore.org/projects/tuareg/
camldebug
intergration with debuggerC-c C-t
key shortcut, needs
.annot
filestyperex-mode
for Emacs but
integration with other editors will become betterocp-ident
https://github.com/OCamlPro/ocp-indent
ocamlbrowser
inspects libraries and programs
OCaml is not a purely functional language, it has built-in:
Mutable arrays.
let a = Array.make 5 0 ina.(3) <- 7; a.(2), a.(3)
Hashtables in the standard distribution (based on arrays).
let h = Hashtbl.create 11 inTakes initial size of the array.Hashtbl.add h “Alpha” 5; Hashtbl.find h “Alpha”
Mutable strings. (Historical reasons…)
let a = String.make 4 ‘a’ ina.[2] <- ‘b’; a.[2], a.[3]
Loops:
Mutable record fields, for example:
type ’a ref = { mutable contents : ’a }Single, mutable field.
A record can have both mutable and immutable fields.
Modifying the field: record.field <- new_value
The ref type has operations:
let (:=) r v = r.contents <- vlet (!) r = r.contents
Exceptions, defined by exception, raised by raise and caught by try-with clauses.
Input-output functions have no “type safeguards” (no IO monad).
Using global state e.g. reference cells makes code non re-entrant: finish one task before starting another – any form of concurrency is excluded.
To go beyond Sys.argv array, see Arg module:http://caml.inria.fr/pub/docs/manual-ocaml/libref/Arg.html
type config = { Example: configuring a Mine Sweeper game. nbcols : int ; nbrows : int ; nbmines : int }let defaultconfig = { nbcols=10; nbrows=10; nbmines=15 }let setnbcols cf n = cf := {!cf with nbcols = n}let setnbrows cf n = cf := {!cf with nbrows = n}let setnbmines cf n = cf := {!cf with nbmines = n}let readargs() = let cf = ref defaultconfig inState of configuration let speclist = will be updated by given functions. [(“-col”, Arg.Int (setnbcols cf), “number of columns”); (“-lin”, Arg.Int (setnbrows cf), “number of lines”); (“-min”, Arg.Int (setnbmines cf), “number of mines”)] in let usagemsg = “usage : minesweep [-col n] [-lin n] [-min n]” in Arg.parse speclist (fun s -> ()) usagemsg; !cf
00
in binary (addresses are in
number of bytes).1
.None
, [] and (), and other integer-like types
(char
, bool
) are represented in the same way
as integers.# let u = let l = ['c'; 'a'; 'm'] in List.tl l ;;``val u : char list = ['a'; 'm']``# let v = let r = ( ['z'] , u ) in match r with p -> (fst p) @ (snd p) ;;``val v : char list = ['z'; 'a'; 'm']
aux
.f
– arguments and local definitions – which are accessed
from a nested function which is part of the returned value of
f
(or assigned to a mutable field).
f
– in the latter case, the nested functions must also be
represented by closures that have a link to the closure of
f
.f x
within the body of another function
g
is in tail position if, roughly “calling
f
is the last thing that g
will do before
returning”.Stack_overflow
exception on too
deep recursion, native code will sometimes cause segmentation
fault!.map
from the standard distribution is
not tail-recursive.gprof
.ocamlopt -p
…
or using ocamlbuild
when program source is in
prog.ml
:
ocamlbuild prog.p.native --
./prog
produces a file
gmon.out
gprof prog > profile.txt
or when we used ocamlbuild
as above:
gprof prog.p.native > profile.txt
This redirects profiling analysis to profile.txt
file.
profile.txt
has three parts:
camlList__assoc_1169
:
caml
means function comes from OCaml
source.List__
means it belongs to a List module.assoc
is the name of the function in source._1169
is used to avoid name clashes, as in
OCaml different functions often have the same names.Optim0.ml
.let readwords file =Imperative programming example. let input =
openin file in let words = ref [] and more = ref true in tryLecture 6
read_lines
function would stack-overflow while !more
dobecause of the try-with clause. Scanf.fscanf input
“%[a-zA-Z0-9’]%[a-zA-Z0-9’]” (fun b x -> words := x :: !words; more
:= x <> ““) done; List.rev (List.tl !words) with Endoffile ->
List.rev !wordslet empty () = []let increment h w =Inefficient map
update. try let c = List.assoc w h in (w, c+1) :: List.removeassoc w h
with Notfound -> (w, 1)::hlet iterate f h =
List.iter (fun (k,v)->f k v) hlet histogram words = List.foldleft
increment (empty ()) wordslet = let words = readwords”./shakespeare.xml”
in let words = List.revmap String.lowercase words in let h = histogram
words in let output = openout “histogram.txt” in iterate (Printf.fprintf
output “%s: %dn”) h; closeout output
% cumulative self self total
time seconds seconds calls s/call s/call name
37.88 8.54 8.54 306656698 0.00 0.00 compare_val
19.97 13.04 4.50 273169 0.00 0.00 camlList__assoc_1169
9.17 15.10 2.07 633527269 0.00 0.00 caml_page_table_lookup
8.72 17.07 1.97 260756 0.00 0.00 camlList__remove_assoc_1189
7.10 18.67 1.60 612779467 0.00 0.00 caml_string_length
4.97 19.79 1.12 306656692 0.00 0.00 caml_compare
2.84 20.43 0.64 caml_c_call
1.53 20.77 0.35 14417 0.00 0.00 caml_page_table_modify
1.07 21.01 0.24 1115 0.00 0.00 sweep_slice
0.89 21.21 0.20 484 0.00 0.00 mark_slice
increment
could be the bottleneck.
compare_val
consumes the most of time.increment
function.
index % time self children called name
-----------------------------------------------0.00 6.47 273169/273169 camlList__fold_left_1078 [7]
8] 28.7 0.00 6.47 273169 camlOptim0__increment_1038 [8]
[4.50 0.00 273169/273169 camlList__assoc_1169 [9]
1.97 0.00 260756/260756 camlList__remove_assoc_1189 [11]
increment
is only called by
List.fold_left. But it seems to account for only 29% of time. It is
because compare
is not analysed correctly, thus not
included in time for increment
:-----------------------------------------------
1.12 12.13 306656692/306656692 caml_c_call [1]
[2] 58.8 1.12 12.13 306656692 caml_compare [2]
8.54 3.60 306656692/306656698 compare_val [3]
(All times measured with profiling turned on.)
Optim0.ml
asymptotic time complexity: \mathcal{O} (n^2), time: 22.53s.
Optimize the data structure, keep the algorithm.
let empty () = Hashtbl.create 511let increment h w = try let c = Hashtbl.find h w in Hashtbl.replace h w (c+1); h with Notfound -> Hashtbl.add h w 1; hlet iterate f h = Hashtbl.iter f h
Optim1.ml
asymptotic time complexity: \mathcal{O} (n), time: 0.63s.
Optimize the algorithm, keep the data structure.
let histogram words = let words = List.sort String.compare words in let k,c,h = List.foldleft (fun (k,c,h) w -> if k = w then k, c+1, h else w, 1, ((k,c)::h)) (““, 0, []) words in (k,c)::h
Optim2.ml
asymptotic time complexity: \mathcal{O} (n \log n), time: 1s.
Optimizing for cache efficiency is more advanced, we will not attempt it.
With algorithmic optimizations we should be concerned with asymptotic complexity in terms of the \mathcal{O} (\cdot) notation, but we will not pursue complexity analysis in the remainder of the lecture.
Optimizations below have been made for educational purposes only.
Avoid polymorphism in generic comparison function (=).
let rec assoc x = function [] -> raise Notfound | (a,b)::l -> if String.compare a x = 0 then b else assoc x llet rec removeassoc x = function | [] -> [] | (a, b as pair) :: l -> if String.compare a x = 0 then l else pair :: removeassoc x l
Optim3.ml
(based on Optim0.ml
) time:
19s.
assoc
is like above but uses Pervasives.compare!caml_compare_val
function.Deforestation means removing intermediate data structures.
let readtohistogram file = let input = openin file in let h = empty
() and more = ref true in try while !more do Scanf.fscanf input
“%[a-zA-Z0-9’]%[a-zA-Z0-9’]” (fun b w -> let w = String.lowercase w
in increment h w; more := w <> ““)
done; h with Endoffile -> h
Optim4.ml
(based on Optim1.ml
) time:
0.51s.
Defunctorization means computing functor applications by hand.
ocamldefun
but it is out of date.create: | ||||||||||
assoc list | 0.25 | 0.25 | 0.18 | 0.19 | 0.17 | 0.22 | 0.19 | 0.19 | 0.19 | |
tree map | 0.48 | 0.81 | 0.82 | 1.2 | 1.6 | 2.3 | 2.7 | 3.6 | 4.1 | 5.1 |
hashtable | 27 | 9.1 | 5.5 | 4 | 2.9 | 2.4 | 2.1 | 1.9 | 1.8 | 3.7 |
create: | ||||||||||||
tree map | 6.5 | 8 | 9.8 | 15 | 19 | 26 | 34 | 41 | 51 | 67 | 80 | 130 |
hashtable | 4.8 | 5.6 | 6.4 | 8.4 | 12 | 15 | 19 | 20 | 22 | 24 | 23 | 33 |
found: | ||||||||||
assoc list | 1.1 | 1.5 | 2.5 | 4.2 | 8.1 | 17 | 30 | 60 | 120 | |
tree map | 1 | 1.1 | 1.3 | 1.5 | 1.9 | 2.1 | 2.5 | 2.8 | 3.1 | 3.6 |
hashtable | 1.4 | 1.5 | 1.4 | 1.4 | 1.5 | 1.5 | 1.6 | 1.6 | 1.8 | 1.8 |
found: | ||||||||||||
tree map | 4.3 | 5.2 | 6 | 7.6 | 9.4 | 12 | 15 | 17 | 19 | 24 | 28 | 32 |
hashtable | 1.8 | 2 | 2.5 | 3.1 | 4 | 5.1 | 5.9 | 6.4 | 6.8 | 7.6 | 6.7 | 7.5 |
not found: | ||||||||||
assoc list | 1.8 | 2.6 | 4.6 | 8 | 16 | 32 | 60 | 120 | 240 | |
tree map | 1.5 | 1.5 | 1.8 | 2.1 | 2.4 | 2.7 | 3 | 3.2 | 3.5 | 3.8 |
hashtable | 1.4 | 1.4 | 1.5 | 1.5 | 1.6 | 1.5 | 1.7 | 1.9 | 2 | 2.1 |
not found: | ||||||||||||
tree map | 4.2 | 4.3 | 4.7 | 4.9 | 5.3 | 5.5 | 6.1 | 6.3 | 6.6 | 7.2 | 7.5 | 7.3 |
hashtable | 1.8 | 1.9 | 2 | 1.9 | 1.9 | 1.9 | 2 | 2 | 2.2 | 2 | 2 | 1.9 |
ocamllex
for lexing, whose rules are like
pattern matching functions, but with patterns being regular
expressions.ocamlyacc
,
belonging to the yacc/bison family of parsers.The format of lexer definitions is as follows: file with
extension .mll
{ header }let ident1 = regexp …rule entrypoint1
[arg1
… argN
] = parse regexp { action1 }| …|
regexp { actionN }and entrypointN [arg1? argN] = parse …and …{ trailer
}
Let-clauses are shorthands for regular expressions.
Each rule-clause entrypoint
defines function(s) that
as the last argument (after arg1
… argN
if
N
>0) takes argument lexbuf
of type
Lexing.lexbuf.
lexbuf
is also visible in actions, just as a regular
argument.entrypoint1
… entrypointN
can be mutually
recursive if we need to read more before we can return output.We can use lexbuf
in actions:
The parser will call an entrypoint
when it needs
another lexeme/token.
The syntax of regular expressions
_
– match a single charactereof
– match end of lexer inputident
– reference the regular expression bound to ident
by an earlier let ident
= regexp definitionident
– bind the substring matched by regexp
to identifier ident
.The precedences are: # highest, followed by *, +, ?, concatenation, |, as.
The type of as ident
variables can be string, char,
string option or char option
ident
)? or
regexp1|(regexp2 as ident
)ocamllex Lexer.mll
produces the lexer code in
Lexer.ml
ocamlbuild
will call ocamllex
and
ocamlyacc
/menhir
if neededUnfortunately if the lexer patterns are big we get an error:
transition table overflow, automaton is too big
We mine a text file for email addresses, that could have been obfuscated to hinder our job…
To compile and run Emails.mll
, processing a file
email_corpus.xml
:
ocamlbuild Emails.native -- email_corpus.xml
{The header with OCaml code. open LexingMake accessing Lexing easier.
let nextline lexbuf =Typical lexer function: move position to next line.
let pos = lexbuf.lexcurrp in lexbuf.lexcurrp <- { pos with poslnum =
pos.poslnum + 1; posbol = pos.poscnum; } type state =Which step of
searching for address we’re at: | SeekSeek: still seeking, Addr (true…):
possibly finished, | Addr of bool * string * string
list
Addr (false…): no domain.
let report state lexbuf =Report the found address, if any. match state with | Seek -> () | Addr (false, , ) -> () | Addr (true, name, addr) ->With line at which it is found. Printf.printf “%d: %s@%sn” lexbuf.lexcurrp.poslnum name (String.concat “.” (List.rev addr))}let newline = (‘’ | “”)Regexp for end of line.let addrchar = [‘a’-‘z’‘A’-‘Z’‘0’-‘9’‘-’’’]let atwsymb = “where” | “WHERE” | “at” | “At” | “AT”let atnwsymb = ‘@’ | “@” | “@”let opensymb = ’ ‘*’(’ ’ ‘* |’ ‘+Demarcate a possible @let closesymb =’ ‘*’)’’ ‘* |’ ’+or . symbol.let atsepsymb = opensymb? atnwsymb closesymb? | opensymb atwsymb closesymb
let dotwsymb = “dot” | “DOT” | “dt” | “DT”let domwsymb = dotwsymb |
“dom” | “DOM”Obfuscation for last dot.let dotsepsymb = opensymb dotwsymb
closesymb |
opensymb? ‘.’ closesymb?let domsepsymb = opensymb domwsymb closesymb
|
opensymb? ‘.’ closesymb?let addrdom =
addrchar addrchar
Restricted form of last part| “edu” |
“EDU” | “org” | “ORG” | “com” | “COM”of address.rule
email state
= parse| newline
Check state
before moving on.{ report state lexbuf; nextline lexbuf; email Seek
lexbuf }\swarrowDetected possible start
of address.| (addrchar+ as name) atsepsymb (addrchar+ as addr) { email
(Addr (false, name, [addr])) lexbuf }
state = match state with | Seek -> SeekWe weren’t looking at an
address. | Addr (, name, addrs) ->Bingo. Addr (true, name,
dom::addrs) in email state lexbuf }| dotsepsymb (addrchar+ as addr)Next
part of address – { let state =must be continued. match state with |
Seek -> Seek | Addr (, name, addrs) ->
Addr (false, name, addr::addrs) in email state lexbuf }|
eof
End of file – end loop.{ report state lexbuf }|Some
boring character – not looking at an address yet.{ report state lexbuf;
email Seek lexbuf }{The trailer with OCaml code. let =Open a file and
start mining for email addresses. let ch = openin Sys.argv.(1) in email
Seek (Lexing.fromchannel ch); closein chClose the file at the end.}
The format of parser definitions is as follows: file with
extension .mly
%{ header %}OCaml code put in front.%parameter < M : signature >Parameters make a functor.%token < type1 > Token1 Token2Terminal productions, variants%token < type3 > Token3returned from lexer.%token NoArgTokenWithout an argument, e.g. keywords or symbols.%nonassoc Token1This token cannot be stacked without parentheses.%left Token3Associates to left,%right Token2to right.%type < type4 > rule1Type of the action of the rule.%start < type5 > rule2The entry point of the grammar.%%Separate out the rules part.%inline rule1 (id1, …, inN) :Inlined rules can propagate priorities.|production1 { action1 }If production matches, perform action.|production2 |production3Several productions{ action2 }with the same action.
%public rule2 :Visible in other files of the grammar. | production4 { action4 }%public rule3 :Override precedence of production5 to that of productions | production5 { action5 } %prec Token1ending with Token1%%The separations are needed even if the sections are empty.trailerOCaml code put at the end of generated source.
Header, actions and trailer are OCaml code.
Comments are (* … ) in OCaml code, / … */ or // … outisde
Rules can optionally be separated by ;
%parameter turns the whole resulting grammar into a functor, multiple parameters are allowed. The parameters are visible in %{…%}.
Terminal symbols Token1 and Token2 are both variants with argument of type type1, called their semantic value.
rule1
… ruleN
must be lower-case
identifiers.
Parameters id1
… idN
can be lower- or
upper-case.
Priorities, i.e. precedence, are declared implicitly: %nonassoc, %left, %right list tokens in increasing priority (Token2 has highest precedence).
%start symbols become names of functions exported in the
.mli
file to invoke the parser. They are automatically
%public.
%public rules can even be defined over multiple files, with productions joined by |.
The syntax of productions, i.e. patterns, each line shows one aspect, they can be combined:
rule2
Token1 rule3
Match tokens in sequence
with Token1 in the middle.a=rule2 t=Token3Name semantic values produced
by rules/tokens.rule2; Token3Parts of pattern can be separated by
semicolon.rule1(arg1,…,argN)Use a rule that takes
arguments.rule2
?Shorthand for
option(rule2)rule2
+Shorthand for
nonemptylist(rule2)rule2*Shorthand for list(rule2)
Always-visible “standard library” – most of rules copied below:
%public option(X): /* nothing */ { None }| x = X { Some x }%public %inline pair(X, Y): x = X; y = Y { (x, y) }
%public %inline separatedpair(X, sep, Y): x = X; sep; y = Y { (x, y)
}%public %inline delimited(opening, X, closing): opening; x = X;
closing
{ x }%public list(X): /* nothing */ { [] }| x = X; xs = list(X) { x ::
xs }%public nonemptylist(X): x = X { [ x ] }| x = X; xs =
nonemptylist(X) { x :: xs }%public %inline separatedlist(separator,
X):
xs = loption(separatednonemptylist(separator, X)) { xs }
%public separatednonemptylist(separator, X): x = X { [ x ] }| x = X; separator; xs = separatednonemptylist(separator, X) { x :: xs }
Only left-recursive rules are truly tail-recursive, as in:
declarations:| { [] }| ds = declarations; option(COMMA); d = declaration { d :: ds }
Invocations can be nested in arguments, e.g.:
plist(X):| xs = loption(Like option
, but returns a
list.
delimited(LPAREN, separatednonemptylist(COMMA, X),
RPAREN)) { xs }
Higher-order parameters are allowed.
procedure(list):| PROCEDURE ID list(formal) SEMICOLON block SEMICOLON {…}
Example where inlining is required (besides being an optimization)
%token < int > INT%token PLUS TIMES%left PLUS%left TIMESMultiplication has higher priority.%%expression:| i = INT { i }\swarrow Without inlining, would not distinguish priorities.| e = expression; o = op; f = expression { o e f }%inline op:Inline operator – generate corresponding rules.| PLUS { ( + ) }| TIMES { ( * ) }
Menhir is an \operatorname{LR} (1) parser generator, i.e. it fails for grammars where looking one token ahead, together with precedences, is insufficient to determine whether a rule applies.
Although \operatorname{LR} (1) grammars are a small subset of context free grammars, the semantic actions can depend on context: actions can be functions that take some form of context as input.
Positions are available in actions via keywords
$startpos
(x
) and
$endpos
(x
) where x
is name given
to part of pattern.
Example based on a Menhir demo. Due to difficulties with
ocamlbuild
, we use option --external-tokens
to
provide type token directly rather than having it generated.
File lexer.mll
:
{ type token = | TIMES | RPAREN | PLUS | MINUS | LPAREN
| INT of (int) | EOL | DIV exception Error of string}
rule line = parse| ([‘n’]* ‘n’) as line { line }| eof { exit 0 }and token = parse| [’ ’ ‘t’] { token lexbuf }| ‘n’ { EOL }| [‘0’-‘9’]+ as i { INT (intofstring i) }| ‘+’ { PLUS }| ‘-’ { MINUS }| ’*’ { TIMES }| ‘/’ { DIV }| ‘(’ { LPAREN }| ‘)’ { RPAREN }| eof { exit 0 }| { raise (Error (Printf.sprintf “At offset %d: unexpected character.n” (Lexing.lexemestart lexbuf))) }
File parser.mly
:
%token
%%main:| e = expr EOL { e }expr:| i = INT { inject i }| LPAREN e = expr RPAREN { e }| e1 = expr PLUS e2 = expr { e1 + e2 }| e1 = expr MINUS e2 = expr { e1 - e2 }| e1 = expr TIMES e2 = expr { e1 * e2 }| e1 = expr DIV e2 = expr { e1 / e2 }| MINUS e = expr %prec UMINUS { - e }
File calc.ml
:
module FloatSemantics = struct type number = float let inject = floatofint let ( + ) = ( +. ) let ( - ) = ( -. ) let ( * ) = ( *. ) let ( / ) = ( /. ) let (\sim- ) = (\sim-. )endmodule FloatParser = Parser.Make(FloatSemantics)
let () = let stdinbuf = Lexing.fromchannel stdin in while true do let
linebuf = Lexing.fromstring (Lexer.line stdinbuf) in try
Printf.printf “%.1fn%!” (FloatParser.main Lexer.token linebuf)
with | Lexer.Error msg -> Printf.fprintf stderr “%s%!” msg |
FloatParser.Error -> Printf.fprintf stderr “At offset %d: syntax
error.n%!” (Lexing.lexemestart linebuf) done
Build and run command:
ocamlbuild calc.native -use-menhir -menhir "menhir parser.mly --base parser --external-tokens Lexer" --
parser.mly
--base
gives the file (without extension) which will
become the module accessed from OCaml--external-tokens
provides the OCaml module which
defines the token
typelog.txt
.EngLexer.mll
:{ type sentence = {Could be in any module visible to EngParser.
subject : string;The actor/actors, i.e. subject noun. action :
string;The action, i.e. verb. plural : bool;Whether one or multiple
actors. adjs : string list;Characteristics of actor. advs : string
list
Characteristics of action. }
type token = | VERB of string | NOUN of string | ADJ of string | ADV of string | PLURAL | SINGULAR | ADET | THEDET | SOMEDET | THISDET | THATDET | THESEDET | THOSEDET | COMMACNJ | ANDCNJ | DOTPUNCT let tokstr = function …Print the token. let adjectives =Recognized adjectives. [“smart”; “extreme”; “green”; “slow”; “old”; “incredible”; “quiet”; “diligent”; “mellow”; “new”] let logfile = openout “log.txt”File with debugging information.let log s = Printf.fprintf logfile “%sn%!” s let lasttok = ref DOTPUNCTState for better tagging.
let tokbuf = Queue.create ()Token buffer, since single word let push
w =is sometimes two tokens. log (“lex:”tokstr w);Log lexed token.
lasttok := w; Queue.push w tokbuf exception LexError of string}let
alphanum = [‘0’-‘9’ ‘a’-‘z’ ‘A’-‘Z’ ’’’ ‘-’]rule line = parseFor
line-based interface.| ([‘’]* ‘’) as l { l }| eof { exit 0 }and lexword
= parse| [’ ’ ’]Skip whitespace. { lexword lexbuf }| ‘.’ { push DOTPUNCT
}End of sentence.| “a” { push ADET } | “the” { push THEDET
}‘‘Keywords’‘.| “some” { push SOMEDET }| “this” { push THISDET } |
“that” { push THATDET }| “these” { push THESEDET } | “those” { push
THOSEDET }| “A” { push ADET } | “The” { push THEDET }| “Some” { push
SOMEDET }| “This” { push THISDET } | “That” { push THATDET }| “These” {
push THESEDET } | “Those” { push THOSEDET }| “and” { push ANDCNJ }|’,’ {
push COMMACNJ }| (alphanum+ as w) “ly”Adverb is adjective that ends in
‘‘ly’’.{
if List.mem w adjectives then push (ADV w) else if List.mem (w”le”)
adjectives then push (ADV (w”le”)) else (push (NOUN w); push SINGULAR)
}
adjectives then push (ADJ w) else match !lasttok with | THEDET |
SOMEDET | THESEDET | THOSEDET | DOTPUNCT | ADJ -> push (NOUN w); push
PLURAL | -> push (VERB w); push SINGULAR }| alphanum+ as
w
Noun contexts vs. verb contexts.{ if List.mem w adjectives
then push (ADJ w) else match !lasttok with | ADET | THEDET | SOMEDET |
THISDET | THATDET | DOTPUNCT | ADJ -> push (NOUN w); push
SINGULAR
| -> push (VERB w); push PLURAL }
Char.escaped w)) }{ let lexeme lexbuf =The proper interface reads from the token buffer. if Queue.isempty tokbuf then lexword lexbuf; Queue.pop tokbuf}
EngParser.mly
:%{ open EngLexerSource of the token type and sentence type.%}%token
%public %inline sep2list(sep1, sep2, X):General purpose.| xs = separatednonemptylist(sep1, X) sep2 x=X { xs @ [x] }We use it for ‘‘comma-and’’ lists:| x=option(X)smart, quiet and diligent. { match x with None->[] | Some x->[x] }singonlydet:How determiners relate to number.| ADET | THISDET | THATDET { log “prs: singonlydet” }pluonlydet:| THESEDET | THOSEDET { log “prs: pluonlydet” }otherdet:| THEDET | SOMEDET { log “prs: otherdet” }np(det):| det adjs=list(ADJ) subject=NOUN { log “prs: np”; adjs, subject }vp(NUM):| advs=separatedlist(ANDCNJ,ADV) action=VERB NUM| action=VERB NUM advs=sep2list(COMMACNJ,ANDCNJ,ADV) { log “prs: vp”; action, advs }
sent(det,NUM):Sentence parameterized by number.| adjsub=np(det) NUM
vbadv=vp(NUM) { log “prs: sent”; {subject=snd adjsub; action=fst vbadv;
plural=false; adjs=fst adjsub; advs=snd vbadv}
}vbsent(NUM):Unfortunately, it doesn’t always work…| NUM
vbadv=vp(NUM)
{ log “prs: vbsent”; vbadv }sentence:Sentence, either singular or plural
number.| s=sent(singonlydet,SINGULAR) DOTPUNCT { log “prs:
sentence1”;
{s with plural = false} }| s=sent(pluonlydet,PLURAL) DOTPUNCT { log
“prs: sentence2”; {s with plural = true} }
sentence3”;Because parser allows only one token look-ahead {subject=snd adjsub; action=fst vbadv; plural=false; adjs=fst adjsub; advs=snd vbadv} }| adjsub=np(otherdet) vbadv=vbsent(PLURAL) DOTPUNCT { log “prs: sentence4”;we need to factor-out the ‘‘common subset’’. {subject=snd adjsub; action=fst vbadv; plural=true; adjs=fst adjsub; advs=snd vbadv} }
Eng.ml
is the same as calc.ml
from
previous example:open EngLexerlet () = let stdinbuf = Lexing.fromchannel stdin in while true do (* Read line by line. *) let linebuf = Lexing.fromstring (line stdinbuf) in
try (* Run the parser on a single line of input. *) let s =
EngParser.sentence lexeme linebuf in Printf.printf
“subject=%s=%b=%s=%snadvs=%s%!” s.subject s.plural (String.concat “,”
s.adjs) s.action (String.concat “,” s.advs) with | LexError msg ->
Printf.fprintf stderr “%sn%!” msg | EngParser.Error -> Printf.fprintf
stderr “At offset %d: syntax error.n%!” (Lexing.lexemestart linebuf)
done
Build & run command:
ocamlbuild Eng.native -use-menhir -menhir "menhir EngParser.mly --base EngParser --external-tokens EngLexer" --
In lecture 6 we performed keyword search, now we turn to phrase search i.e. require that given words be consecutive in the document.
We start with some English-specific transformations used in lexer:
let whorpronoun w = w = “where” || w = “what” || w = “who” || w = “he” || w = “she” || w = “it” || w = “I” || w = “you” || w = “we” || w = “they”let abridged w1 w2 =Remove shortened forms like I’ll or press’d. if w2 = “ll” then [w1; “will”] else if w2 = “s” then if whorpronoun w1 then [w1; “is”] else [“of”; w1] else if w2 = “d” then [w1”ed”] else if w1 = “o” || w1 = “O” then if w2.[0] = ‘e’ && w2.[1] = ‘r’ then [w1”v”w2] else [“of”; w2] else if w2 = “t” then [w1; “it”] else [w1”’“w2]
For now we normalize words just by lowercasing, but see exercise 8.
In lexer we tokenize text: separate words and normalize them.
We store the number of each word occurrence, excluding XML tags.
{ open IndexParser let word = ref 0 let linebreaks = ref [] let
commentstart = ref Lexing.dummypos let resetasfile lexbuf s =General
purpose lexer function: let pos = lexbuf.Lexing.lexcurrp instart lexing
from a file. lexbuf.Lexing.lexcurrp <- { pos with Lexing.poslnum =
1;
posfname = s; posbol = pos.Lexing.poscnum; }; linebreaks := []; word :=
0 let nextline lexbuf =Old friend. …Besides changing position, remember
a line break. linebreaks := !word :: !linebreaks
let parseerrormsg startpos endpos report =General purpose lexer
function:
let clbeg =report a syntax error. startpos.Lexing.poscnum -
startpos.Lexing.posbol in ignore (Format.flushstrformatter ());
Printf.sprintf “File”%s”, lines %d-%d, characters %d-%d: %sn”
startpos.Lexing.posfname startpos.Lexing.poslnum endpos.Lexing.poslnum
clbeg (clbeg+(endpos.Lexing.poscnum - startpos.Lexing.poscnum))
report}let alphanum = [‘0’-‘9’ ‘a’-‘z’ ‘A’-‘Z’]let newline = (‘n’ |
“rn”)let xmlstart = (“” | “?>”)rule
token = parse | [’ ’ ‘t’] { token lexbuf } | newline { nextline lexbuf;
token lexbuf }
| ‘<’ alphanum+ ‘>’ as w
Dedicated token variants
for XML tags.{ OPEN w } | “</” alphanum+ ‘>’ as w { CLOSE w } |
“’tis” { word := !word+2; WORDS [“it”, !word-1; “is”, !word] } | “‘Tis”
{ word := !word+2; WORDS [“It”, !word-1; “is”, !word] } | “o’clock” {
incr word; WORDS [“o’clock”, !word] } | “O’clock” { incr word; WORDS
[“O’clock”, !word] } | (alphanum+ as w1)’’’ (alphanum+ as w2) { let
words = EngMorph.abridged w1 w2 in let words = List.map (fun w ->
incr word; w, !word) words in WORDS words } | alphanum+ as w { incr
word; WORDS [w, !word] } |”&” { incr word; WORDS [“&”, !word]
}
| [‘.’ ‘!’ ‘?’] as pDedicated tokens for punctuation { SENTENCE
(Char.escaped p) }so that it doesn’t break phrases. | “–” { PUNCT “–” }
| [‘,’ ‘:’ ’’’ ‘-’ ‘;’] as p { PUNCT (Char.escaped p) } | eof { EOF } |
xmlstart { commentstart := lexbuf.Lexing.lexcurrp; let s = comment []
lexbuf in COMMENT s } | { let pos = lexbuf.Lexing.lexcurrp in let pos’ =
{pos with Lexing.poscnum = pos.Lexing.poscnum + 1} in Printf.printf
“%s%!”
(parseerrormsg pos pos’ “lexer error”); failwith “LEXER ERROR” }
and comment strings = parse | xmlend { String.concat “” (List.rev strings) } | eof { let pos = !commentstart in let pos’ = lexbuf.Lexing.lexcurrp in Printf.printf “%sn%!” (parseerrormsg pos pos’ “lexer error: unclosed comment”); failwith “LEXER ERROR” } | newline { nextline lexbuf; comment (Lexing.lexeme lexbuf :: strings) lexbuf } | { comment (Lexing.lexeme lexbuf :: strings) lexbuf }
type token =| WORDS of (string * int) list| OPEN of string | CLOSE of string | COMMENT of string| SENTENCE of string | PUNCT of string| EOF
let invindex update ii lexer lexbuf = let rec aux ii = match lexer
lexbuf with | WORDS ws -> let ws = List.map (fun
(w,p)->EngMorph.normalize w, p) ws in aux (List.foldleft update ii
ws) | OPEN | CLOSE | SENTENCE | PUNCT | COMMENT -> aux ii
| EOF -> ii in aux ii
let phrase lexer lexbuf = let rec aux words = match lexer lexbuf with | WORDS ws -> let ws = List.map (fun (w,p)->EngMorph.normalize w) ws in aux (List.revappend ws words) | OPEN | CLOSE | SENTENCE | PUNCT | COMMENT -> aux words | EOF -> List.rev words in aux []
let update ii (w, p) = try let ps = List.assoc w ii inAdd position to
the postings list of w
. (w, p::ps) :: List.removeassoc w ii
with Notfound -> (w, [p])::iilet empty = []let find w ii = List.assoc
w iilet mapv f ii = List.map (fun (k,v)->k, f v) iilet index file =
let ch = openin file in let lexbuf = Lexing.fromchannel ch in
EngLexer.resetasfile lexbuf file; let ii = IndexParser.invindex update
empty EngLexer.token lexbuf in closein ch;Keep postings lists in
increasing order. mapv List.rev ii, List.rev !EngLexer.linebreakslet
findline linebreaks p =Recover the line in document of a position. let
rec aux line = function | [] -> line
| bp:: when p < bp -> line | ::breaks -> aux (line+1) breaks in
aux 1 linebreakslet search (ii, linebreaks) phrase = let lexbuf =
Lexing.fromstring phrase in EngLexer.resetasfile lexbuf (“search
phrase:”phrase); let phrase = IndexParser.phrase EngLexer.token lexbuf
in let rec aux wpos = functionMerge postings lists for words in query: |
[] -> wpos
no more words in query;| w
::ws
->for positions of w
, keep those that are next to let
nwpos = find w ii infiltered positions of previous word. aux
(List.filter (fun p->List.mem (p-1) wpos) nwpos) ws in let wpos =
match phrase with | [] -> []No results for an empty query.
| w::ws -> aux (find w ii) ws in List.map (findline linebreaks)
wposAnswer in terms of document lines.
let shakespeare = index “./shakespeare.xml”let query q = let lines = search shakespeare q in Printf.printf “%s: lines %sn%!” q (String.concat “,” (List.map stringofint lines))
Test: 200 searches of the queries:
[“first witch”; “wherefore art thou”; “captain’s captain”; “flatter’d”; “of Fulvia”; “that which we call a rose”; “the undiscovered country”]
Invocation:
ocamlbuild InvIndex.native -libs unix --
Time: 7.3s
mapv
).ocamlbuild InvIndex1.native -libs unix --
ocamlbuild InvIndex2.native -libs unix --
Pairs of words are much less frequent than single words so storing them means less work for postings lists merging.
Can result in much bigger index size: \min (W^2, N) where W is the number of distinct words and N the total number of words in documents.
Invocation that gives us stack backtraces:
ocamlbuild InvIndex3.native -cflag -g -libs unix; export OCAMLRUNPARAM="b"; ./InvIndex3.native
Time: 2.4s – disappointing.
first(w)
– first position in documents at which
w
appearslast(w)
– last position of w
next(w,cp)
– first position of w
after
position cp
prev(w,cp)
– last position of w
before
position cp
next
and prev
operations in
stages:
cp
of last found word or phrase.let rec nextphrase ii phrase cp =Return the beginning and end
position let rec aux cp = functionof occurrence of phrase
after position cp
. | [] -> raise NotfoundEmpty phrase
counts as not occurring. | [w] ->Single or last word of phrase has
the same let np = next ii w cp in np, np
beg. and end
position.| w::ws ->After locating the endp. move back. let np, fp =
aux (next ii w cp) ws in prev ii w np, fp inIf distance is this small,
let np, fp = aux cp phrase inwords are consecutive. if fp - np =
List.length phrase - 1 then np, fp else nextphrase ii phrase fp
let search (ii, linebreaks) phrase = let lexbuf = Lexing.fromstring
phrase in EngLexer.resetasfile lexbuf (“search phrase:”phrase); let
phrase = IndexParser.phrase EngLexer.token lexbuf in let rec aux cp =
tryFind all occurrences of the phrase. let np, fp = nextphrase ii phrase
cp in
np :: aux fp with Notfound -> [] inMoved past last occurrence.
List.map (findline linebreaks) (aux (-1))
module S = Set.Make(struct type t=int let compare i j = i-j end)let
update ii (w, p) = (try let ps = Hashtbl.find ii w in Hashtbl.replace ii
w (S.add p ps) with Notfound -> Hashtbl.add ii w (S.singleton p));
iilet first ii w = S.minelt (find w ii)The functions raise Not_foundlet
last ii w = S.maxelt (find w ii)whenever such position would not
exist.let prev ii w cp = let ps = find w ii inSplit the set into
elements let smaller, , = S.split cp ps insmaller and bigger than
cp
. S.maxelt smallerlet next ii w cp = let ps = find w ii
in let , , bigger = S.split cp ps in S.minelt bigger
ocamlbuild InvIndex4.native -libs unix --
let prev ii w cp = let ps = find w ii in let rec aux b e =We
implement binary search separately for prev
if e-b <= 1
then ps.(b)to make sure here we return less than cp
else
let m = (b+e)/2 in if ps.(m) < cp then aux m e
else aux b
m in let l = Array.length ps in if l = 0 || ps.(0) >= cp then raise
Notfound else aux 0 (l-1)let next ii w cp = let ps = find w ii in let
rec aux b e = if e-b <= 1 then ps.(e)and here more than
cp
. else let m = (b+e)/2 in if ps.(m) <= cp then aux m e
else aux b m in let l = Array.length ps in if l = 0 || ps.(l-1) <= cp
then raise Notfound else aux 0 (l-1)
InvIndex5.ml
. Time: 2.4slet prev ii w cp = let cw,ps = find w ii inFor each word we add a cell with last visited occurrence. let l = Array.length ps in if l = 0 || ps.(0) >= cp then raise Notfound else if ps.(l-1) < cp then cw := l-1 else (Reset pointer if current position is not ‘‘ahead’’ of it. if !cw < l-1 && ps.(!cw+1) < cp then cw := l-1;Otherwise scan while ps.(!cw) >= cp do decr cw donestarting from last visited. ); ps.(!cw)let next ii w cp = let cw,ps = find w ii in let l = Array.length ps in if l = 0 || ps.(l-1) <= cp then raise Notfound else if ps.(0) > cp then cw := 0 else (Reset pointer if current position is not ahead of it. if !cw > 0 && ps.(!cw-1) > cp then cw := 0; while ps.(!cw) <= cp do incr cw done ); ps.(!cw)
End of index
-building function:
mapv (fun ps->ref 0, Array.oflist (List.rev ps)) ii,…
File: InvIndex6.ml
Time: 2.8s
let next ii w cp = let cw,ps = find w ii in let l = Array.length ps
in if l = 0 || ps.(l-1) <= cp then raise Notfound; let rec jump (b,e
as bounds) j =Locate the interval with cp
inside. if e <
l-1 && ps.(e) <= cp then jump (e,e+j) (2*j) else bounds in
let rec binse b e =Binary search over that interval. if e-b <= 1 then
e else let m = (b+e)/2 in if ps.(m) <= cp then binse m e else binse b
m in if ps.(0) > cp then cw := 0 else ( let b =The invariant is that
ps.(b) <= cp
. if !cw > 0 && ps.(!cw-1) <=
cp then !cw-1 else 0 in let b,e = jump (b,b+1) 2 inLocate interval
starting near !cw
. let e = if e > l-1 then l-1 else e in
cw := binse b e ); ps.(!cw)
prev
is symmetric to next
.InvIndex7.ml
Exercise 1.
(Exercise 6.1 from “Modern Compiler Implementation in ML” by
Andrew W. Appel.) Using the ocamlopt
compiler with
parameter -S
and other parameters turning on all possible
compiler optimizations, evaluate the compiled programs by these
criteria:
b
is live across more than one
procedure call, is it kept in a callee-save register? Explain how it
would speed up the program:let f a = let b = a+1 in let c = g () in let
d = h c in b+cx
is never live across a procedure
call, is it properly kept in a caller-save register? Explain how doing
thes would speed up the program:let h y = let x = y+1 in let z = f y in
f zExercise 2.
As above, verify whether escaping variables of a function are kept in a closure corresponding to the function, or in closures corresponding to the local, i.e. nested, functions that are returned from the function (or assigned to a mutable field).
Exercise 3.
As above, verify that OCaml compiler performs inline expansion of small functions. Check whether the compiler can inline, or specialize (produce a local function to help inlining), recursive functions.
Exercise 4.
Write a “.mll
program” that anonymizes, or masks, text.
That is, it replaces identified probable full names (of persons,
companies etc.) with fresh shorthands Mr. A, Ms. B, or
Mr./Ms. C when the gender cannot be easily determined. The same
(full) name should be replaced with the same letter.
Exercise 5.
In the lexer EngLexer we call function abridged
from the
module EngMorph. Inline the operation of abridged
into the
lexer by adding a new regular expression pattern for each if clause.
Assess the speedup on the Shakespeare corpus and the
readability and either keep the change or revert it.
Exercise 6.
Make the lexer re-entrant for the second Menhir example (toy English grammar parser).
Exercise 7.
Make the determiner optional in the toy English grammar.
Exercise 8.
Integrate into the Phrase search example, the Porter
Stemmer whose source is in the stemmer.ml
file.
Exercise 9.
Revisit the search engine example from lecture 6.
Exercise 10.
Write an XML parser tailored to the shakespeare.xml
corpus provided with the phrase search example. Modify the phrase search
engine to provide detailed information for each found location,
e.g. which play and who speaks the phrase.
Zippers. Functional Reactive Programming. GUIs.
*‘‘Zipper’‘* in Haskell Wikibook and ‘‘The Zipper’’
by Gerard Huet ‘‘How froc
works’’ by Jacob Donham
‘‘The Haskell School of Expression’’ by Paul Hudak
‘‘Deprecating the Observer Pattern with
Scala.React
’’ by Ingo Maier, Martin Odersky
If you see any error on the slides, let me know!
type btree = Tip | Node of int * btree * btree
\begin{matrix} T & = & 1 + xT^2\\\\\\ \frac{\partial T}{\partial x} & = & 0 + T^2 + 2 xT \frac{\partial T}{\partial x} = TT + 2 xT \frac{\partial T}{\partial x} \end{matrix}
type btree_dir = LeftBranch | RightBranch
type btree_deriv =
of btree * btree
| Here of btree_dir * int * btree * btree_deriv | Below
Location = context + subtree! But there’s a problem above.
But we cannot easily move the location if Here is at the bottom.
The part closest to the location should be on top.
Revisiting equations for trees and lists:
\begin{matrix} T & = & 1 + xT^2\\\\\\ \frac{\partial T}{\partial x} & = & 0 + T^2 + 2 xT \frac{\partial T}{\partial x}\\\\\\ \frac{\partial T}{\partial x} & = & \frac{T^2}{1 - 2 xT}\\\\\\ L (y) & = & 1 + yL (y)\\\\\\ L (y) & = & \frac{1}{1 - y}\\\\\\ \frac{\partial T}{\partial x} & = & T^2 L (2 xT) \end{matrix}
I.e. the context can be stored as a list with the root as the last node.
Contexts of subtrees are more useful than of single elements.
type ’a tree = Tip | Node of ’a tree * ’a * ’a treetype treedir = Leftbr | Rightbrtype ’a context = (treedir * ’a * ’a tree) listtype ’a location = {sub: ’a tree; ctx: ’a context}let access {sub} = sublet change {ctx} sub = {sub; ctx}let modify f {sub; ctx} = {sub=f sub; ctx}
We can imagine a location as a rooted tree, which is hanging pinned at one of its nodes. Let’s look at pictures inhttp://en.wikibooks.org/wiki/Haskell/Zippers
Moving around:
let ascend loc = match loc.ctx with | [] -> loc
Or
raise exception.| (Leftbr, n, l) :: upctx -> {sub=Node (l, n,
loc.sub); ctx=upctx} | (Rightbr, n, r) :: upctx -> {sub=Node
(loc.sub, n, r); ctx=upctx}let descleft loc = match loc.sub with | Tip
-> loc
Or raise exception.| Node (l, n, r) -> {sub=l;
ctx=(Rightbr, n, r)::loc.ctx}let descright loc = match loc.sub with |
Tip -> loc
Or raise exception.| Node (l, n, r) ->
{sub=r; ctx=(Leftbr, n, l)::loc.ctx}
Following The Zipper, let’s look at a tree with arbitrary number of branches.
type doc = Text of string | Line | Group of doc listtype context = (doc list * doc list) listtype location = {sub: doc; ctx: context}
let goup loc = match loc.ctx with | [] -> invalidarg “goup: at
top” | (left, right) :: upctx ->Previous subdocument and its
siblings.
{sub=Group (List.rev left @ loc.sub::right); ctx=upctx}let goleft loc =
match loc.ctx with | [] -> invalidarg “goleft: at top” | (l::left,
right) :: upctx ->Left sibling of previous subdocument. {sub=l;
ctx=(left, loc.sub::right) :: upctx} | ([], ) :: -> invalidarg
“goleft: at first”
let goright loc = match loc.ctx with | [] -> invalidarg “goright: at top” | (left, r::right) :: upctx -> {sub=r; ctx=(loc.sub::left, right) :: upctx} | (, []) :: -> invalidarg “goright: at last”let godown loc =Go to the first (i.e. leftmost) subdocument. match loc.sub with | Text -> invalidarg “godown: at text” | Line -> invalidarg “godown: at line” | Group [] -> invalidarg “godown: at empty” | Group (doc::docs) -> {sub=doc; ctx=([], docs)::loc.ctx}
type op = Add | Multype expr = Val of int | Var of string | App of expropexprtype exprdir = Leftarg | Rightargtype context = (exprdir * op * expr) listtype location = {sub: expr; ctx: context}
p
.let rec findaux p e = if p e then Some (e, []) else match e with | Val | Var -> None | App (l, op, r) -> match findaux p l with | Some (sub, upctx) -> Some (sub, (Rightarg, op, r)::upctx) | None -> match findaux p r with | Some (sub, upctx) -> Some (sub, (Leftarg, op, l)::upctx) | None -> None
let find p e = match findaux p e with | None -> None | Some (sub, ctx) -> Some {sub; ctx=List.rev ctx}
let rec pullout loc = match loc.ctx with | [] ->
loc
Done.| (Leftarg, op, l) :: upctx ->D [e \circ C [x]] \Rightarrow D [C [x] \circ
e] pullout {loc with ctx=(Rightarg, op, l) :: upctx} | (Rightarg,
op1, e1) :: (, op2, e2) :: upctx when op1 = op2 ->D [(C [x] \circ e_{1}) \circ e_{2}] / D [e_{2} \circ
(C [x] \circ e_{1})] \Rightarrow D [C [x] \circ (e_{1} \circ
e_{2})] pullout {loc with ctx=(Rightarg, op1, App(e1,op1,e2)) ::
upctx} | (Rightarg, Add, e1) :: (, Mul, e2) :: upctx -> pullout {loc
with ctx=D [(C [x] + e_{1}) e_{2}] / D [e_{2}
(C [x] + e_{1})] \Rightarrow D [C [x] e_{2} + e_{1} e_{2}]
(Rightarg, Mul, e2) :: (Rightarg, Add, App(e1,Mul,e2)) :: upctx} |
(Rightarg, op, r)::upctx ->Move up the context. pullout
{sub=App(loc.sub, op, r); ctx=upctx}
Since operators are commutative, we ignore the direction for the second piece of context above.
Test:
let (+) a b = App (a, Add, b)let ( * ) a b = App (a, Mul, b)let (!) a = Val alet x = Var “x”let y = Var “y”let ex = !5 + y * (!7 + x) * (!3 + y)let loc = find (fun e->e=x) exlet sol = match loc with | None -> raise Notfound | Some loc -> pullout loc# let = expr2str sol;;- : string = “(((xy)(3+y))+(((7y)(3+y))+5))”
For best results we can iterate the pull_out
function until fixpoint.
'a Froc_sa.t
, and the ability to modify them is exposed by
type 'a Froc_sa.u
– the writeables.The monadic value 'a changeable
will be the
dependency graph of the computation of the represented value
'a
.
Let’s look at the example in “How froc works”, representing computation
let u = v / w + x * y + z
and its state with partial results memoized
where n0, n1, n2
are interior nodes of
computation.
Modify inputs v
and z
simultaneously
We need to update n2
before u
.
We use the gray numbers – the order of computation – for the
order of update of n0
, n2
and
u
.
Similarly to parallel
in the concurrency monad, we
provide bind2
, bind3
, … – and corresponding
lift2
, lift3
, … – to introduce nodes with
several children.
let n0 = bind2 v w (fun v w -> return (v / w)) let n1 = bind2 x y (fun x y -> return (x * y)) let n2 = bind2 n0 n1 (fun n0 n1 -> return (n0 + n1)) let u = bind2 n2 z (fun n2 z -> return (n2 + z))
Do-notation is not necessary to have readable expressions.
let (/) = lift2 (/) let ( * ) = lift2 ( * ) let (+) = lift2 (+) let u = v / w + x * y + z
As in other monads, we can decrease overhead by using bigger chunks.
let n0 = blift2 v w (fun v w -> v / w) let n2 = blift3 n0 x y (fun n0 x y -> n0 + x * y) let u = blift2 n2 z (fun n2 z -> n2 + z)
We have a problem if we recompute all nodes by order of computation.
let b = x >>= fun x -> return (x = 0) let n0 = x >>= fun x -> return (100 / x) let y = bind2 b n0 (fun b n0->if b then return 0 else n0)
Rather than a signle “time” stamp, we store intervals: begin and end of computation
When updating the y
node, we first detach nodes in
range 4-9 from the graph.
When value of b
does not change, then we skip
updating y
and proceed with updating n0
.
y
with time stamp smaller than
y
change.y
is a link to the value of
n0
so it will change anyway.We need memoization to re-attach the same nodes in case they don’t need updating.
Download Froc from https://github.com/jaked/froc/downloads
Install for example with
cd froc-0.2a; ./configure; make all; sudo make install
Frocsa (for self-adjusting) exports the monadic type
t
for changeable computation, and a handle type
u
for updating the computation.
open Frocsatype tree =Binary tree with nodes storing their screen location.| Leaf of int * intWe will grow the tree| Node of int * int * tree t * tree tby modifying subtrees.
let rec display px py t =Displaying the tree is changeable effect: match t withwhenever the tree changes, displaying will be updated. | Leaf (x,
let growat (x, depth, upd) = let xl = x-f2i
(width.(2.0(\sim-.(i2f
(depth+1))))) in let l, updl = changeable (Leaf (xl, (depth+1)20))
in
let xr = x+f2i (width.(2.0(\sim-.(i2f (depth+1))))) in let r, updr =
changeable (Leaf (xr, (depth+1)20)) in write upd (Node (x,
depth*20, l, r));Update the old leaf propagate ();and keep handles to
make future updates. [xl, depth+1, updl; xr, depth+1, updr]
let rec loop t subts steps = if steps <= 0 then () else loop t
(concatmap growat subts) (steps-1)let incremental steps () =
Graphics.opengraph ” 1024x600”; let t, u = changeable (Leaf (512, 20))
in
let d = t >>= display (f2i (width /. 2.)) 0 inDisplay once loop t
[512, 1, u] steps;– new nodes will be drawn automatically.
Graphics.closegraph ();;
Compare with rebuilding and redrawing the whole tree. Unfortunately the overhead of incremental computation is quite large. Byte code run:
depth |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
incremental |
0.66s |
1s |
2.2s |
4.4s |
9.3s |
21s |
50s |
140s |
255s |
rebuilding |
0.5s |
0.63s |
1.3s |
3s |
5.3s |
13s |
39s |
190s |
FRP is an attempt to declaratively deal with time.
Behaviors are functions of time.
Events are sets of (time, value) pairs.
Two problems
FRP is synchronous: it is possible to set up for events to happen at the same time, and it is continuous: behaviors can have details at arbitrary time resolution.
Ideally we would define:
type time = floattype ’a behavior = time -> ’aArbitrary function.type ’a event = (’a, time) streamIncreasing time instants.
Forcing a lazy list (stream) of events would wait till an event arrives.
But behaviors need to react to external events:
type useraction =| Key of char * bool| Button of int * int * bool * bool| MouseMove of int * int| Resize of int * inttype ’a behavior = useraction event -> time -> ’a
Scanning through an event list since the beginnig of time till current time, each time we evaluate a behavior – very wasteful wrt. time&space.
Producing a stream of behaviors for the stream of time allows to forget about events already in the past.
type ’a behavior = useraction event -> time stream -> ’a stream
Next optimization is to pair user actions with sampling times.
type ’a behavior = (useraction option * time) stream -> ’a stream
None action corresponds to sampling time when nothing happens.
Turning behaviors and events from functions of time into input-output streams is similar to optimizing interesction of ordered lists from O (mn) to O (m + n) time.
Now we can in turn define events in terms of behaviors:
type ’a event = ’a option behavior
although it betrays the discrete character of events (happening at points in time rather than varying over intervals of time).
We’ve gotten very close to stream processing as discussed in lecture 7.
Behaviors are monadic (but see next point) – in original specification:
type ’a behavior = time -> ’aval return : ’a -> ’a behaviorlet return a = fun -> aval bind : ’a behavior -> (’a -> ’b behavior) -> ’b behaviorlet bind a f = fun t -> f (a t) t
As we’ve seen with changeables, we mostly use lifting. In Haskell world we’d call behaviors applicative. To build our own lifters in any monad:
val ap : (’a -> ’b) monad -> ’a monad -> ’b monadlet ap fm am = perform f <– fm; a <– am; return (f a)
parallel
for concurrent computing.Going from events to behaviors. until
and
switch
have type
’a behavior -> ’a behavior event -> ’a behavior
step
has type
’a -> ’a event -> ’a behavior
until b es
behaves as b
until the first
event in es
, then behaves as the behavior in that
eventswitch b es
behaves as the behavior from the last event
in es
prior to current time, if any, otherwise as
b
step a b
starts with behavior returning a
and then switches to returning the value of the last event in
b
(prior to current time) – a step function.We will use “signal” to refer to a behavior or an event. But often “signal” is used as our behavior (check terminology when looking at a new FRP library).
The stream processing infrastructure should be familiar.
type ’a stream = ’a stream Lazy.tand ’a stream = Cons of ’a * ’a
streamlet rec lmap f l = lazy ( let Cons (x, xs) = Lazy.force l in Cons
(f x, lmap f xs))let rec liter (f : ’a -> unit) (l : ’a stream) :
unit = let Cons (x, xs) = Lazy.force l in f x; liter f xslet rec lmap2 f
xs ys = lazy (
let Cons (x, xs) = Lazy.force xs in let Cons (y, ys) = Lazy.force ys
in
Cons (f x y, lmap2 f xs ys))let rec lmap3 f xs ys zs = lazy ( let Cons
(x, xs) = Lazy.force xs in let Cons (y, ys) = Lazy.force ys in let Cons
(z, zs) = Lazy.force zs in Cons (f x y z, lmap3 f xs ys zs))let rec
lfold acc f (l : ’a stream) = lazy ( let Cons (x, xs) = Lazy.force l
inFold a function over the stream let acc = f acc x inproducing a stream
of partial results.
Cons (acc, lfold acc f xs))
Since a behavior is a function of user actions and sample times, we need to ensure that only one stream is created for the actual input stream.
type (‘a, ’b) memo1 = {memof : ’a -> ’b; mutable memor : (’a * ’b)
option}let memo1 f = {memof = f; memor = None}let memo1app f x = match
f.memor with | Some (y, res) when x == y -> res
Physical
equality is OK –| ->external input is ‘‘physically’’ unique. let res
= f.memof x inWhile debugging, we can monitor f.memor <- Some (x,
res);whether f.memor = None before. reslet ($) = memo1apptype ’a
behavior =
((useraction option * time) stream, ’a stream) memo1
The monadic/applicative functions to build complex behaviors.
.ml
files,
work together with an .mli
file to catch problems early.
You can later add more type annotations as needed to find out what’s
wrong.let returnB x : ’a behavior = let rec xs = lazy (Cons (x, xs)) in memo1 (fun -> xs)let ( !* ) = returnBlet liftB f fb = memo1 (fun uts -> lmap f (fb $ uts))let liftB2 f fb1 fb2 = memo1 (fun uts -> lmap2 f (fb1 $ uts) (fb2 $ uts))let liftB3 f fb1 fb2 fb3 = memo1 (fun uts -> lmap3 f (fb1 $ uts) (fb2 $ uts) (fb3 $ uts))let liftE f (fe : ’a event) : ’b event = memo1 (fun uts -> lmap (function Some e -> Some (f e) | None -> None) (fe $ uts))let (=>>) fe f = liftE f felet (->>) e v = e =>> fun -> v
Creating events out of behaviors.
let whileB (fb : bool behavior) : unit event = memo1 (fun uts
->
lmap (function true -> Some () | false -> None) (fb $ uts))let
unique fe : ’a event = memo1 (fun uts -> let xs = fe $ uts in
lmap2 (fun x y -> if x = y then None else y) (lazy (Cons (None, xs)))
xs)let whenB fb = memo1 (fun uts -> unique (whileB fb) $ uts)let
snapshot fe fb : (’a * ’b) event = memo1 (fun uts -> lmap2 (fun
x->function Some y -> Some (y,x) | None -> None) (fb $ uts) (fe
$ uts))
Creating behaviors out of events.
let step acc fe =The step function: value of last event. memo1 (fun uts -> lfold acc (fun acc -> function None -> acc | Some v -> v) (fe $ uts))let stepaccum acc ff =Transform a value by a series of functions. memo1 (fun uts -> lfold acc (fun acc -> function | None -> acc | Some f -> f acc) (ff $ uts))
To numerically integrate a behavior, we need to access the sampling times.
let integral fb = let rec loop t0 acc uts bs = let Cons ((,t1), uts) = Lazy.force uts in let Cons (b, bs) = Lazy.force bs in let acc = acc +. (t1 -. t0) *. b inb =\operatorname{fb} (t_{1}), \operatorname{acc} \approx \int_{t \leqslant t_{0}} f. Cons (acc, lazy (loop t1 acc uts bs)) in memo1 (fun uts -> lazy ( let Cons ((,t), uts’) = Lazy.force uts in Cons (0., lazy (loop t 0. uts’ (fb $ uts)))))
User actions:
let lbp : unit event = memo1 (fun uts -> lmap (function Some(Button(,)), -> Some() | -> None) uts)let mm : (int * int) event = memo1 (fun uts -> lmap (function Some(MouseMove(x,y)), ->Some(x,y) | ->None) uts)let screen : (int * int) event = memo1 (fun uts -> lmap (function Some(Resize(x,y)), ->Some(x,y) | ->None) uts)let mousex : int behavior = step 0 (liftE fst mm)let mousey : int behavior = step 0 (liftE snd mm)let width : int behavior = step 640 (liftE fst screen)let height : int behavior = step 512 (liftE snd screen)
A scene graph is a data structure that represents a “world” which can be drawn on screen.
type scene =| Rect of int * int * int * intposition, width, height|
Circle of int * int * intposition, radius| Group of scene list| Color of
Graphics.color * scene
color of subscene objects|Translate
of float * float * sceneadditional offset of origin
Drawing a scene explains what we mean above.
let draw sc = let f2i = intoffloat in let open Graphics in let rec
aux tx ty = functionAccumulate translations. | Rect (x, y, w, h)
->
fillrect (f2i tx+x) (f2i ty+y) w h | Circle (x, y, r) -> fillcircle
(f2i tx+x) (f2i ty+y) r | Group scs -> List.iter (aux tx ty)
scs
\swarrow Set color for
sc
objects.| Color (c, sc) -> setcolor c; aux tx ty sc |
Translate (x, y, sc) -> aux (tx+.x) (ty+.y) sc in
cleargraph ();‘‘Fast and clean’’ removing of previous picture. aux 0. 0.
sc; synchronize ()Synchronize the double buffer – avoiding
flickering.
An animation is a scene behavior. To animate it we need to create the input stream: the user actions and sampling times stream.
reactimate
, asking for (i.e. passing as argument) a
producer of user actions and a consumer of scene graphs (like
draw
).let reactimate (anim : scene behavior) = let open Graphics in let notb = function Some (Button (,)) -> false | -> true in let current oldm oldscr (oldu, t0) = let rec delay () = let t1 = Unix.gettimeofday () in let d = 0.01 -. (t1 -. t0) in try if d > 0. then Thread.delay d; Unix.gettimeofday () with Unix.Unixerror (( Unix.EAGAIN ), , ) -> delay () in let t1 = delay () in let s = Graphics.waitnextevent [Poll] in let x = s.mousex and y = s.mousey and scrx = Graphics.sizex () and scry = Graphics.sizey () in let ue = if s.keypressed then Some (Key s.key) else if (scrx, scry) <> oldscr then Some (Resize (scrx,scry)) else if s.button && notb oldu then Some (Button (x, y)) else if (x, y) <> oldm then Some (MouseMove (x, y)) else None in (x, y), (scrx, scry), (ue, t1) in opengraph ““;Open window. displaymode false;Draw using double buffering. let t0 = Unix.gettimeofday () in let rec utstep mpos scr ut = lazy ( let mpos, scr, ut = current mpos scr ut in Cons (ut, utstep mpos scr ut)) in let scr = Graphics.sizex (), Graphics.sizey () in let ut0 = Some (Resize (fst scr, snd scr)), t0 in liter draw (anim $ lazy (Cons (ut0, utstep (0,0) scr ut0))); closegraph ()Close window – unfortunately never happens.
General-purpose behavior operators.
let (+) = liftB2 (+)let (-) = liftB2 (-)let ( *** ) = liftB2 ( * )let (/) = liftB2 (/)let (&&) = liftB2 (&&)let (||) = liftB2 (||)let (<) = liftB2 (<)let (>*) = liftB2 (>)
The walls are drawn on left, top and right borders of the window.
let walls = liftB2 (fun w h -> Color (Graphics.blue, Group [Rect
(0, 0, 20, h-1); Rect (0, h-21, w-1, 20); Rect (w-21, 0, 20,
h-1)]))
width height
The paddle is tied to the mouse at the bottom border of the window.
let paddle = liftB (fun mx -> Color (Graphics.black, Rect (mx, 0, 50, 10))) mousex
The ball has a velocity in pixels per second. It bounces from the walls, which is hard-coded in terms of distance from window borders.
vel
value
changing sign at each horizontal bounce.let pbal vel = let rec xvel uts = stepaccum vel (xbounce ->> (\sim-.)) $ uts and xvel = {memof = xvel; memor = None} and xpos uts = (liftB intoffloat (integral xvel) +* width /* !2) $ uts and xpos = {memof = xpos; memor = None} and xbounce uts = whenB ((xpos > width -* !27) || (xpos <* !27)) $ uts and xbounce = {memof = xbounce; memor = None} in let rec yvel uts = (stepaccum vel (ybounce ->> (\sim-.))) $ uts and yvel = {memof = yvel; memor = None} and ypos uts = (liftB intoffloat (integral yvel) + height /* !2) $ uts and ypos = {memof = ypos; memor = None} and ybounce uts = whenB ( (ypos > height -* !27) || ((ypos <* !17) && (ypos >* !7) && (xpos >* mousex) &&* (xpos <* mousex +* !*50))) $ uts and ybounce = {memof = ybounce; memor = None} in liftB2 (fun x y -> Color (Graphics.red, Circle (x, y, 6))) xpos ypos
Invocation:
ocamlbuild Lec10b.native -cflags -I,+threads -libs graphics,unix,threads/threads --
send
– starts an update
cycle. Signals cannot call send
, but can
send_deferred
which will send an event in next cycle.
fix_b
, fix_e
functions to define signals recursively. Current value refers to value
from previous update cycle, and defers next recursive step to next
cycle, until convergence.send_deferred
and fix_b
, fix_e
,
or can be invoked from outside Froc by sending events at
arbitrary times.
time
behavior that holds a clock
event value, events from “back-to-back” update cycles can be at the same
clock time although not simultaneous in this sense.send
, send_deferred
,
notify_e
, notify_b
and sample
:
sample
, notify_e
, notify_b
are used from outside the behavior (from its “environment”) analogously
to observing result of a function,send
, send_deferred
are used from outside
analogously to providing input to a function.Rather than following our incremental computing example (a scene with changeable parts), we follow our FRP example: a scene behavior.
First we introduce time:
open Froclet clock, tick = makeevent ()let time = hold (Unix.gettimeofday ()) clock
Next we define integration:
let integral fb = let aux (sum, t0) t1 = sum +. (t1 -. t0) *. sample fb, t1 in collectb aux (0., sample time) clock
For convenience, the integral remembers the current upper limit of integration. It will be useful to get the integer part:
let integres fb = lift (fun (v,) -> intoffloat v) (integral fb)
We can also define integration in pure style:
let pair fa fb = lift2 (fun x y -> x, y) fa fblet integralnice fb = let samples = changes (pair fb time) in let aux (sum, t0) (fv, t1) = sum +. (t1 -. t0) *. fv, t1 in collectb aux (0., sample time) samples
The initial value (0., sample time) is not “inside” the behavior so
sample
here does not spoil the pure style.
The scene
datatype and how we draw
a
scene does not change.
Signals which will be sent to behaviors:
let mousemovex, movemousex = makeevent ()let mousemovey, movemousey = makeevent ()let mousex = hold 0 mousemovexlet mousey = hold 0 mousemovexlet widthresized, resizewidth = makeevent ()let heightresized, resizeheight = makeevent ()let width = hold 640 widthresizedlet height = hold 512 heightresizedlet mbuttonpressed, pressmbutton = makeevent ()let keypressed, presskey = makeevent ()
The user interface main loop, emiting signals and observing behaviors:
let reactimate (anim : scene behavior) = let open Graphics in let rec loop omx omy osx osy omb t0 = let rec delay () = let t1 = Unix.gettimeofday () in let d = 0.01 -. (t1 -. t0) in try if d > 0. then Thread.delay d; Unix.gettimeofday () with Unix.Unixerror ((* Unix.EAGAIN *), , ) -> delay () in let t1 = delay () in let s = Graphics.waitnextevent [Poll] in let x = s.mousex and y = s.mousey and scrx = Graphics.sizex () and scry = Graphics.sizey () in if s.keypressed then send presskey s.key;We can send signals if scrx <> osx then send resizewidth scrx;one by one. if scry <> osy then send resizeheight scry; if s.button && not omb then send pressmbutton (); if x <> omx then send movemousex x; if y <> omy then send movemousey y; send tick t1; draw (sample anim);After all signals are updated, observe behavior. loop x y scrx scry s.button t1 in opengraph ““; displaymode false; loop 0 0 640 512 false (Unix.gettimeofday ()); closegraph ()
The simple behaviors as in Lec10b.ml
. Pragmatic
(impure) bouncing:
let pbal vel = let xbounce, bouncex = makeevent () in let ybounce, bouncey
=in let xvel = collectb (fun v -> $\sim$-.v) vel xbounce
makeevent () in let yvel = collectb (fun v -> $\sim$-.v) vel ybounce in let xpos =
2 in let ypos = integres yvel +* height /*
integres xvel +* width /* !*2 in let xbounce = whentrue ((xpos >* width -* !*27) ||*
!*27)) in notifye xbounce (send bouncex); let ybounce =
(xpos <* !*27) ||* ((ypos <*
whentrue ( (ypos >* height -* !*17) &&* (ypos >* !*7) &&* (xpos >* mousex) &&*
!*50))) in notifye ybounce (send bouncey);
(xpos <* mousex +* !*fun x y -> Color (Graphics.red, Circle (x, y, 6))) xpos ypos
lift4 ( (hold () xbounce) (hold () ybounce)
We hold on to xbounce and ybounce above to prevent garbage collecting them. We could instead remember them in the “toplevel”:
let pbal vel = … xbounce, ybounce, lift2 (fun x y -> Color (Graphics.red, Circle (x, y, 6))) xpos yposlet xb, yb, ball = pbal 100.let game = lift3 (fun walls paddle ball -> Group [walls; paddle; ball]) walls paddle ball
We can easily monitor signals while debugging, e.g.:
notifye xbounce (fun () -> Printf.printf “xbounce%!”); notifye ybounce (fun () -> Printf.printf “ybounce%!”);
Invocation:ocamlbuild Lec10c.native -cflags -I,+froc,-I,+threads -libs froc/froc,unix,graphics,threads/threads --
Real-world behaviors often are state machines, going through several stages. We don’t have declarative means for it yet.
We want a flow to be able to proceed through events: when the first event arrives we remember its result and wait for the next event, disregarding any further arrivals of the first event!
map
, or attaching a notification to a behavior change:
bind b1 (fun v1 -> notify_b ~now:false b2 (fun v2 ->
…)), will not work.We also want to be able to repeat or loop a flow, but starting from the notification of the first event that happens after the notification of the last event.
next e
is an event propagating only the first
occurrence of e
. This will be the basis of our
await
function.
The whole flow should be cancellable from outside at any time.
A flow is a kind of a lightweight thread as in end of
lecture 8, we’ll make it a monad. It only “stores” a non-unit value when
it await
s an event. But it has a primitive to
emit
values.
await
in the role of
suspend
.We build a module Flow with monadic type (’a, ’b) flow “storing”
'b
and emitting 'a
.
type (’a, ’b) flowtype cancellableA handle to cancel a flow (stop
further computation).val noopflow : (’a, unit) flowSame as
return
().val return : ’b -> (’a, ’b) flowCompleted
flow.val await : ’b Froc.event -> (’a, ’b) flowWait and store
event:val bind :the principled way to input. (’a, ’b) flow -> (’b
-> (’a, ’c) flow) -> (’a, ’c) flowval emit : ’a -> (’a, unit)
flowThe principled way to output.val cancel : cancellable -> unitval
repeat :Loop the given flow and store the stop event. ?until:’a
Froc.event -> (’b, unit) flow -> (’b, ’a) flowval eventflow : (’a,
unit) flow -> ’a Froc.event * cancellableval behaviorflow :The
initial value of a behavior and a flow to update it.
’a -> (’a, unit) flow -> ’a Froc.behavior * cancellableval
iscancelled : cancellable -> bool
We follow our (or Lwt) implementation of lightweight threads, adapting it to the need of cancelling flows.
module F = Froctype ’a result =| Return of 'a
\downarrowNotifications to cancel when
cancelled.| Sleep of (’a -> unit) list * F.cancel ref list|
Cancelled| Link of ’a stateand ’a state = {mutable state : ’a
result}type cancellable = unit state
Functions find
, wakeup
,
connect
are as in lecture 8 (but connecting to cancelled
thread cancels the other thread).
Our monad is actually a reader monad over the result state. The
reader supplies the emit
function. (See exercise 10.)
type (’a, ’b) flow = (’a -> unit) -> ’b state
The return
and bind
functions are as in
our lightweight threads, but we need to handle cancelled flows: for
m = bind a b
, if a
is cancelled then
m
is cancelled, and if m
is cancelled then
don’t wake up b
:
let waiter x = if not (iscancelled m) then connect m (b
x emit) in …
await
is implemented like next
, but it
wakes up a flow:
let await t = fun emit -> let c = ref F.nocancel in let m =
{state=Sleep ([], [c])} in c := F.notifyecancel t begin fun r
->
F.cancel !c; c := F.nocancel; wakeup m r end; m
repeat
attaches the whole loop as a waiter for the
loop body.
let repeat ?(until=F.never) fa = fun emit -> let c = ref
F.nocancel in let out = {state=Sleep ([], [c])} in let cancelbody = ref
{state=Cancelled} in c := F.notifyecancel until begin fun tv ->
F.cancel !c; c := F.nocancel; Exiting the loop consists of cancelling
the loop body cancel !cancelbody; wakeup out tv
and waking
up loop waiters.end; let rec loop () = let a = find (fa emit) in
cancelbody := a; (match a.state with | Cancelled -> cancel out;
F.cancel !c | Return x -> failwith “loopuntil: not implemented for
unsuspended flows” | Sleep (xwaiters, xcancels) -> a.state <-
Sleep (loop::xwaiters, xcancels)
| Link -> assert false) in loop (); out
Example: drawing shapes.
Invocation:ocamlbuild Lec10d.native -pp "camlp4o monad/pa_monad.cmo" -libs froc/froc,graphics -cflags -I,+froc --
The event handlers and drawing/event dispatch loop
reactimate
is similar to the paddle game example (we
removed unnecessary events).
The scene is a list of shapes, the first shape is open.
type scene = (int * int) list listlet draw sc = let open Graphics
in
cleargraph (); (match sc with | [] -> () | opn::cld ->
drawpolyline (Array.oflist opn); List.iter (fillpoly -| Array.oflist)
cld); synchronize ()
We build a flow and turn it into a behavior to animate.
let painter = let cld = ref [] inGlobal state of painter. repeat
(perform
await mbuttonpressed;Start when button down. let opn = ref [] in
repeat (perform mpos <– await mousemove;\swarrowAdd next position to line. emit (opn
:= mpos :: !opn; !opn :: !cld))
\simuntil:mbuttonreleased;\swarrowStart new shape. emit (cld := !opn ::
!cld; opn := []; [] :: !cld))let painter, cancelpainter = behaviorflow
[] painterlet () = reactimate painter
Global state and thread-local state can be used with lightweight threads, but pay attention to semantics – which computations are inside the monad and which while building the initial monadic value.
return
and emit
arguments are not inside the monad. E.g. if in the “first line” of a
loop effects are executed only at the start of the loop – but if after
bind (“below first line” of a loop), at each step of the loop.let f = repeat (perform emit (Printf.printf “[0]%!”; ‘0’); () <– await aas; emit (Printf.printf “[1]%!”; ‘1’); () <– await bs; emit (Printf.printf “[2]%!”; ‘2’); () <– await cs; emit (Printf.printf “[3]%!”; ‘3’); () <– await ds; emit (Printf.printf “[4]%!”; ‘4’))let e, cancele = eventflow flet () = F.notifye e (fun c -> Printf.printf “flow: %c%!” c); Printf.printf “notification installed%!”let () = F.send a (); F.send b (); F.send c (); F.send d (); F.send a (); F.send b (); F.send c (); F.send d ()
[0]Only printed once – when building the
loop.notification installed
Only installed
after the first flow event sent.event: aEvent
notification (see source Lec10e.ml
).[1]Second
emit
computed after first await
returns.flow:
1Emitted signal.event: bNext event…[2]flow: 2event: c[3]flow: 3event:
d[4]flow: 4Last signal emitted from first turn of the loop –flow: 0and
first signal of the second turn (but [0]
not
printed).event: a[1]flow: 1event: b[2]flow: 2event: c[3]flow: 3event:
d[4]flow: 4flow: 0Program ends while flow in third turn of the loop.
let digits, digit = F.makeevent ()We represent the mechanicslet ops,
op = F.makeevent ()of the calculator directly as a flow.let dots, dot =
F.makeevent ()let calc =We need two state variables for two arguments of
calculation let f = ref (fun x -> x) and now = ref 0.0 inbut we
repeat (performremember the older argument in partial application. op
<– repeat
(performEnter the digits of a number (on later turns d <– await
digits;starting from the second digit) emit (now := 10. *. !now +. d;
!now)) \simuntil:ops;until operator
button is pressed.
emit (now := !f !now; f := op !now; !now); d <–
repeat
\nwarrowCompute the
result and ‘‘store away’’ the operator.(perform op <– await ops;
return (f := op !now)) \simuntil:digits;The user can pick a
different operator. emit (now := d; !now))Reset the state to a new
number.let calce, cancelcalc = eventflow calcNotifies display
update.
Widget toolkit Tk known from the Tcl language.
Invocation:ocamlbuild Lec10tk.byte -cflags -I,+froc -libs froc/froc -pkg labltk -pp "camlp4o monad/pa_monad.cmo" --
ocamlopt
(native).Layout of the calculator – common across GUIs.
let layout = [|[|“7”,’Di 7.; “8”,’Di 8.; “9”,’Di 9.; “+”,’O
(+.)|];
[|“4”,’Di 4.; “5”,’Di 5.; “6”,’Di 6.; “-”,’O (-.)|]; [|“1”,’Di 1.;
“2”,’Di 2.; “3”,’Di 3.; ““,’O ( .)|]; [|“0”,’Di 0.; “.”,’Dot;
“=”, ’O sk; “/”,’O (/.)|]|]
Every widget (window gadget) has a parent in which it is located.
Buttons have action associated with pressing them, labels just provide information, entries (aka. edit fields) are for entering info from keyboard.
command
argument.Frames in Tk group widgets.
The parent is sent as last argument, after optional labelled arguments.
let top = Tk.openTk ()let btnframe = Frame.create \simrelief:’Groove \simborderwidth:2 toplet buttons = Array.map (Array.map (function | text, ’Dot -> Button.create \simtext \simcommand:(fun () -> F.send dot ()) btnframe | text, ’Di d -> Button.create \simtext \simcommand:(fun () -> F.send digit d) btnframe | text, ’O f -> Button.create \simtext \simcommand:(fun () -> F.send op f) btnframe)) layoutlet result = Label.create \simtext:“0” \simrelief:’Sunken top
GUI toolkits have layout algorithms, so we only need to tell
which widgets hang together and whether they should fill all available
space etc. – via pack
, or grid
for
“rectangular” organization.
\simfill: the allocated space in
‘X
, ‘Y
, ‘Both
or
‘None
axes;\simexpand:
maximally how much space is allocated or only as needed.
\simanchor: allows to glue a
widget in particular direction (‘Center
, ‘E
,
‘Ne
etc.)
The grid
packing flexibility: \simcolumnspan and \simrowspan.
configure
functions accept the same arguments as
create
but change existing widgets.
let () = Wm.titleset top “Calculator”; Tk.pack [result] \simside:’Top \simfill:’X; Tk.pack [btnframe] \simside:’Bottom \simexpand:true;
Array.iteri (fun column ->Array.iteri (fun row button -> Tk.grid
\simcolumn \simrow [button])) buttons; Wm.geometryset
top “200x200”;
F.notifye calce (fun now -> Label.configure \simtext:(stringoffloat now) result);
Tk.mainLoop ()
LablGTk is build as an object-oriented layer over a low-level layer of functions interfacing with the GTk+ library, which is written in C.
In OCaml, object fields are only visible to object methods, and methods are called with # syntax, e.g. window#show ()
The interaction with the application is reactive:
event
.Automatic layout (aka. packing) seems less sophisticated than in Tk:
‘START
or
‘END
.Automatic grid layout is called table
.
‘X
,
‘Y
, ‘BOTH
, ‘NONE
.The coerce
method casts the type of the object (in
Tk there is coe
function).
Labels don’t have a dedicated module – see definition of
result
widget.
Widgets have setter methods widget#set_X
(instead of
a single configure
function in Tk).
Invocation:ocamlbuild Lec10gtk.native -cflags -I,+froc -libs froc/froc -pkg lablgtk2 -pp "camlp4o monad/pa_monad.cmo" --
The model part of application doesn’t change.
Setup:
let = GtkMain.Main.init ()let window = GWindow.window \simwidth:200 \simheight:200 \simtitle:“Calculator” ()let top = GPack.vbox \simpacking:window#add ()let result = GMisc.label \simtext:“0” \simpacking:top#add ()let btnframe = GPack.table \simrows:(Array.length layout) \simcolumns:(Array.length layout.(0)) \simpacking:top#add ()
Button actions:
let buttons = Array.map (Array.map (function | label, ’Dot -> let
b = GButton.button \simlabel () in let
= b#connect#clicked
\simcallback:(fun () -> F.send dot
()) in b | label, ’Di d ->
let b = GButton.button \simlabel () in
let = b#connect#clicked
\simcallback:(fun () -> F.send digit
d) in b | label, ’O f ->
let b = GButton.button \simlabel () in
let = b#connect#clicked
\simcallback:(fun () -> F.send op f)
in b)) layout
Button layout, result notification, start application:
let deleteevent = GMain.Main.quit (); falselet () = let = window#event#connect#delete \simcallback:deleteevent in Array.iteri (fun column->Array.iteri (fun row button -> btnframe#attach \simleft:column \simtop:row \simfill:’BOTH \simexpand:’BOTH (button#coerce)) ) buttons; F.notifye calce (fun now -> result#setlabel (stringoffloat now)); window#show (); GMain.Main.main ()
Functional Programming
Zippers, Reactivity, GUIs
Exercise 1: Introduce operators -, / into the context rewriting “pull out subexpression” example. Remember that they are not commutative.
Exercise 2: Add to the paddle game example:
Exercise 3: Our numerical integration function roughly corresponds to the rectangle rule. Modify the rule and write a test for the accuracy of:
rule.* http://en.wikipedia.org/wiki/Simpson%27s_rule
Exercise 4: Explain the recursive behavior of integration:
*Lec10b.ml*
, do we look at past
velocity to determine current position, at past position to determine
current velocity, both, or neither?*integral*
and *integral_nice*
in
*Lec10c.ml*
, what happens when we replace the former
with the latter in the *pbal*
function? How about
after rewriting *pbal*
into pure style as in the
following exercise?Exercise 5: Reimplement the Froc based
paddle ball example in a pure style: rewrite the pbal
function to not use notify_e
.
Exercise 6: * Our implementation of flows is a bit
heavy. One alternative approach is to use continuations, as in
Scala.React
. OCaml has a continuations library
Delimcc; for how it can cooperate with Froc, seehttp://ambassadortothecomputers.blogspot.com/2010/08/mixing-monadic-and-direct-style-code.html
Exercise 7: Implement parallel
for
flows, retaining coarse-grained implementation and using the event queue
from Froc somehow (instead of introducing a new job queue).
Exercise 8: Add quitting, e.g. via a
'q'
key press, to the painter example. Use the
is_cancelled
function.
Exercise 9: Our calculator example is not finished.
Implement entering decimal fractions: add handling of the
dots
event.
Exercise 10: The Flow module has reader monad functions that have not been discussed on slides:let local f m = fun emit -> m (fun x -> emit (f x))let localopt f m = fun emit -> m (fun x -> match f x with None -> () | Some y -> emit y)val local : (’a -> ’b) -> (’a, ’c) flow -> (’b, ’c) flowval localopt : (’a -> ’b option) -> (’a, ’c) flow -> (’b, ’c) flow
Implement an example that uses this compositionality-increasing capability.
The Expression Problem
The Expression Problem
Code organization, extensibility and reuse
Ralf Lämmel lectures on MSDN’s Channel 9:The Expression Problem, Haskell’s Type Classes
The old book Developing Applications with Objective Caml:Comparison of Modules and Objects, Extending Components
The new book Real World OCaml: Chapter 11: Objects, Chapter 12: Classes
Jacques Garrigue’s Code reuse through polymorphic variants,and Recursive Modules for Programming with Keiko Nakata
Graham Hutton’s and Erik Meijer’s Monadic Parser CombinatorsThe Expression Problem: Definition
The Expression Problem: design an implementation for expressions, where:
By extensibility we mean three conditions:
The name comes from an example: extend a language of expressions with new constructs:
Var
, \lambda-abstractions Abs
,
function applications App
;Var
, constants Num
,
addition Add
, multiplication Mult
; …and new oparations:
eval
;string_of
;free_vars
; …Functional Programming
Non-solution: ordinary Algebraic DatatypesPattern matching makes functional extensibility easy in functional programming.
Ensuring datatype extensibility is complicated when using standard variant types.
For brevity, we will place examples in a single file, but the component type and function definitions are not mutually recursive so can be put in separate modules.
Non-solution penalty points:
lexpr_t
) cannot be used with a value from a narrower
langugage (e.g. expr_t
).wrap
and unwrap
functions, adding tags e.g. Lambda
and
Expr
.unwrap
.Verdict: non-solution, but better than extensible variant types-based approach (next) and direct OOP approach (later).
type0.5emvar0.5em=0.5emstringVariables constitute a sub-language of
its own.We treat this sub-language slightly differently – no need for a
dedicated
variant.let0.5emevalvar0.5emwrap0.5emsub0.5em(s0.5em:0.5emvar)0.5em=0.5em0.5emtry0.5emList.assoc0.5ems0.5emsub0.5emwith0.5emNotfound0.5em->0.5emwrap0.5emstype0.5em’a0.5emlambda0.5em=Here
we define the sub-language of \lambda-expressions.0.5em0.5emVarL0.5emof0.5emvar0.5em|0.5emAbs0.5emof0.5emstring0.5em0.5em’a0.5em|0.5emApp0.5emof0.5em’a0.5em0.5em’aDuring
evaluation, we need to freshen variables to avoid
capturelet0.5emgensym0.5em=0.5emlet0.5emn0.5em=0.5emref0.5em00.5emin0.5emfun0.5em()0.5em->0.5emincr0.5emn;0.5em”“0.5emˆ0.5emstringofint0.5em!n(mistaking
distinct variables with the same
name).let0.5emevallambda0.5emevalrec0.5emwrap0.5emunwrap0.5emsubst0.5eme0.5em=0.5em0.5emmatch0.5emunwrap0.5eme0.5emwithAlternatively,
unwrapping could use an
exception,0.5em0.5em|0.5emSome0.5em(VarL0.5emv)0.5em->0.5emevalvar0.5em(fun0.5emv0.5em->0.5emwrap0.5em(VarL0.5emv))0.5emsubst0.5emv0.5em0.5em|0.5emSome0.5em(App0.5em(l1,0.5eml2))0.5em->but
we use the option type as it is
safer0.5em0.5em0.5em0.5emlet0.5eml1’0.5em=0.5emevalrec0.5emsubst0.5eml1
and
more flexible in this
context.0.5em0.5em0.5em0.5emand0.5eml2’0.5em=0.5emevalrec0.5emsubst0.5eml20.5eminRecursive
processing function returns
expression0.5em0.5em0.5em0.5em(match0.5emunwrap0.5eml1’0.5emwithof the
completed language, we
need0.5em0.5em0.5em0.5em|0.5emSome0.5em(Abs0.5em(s,0.5embody))0.5em->to
unwrap it into the current
sub-language.0.5em0.5em0.5em0.5em0.5em0.5emevalrec0.5em[s,0.5eml2’]0.5embody
The
recursive call is already
wrapped.0.5em0.5em0.5em0.5em
|0.5em0.5em _
->0.5emwrap0.5em(App0.5em(l1’,0.5eml2’)))Wrap into the completed
language.0.5em0.5em0.5emSome0.5em(Abs0.5em(s,0.5eml1))0.5em->0.5em0.5em0.5em0.5emlet0.5ems’0.5em=0.5emgensym0.5em()0.5eminRename
variable to avoid capture (\alpha-equivalence).0.5em0.5em0.5em0.5emwrap0.5em(Abs0.5em(s’,0.5emevalrec0.5em((s,0.5emwrap0.5em(VarL0.5ems’))::subst)0.5eml1))0.5em0.5em0.5emNone0.5em->0.5emeFalling-through
when not in the current
sub-language.type0.5emlambdat0.5em=0.5emLambdat0.5emof0.5emlambdat0.5emlambdaDefining
\lambda-expressionsas the completed
language,let0.5emrec0.5emeval10.5emsubst0.5em=and the corresponding
eval
function.0.5em0.5emevallambda0.5emeval10.5em0.5em0.5em0.5em(fun0.5eme0.5em->0.5emLambdat0.5eme)0.5em(fun0.5em(Lambdat0.5eme)0.5em->0.5emSome0.5eme)0.5emsubsttype0.5em’a0.5emexpr0.5em=The
sub-language of arithmetic
expressions.0.5em0.5emVarE0.5emof0.5emvar0.5em0.5emNum0.5emof0.5emint0.5em0.5emAdd0.5emof0.5em’a0.5em0.5em’a0.5em0.5emMult0.5emof0.5em’a0.5em0.5em’alet0.5emevalexpr0.5emevalrec0.5emwrap0.5emunwrap0.5emsubst0.5eme0.5em=0.5em0.5emmatch0.5emunwrap0.5eme0.5emwith0.5em0.5em0.5emSome0.5em(Num0.5em)0.5em->0.5eme0.5em0.5em0.5emSome0.5em(VarE0.5emv)0.5em->0.5em0.5em0.5em0.5emevalvar0.5em(fun0.5emx0.5em->0.5emwrap0.5em(VarE0.5emx))0.5emsubst0.5emv0.5em0.5em0.5emSome0.5em(Add0.5em(m,0.5emn))0.5em->0.5em0.5em0.5em0.5emlet0.5emm’0.5em=0.5emevalrec0.5emsubst0.5emm0.5em0.5em0.5em0.5emand0.5emn’0.5em=0.5emevalrec0.5emsubst0.5emn0.5emin0.5em0.5em0.5em0.5em(match0.5emunwrap0.5emm’,0.5emunwrap0.5emn’0.5emwithUnwrapping
to check if the
subexpressions0.5em0.5em0.5em0.5em0.5emSome0.5em(Num0.5emm’),0.5emSome0.5em(Num0.5emn’)0.5em->got
computed to
values.0.5em0.5em0.5em0.5em0.5em0.5emwrap0.5em(Num0.5em(m’0.5em+0.5emn’))0.5em0.5em0.5em0.5em->0.5emwrap0.5em(Add0.5em(m’,0.5emn’)))Here
m'
and n'
are
wrapped.0.5em0.5em0.5emSome0.5em(Mult0.5em(m,0.5emn))0.5em->0.5em0.5em0.5em0.5emlet0.5emm’0.5em=0.5emevalrec0.5emsubst0.5emm0.5em0.5em0.5em0.5emand0.5emn’0.5em=0.5emevalrec0.5emsubst0.5emn0.5emin0.5em0.5em0.5em0.5em(match0.5emunwrap0.5emm’,0.5emunwrap0.5emn’0.5emwith0.5em0.5em0.5em0.5em0.5emSome0.5em(Num0.5emm’),0.5emSome0.5em(Num0.5emn’)0.5em->0.5em0.5em0.5em0.5em0.5em0.5emwrap0.5em(Num0.5em(m’0.5em*0.5emn’))0.5em0.5em0.5em0.5em->0.5emwrap0.5em(Mult0.5em(m’,0.5emn’)))0.5em0.5em0.5emNone0.5em->0.5emetype0.5emexprt0.5em=0.5emExprt0.5emof0.5emexprt0.5emexprDefining
arithmetic expressionsas the completed
language,let0.5emrec0.5emeval20.5emsubst0.5em=aka. ‘‘tying the recursive
knot’‘.0.5em0.5emevalexpr0.5emeval20.5em0.5em0.5em0.5em(fun0.5eme0.5em->0.5emExprt0.5eme)0.5em(fun0.5em(Exprt0.5eme)0.5em->0.5emSome0.5eme)0.5emsubsttype0.5em’a0.5emlexpr0.5em=The
language merging \lambda-expressions
and arithmetic
expressions,0.5em0.5emLambda0.5emof0.5em’a0.5emlambda0.5em0.5emExpr0.5emof0.5em’a0.5emexprcan
also be used asa sub-language for further
extensions.let0.5emevallexpr0.5emevalrec0.5emwrap0.5emunwrap0.5emsubst0.5eme0.5em=0.5em0.5emevallambda0.5emevalrec0.5em0.5em0.5em0.5em(fun0.5eme0.5em->0.5emwrap0.5em(Lambda0.5eme))0.5em0.5em0.5em0.5em(fun0.5eme0.5em->0.5em0.5em0.5em0.5em0.5em0.5emmatch0.5emunwrap0.5eme0.5emwith0.5em0.5em0.5em0.5em0.5em0.5em0.5emSome0.5em(Lambda0.5eme)0.5em->0.5emSome0.5eme0.5em0.5em0.5em0.5em0.5em0.5em->0.5emNone)0.5em0.5em0.5em0.5emsubst0.5em0.5em0.5em0.5em(evalexpr0.5emevalrec
We
use the ‘‘fall-through’’ property of
eval_expr``0.5em0.5em0.5em0.5em0.5em0.5em0.5em
(fun0.5eme0.5em->0.5emwrap0.5em(Expr0.5eme))to
combine the
evaluators.0.5em0.5em0.5em0.5em0.5em0.5em0.5em(fun0.5eme0.5em->0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5emmatch0.5emunwrap0.5eme0.5emwith0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5emSome0.5em(Expr0.5eme)0.5em->0.5emSome0.5eme0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em->0.5emNone)0.5em0.5em0.5em0.5em0.5em0.5em0.5emsubst0.5eme)type0.5emlexprt0.5em=0.5emLExprt0.5emof0.5emlexprt0.5emlexprTying
the recursive knot one last
time.let0.5emrec0.5emeval30.5emsubst0.5em=0.5em0.5emevallexpr0.5emeval30.5em0.5em0.5em0.5em(fun0.5eme0.5em->0.5emLExprt0.5eme)0.5em0.5em0.5em0.5em(fun0.5em(LExprt0.5eme)0.5em->0.5emSome0.5eme)0.5emsubstLightweight
FP non-solution: Extensible Variant Types
type0.5emexpr0.5em=0.5em..This is how extensible variant types are defined.type0.5emvarname0.5em=0.5emstringtype0.5emexpr0.5em+=0.5emVar0.5emof0.5emstringWe add a variant case.let0.5emevalvar0.5emsub0.5em=0.5emfunction0.5em0.5em0.5emVar0.5ems0.5emas0.5emv0.5em->0.5em(try0.5emList.assoc0.5ems0.5emsub0.5emwith0.5emNotfound0.5em->0.5emv)0.5em0.5em0.5eme0.5em->0.5emelet0.5emgensym0.5em=0.5emlet0.5emn0.5em=0.5emref0.5em00.5emin0.5emfun0.5em()0.5em->0.5emincr0.5emn;0.5em”“0.5em0.5emstringofint0.5em!ntype0.5emexpr0.5em+=0.5emAbs0.5emof0.5emstring0.5em0.5emexpr0.5em0.5emApp0.5emof0.5emexpr0.5em0.5emexprThe sub-languagesare not differentiated by types, a shortcoming of this non-solution.let0.5emevallambda0.5emevalrec0.5emsubst0.5em=0.5emfunction0.5em0.5em0.5emVar0.5em0.5emas0.5emv0.5em->0.5emevalvar0.5emsubst0.5emv0.5em0.5em0.5emApp0.5em(l1,0.5eml2)0.5em->0.5em0.5em0.5em0.5emlet0.5eml2’0.5em=0.5emevalrec0.5emsubst0.5eml20.5emin0.5em0.5em0.5em0.5em(match0.5emevalrec0.5emsubst0.5eml10.5emwith0.5em0.5em0.5em0.5em0.5emAbs0.5em(s,0.5embody)0.5em->0.5em0.5em0.5em0.5em0.5em0.5emevalrec0.5em[s,0.5eml2’]0.5embody0.5em0.5em0.5em0.5em0.5eml1’0.5em->0.5emApp0.5em(l1’,0.5eml2’))0.5em0.5em0.5emAbs0.5em(s,0.5eml1)0.5em->0.5em0.5em0.5em0.5emlet0.5ems’0.5em=0.5emgensym0.5em()0.5emin0.5em0.5em0.5em0.5emAbs0.5em(s’,0.5emevalrec0.5em((s,0.5emVar0.5ems’)::subst)0.5eml1)0.5em0.5em0.5eme0.5em->0.5emelet0.5emfreevarslambda0.5emfreevarsrec0.5em=0.5emfunction0.5em0.5em0.5emVar0.5emv0.5em->0.5em[v]0.5em0.5em0.5emApp0.5em(l1,0.5eml2)0.5em->0.5emfreevarsrec0.5eml10.5em@0.5emfreevarsrec0.5eml20.5em0.5em0.5emAbs0.5em(s,0.5eml1)0.5em->0.5em0.5em0.5em0.5emList.filter0.5em(fun0.5emv0.5em->0.5emv0.5em<>0.5ems)0.5em(freevarsrec0.5eml1)0.5em0.5em->0.5em[]let0.5emrec0.5emeval10.5emsubst0.5eme0.5em=0.5emevallambda0.5emeval10.5emsubst0.5emelet0.5emrec0.5emfreevars10.5eme0.5em=0.5emfreevarslambda0.5emfreevars10.5emelet0.5emtest10.5em=0.5emApp0.5em(Abs0.5em(”x”,0.5emVar0.5em”x”),0.5emVar0.5em”y”)let0.5emetest0.5em=0.5emeval10.5em[]0.5emtest1let0.5emfvtest0.5em=0.5emfreevars10.5emtest1type0.5emexpr0.5em+=0.5emNum0.5emof0.5emint0.5em0.5emAdd0.5emof0.5emexpr0.5em0.5emexpr0.5em0.5emMult0.5emof0.5emexpr0.5em0.5emexprlet0.5emmapexpr0.5emf0.5em=0.5emfunction0.5em0.5em0.5emAdd0.5em(e1,0.5eme2)0.5em->0.5emAdd0.5em(f0.5eme1,0.5emf0.5eme2)0.5em0.5em0.5emMult0.5em(e1,0.5eme2)0.5em->0.5emMult0.5em(f0.5eme1,0.5emf0.5eme2)0.5em0.5em0.5eme0.5em->0.5emelet0.5emevalexpr0.5emevalrec0.5emsubst0.5eme0.5em=0.5em0.5emmatch0.5emmapexpr0.5em(evalrec0.5emsubst)0.5eme0.5emwith0.5em0.5em0.5emAdd0.5em(Num0.5emm,0.5emNum0.5emn)0.5em->0.5emNum0.5em(m0.5em+0.5emn)0.5em0.5em0.5emMult0.5em(Num0.5emm,0.5emNum0.5emn)0.5em->0.5emNum0.5em(m0.5em*0.5emn)0.5em0.5em0.5em(Num0.5em0.5em0.5emAdd0.5em0.5em0.5emMult0.5em)0.5emas0.5eme0.5em->0.5eme0.5em0.5em0.5eme0.5em->0.5emelet0.5emfreevarsexpr0.5emfreevarsrec0.5em=0.5emfunction0.5em0.5em0.5emNum0.5em0.5em->0.5em[]0.5em0.5em0.5emAdd0.5em(e1,0.5eme2)0.5em0.5emMult0.5em(e1,0.5eme2)0.5em->0.5emfreevarsrec0.5eme10.5em@0.5emfreevarsrec0.5eme20.5em0.5em->0.5em[]let0.5emrec0.5emeval20.5emsubst0.5eme0.5em=0.5emevalexpr0.5emeval20.5emsubst0.5emelet0.5emrec0.5emfreevars20.5eme0.5em=0.5emfreevarsexpr0.5emfreevars20.5emelet0.5emtest20.5em=0.5emAdd0.5em(Mult0.5em(Num0.5em3,0.5emVar0.5em”x”),0.5emNum0.5em1)let0.5emetest20.5em=0.5emeval20.5em[]0.5emtest2let0.5emfvtest20.5em=0.5emfreevars20.5emtest2let0.5emevallexpr0.5emevalrec0.5emsubst0.5eme0.5em=0.5em0.5emevalexpr0.5emevalrec0.5emsubst0.5em(evallambda0.5emevalrec0.5emsubst0.5eme)let0.5emfreevarslexpr0.5emfreevarsrec0.5eme0.5em=0.5em0.5emfreevarslambda0.5emfreevarsrec0.5eme0.5em@0.5emfreevarsexpr0.5emfreevarsrec0.5emelet0.5emrec0.5emeval30.5emsubst0.5eme0.5em=0.5emevallexpr0.5emeval30.5emsubst0.5emelet0.5emrec0.5emfreevars30.5eme0.5em=0.5emfreevarslexpr0.5emfreevars30.5emelet0.5emtest30.5em=0.5em0.5emApp0.5em(Abs0.5em(“x”,0.5emAdd0.5em(Mult0.5em(Num0.5em3,0.5emVar0.5em”x”),0.5emNum0.5em1)),0.5em0.5em0.5em0.5em0.5em0.5em0.5emNum0.5em2)let0.5emetest30.5em=0.5emeval30.5em[]0.5emtest3let0.5emfvtest30.5em=0.5emfreevars30.5emtest3Object Oriented Programming: Subtyping
let0.5emf0.5emx0.5em=0.5emx#mMethod invocation:
object#method.val0.5emf0.5em:0.5em<0.5emm0.5em:0.5em’a;0.5em..0.5em>0.5em->0.5em’aType
poymorphic in two ways: 'a
is the method type,.. means that
objects with more methods will be accepted.
Methods are computed when they are invoked, even if they do not take arguments.
We define objects inside object…end (compare: records {…}) using keywords method for methods, val for constant fields and val mutable for mutable fields. Constructor arguments can often be used instead of constant fields:
let0.5emsquare0.5emw0.5em=0.5emobject0.5em0.5emmethod0.5emarea0.5em=0.5emfloatofint0.5em(w0.5em*0.5emw)0.5emmethod0.5emwidth0.5em=0.5emw0.5emend
Subtyping often needs to be explicit: we write (object :> supertype) or in more complex cases (object : type :> supertype).
let0.5ema0.5em=0.5emobject0.5emmethod0.5emm0.5em=0.5em70.5em0.5emmethod0.5emx0.5em=0.5em”a”0.5emendToy example: object typeslet0.5emb0.5em=0.5emobject0.5emmethod0.5emm0.5em=0.5em420.5emmethod0.5emy0.5em=0.5em”b”0.5emendshare some but not all methods.let0.5eml0.5em=0.5em[a;0.5emb]The exact types of the objects do not agree.Error:0.5emThis0.5emexpression0.5emhas0.5emtype0.5em<0.5emm0.5em:0.5emint;0.5emy0.5em:0.5emstring0.5em>0.5em0.5em0.5em0.5em0.5em0.5em0.5embut0.5eman0.5emexpression0.5emwas0.5emexpected0.5emof0.5emtype0.5em<0.5emm0.5em:0.5emint;0.5emx0.5em:0.5emstring0.5em>0.5em0.5em0.5em0.5em0.5em0.5em0.5emThe0.5emsecond0.5emobject0.5emtype0.5emhas0.5emno0.5emmethod0.5emylet0.5eml0.5em=0.5em[(a0.5em:>0.5em<m0.5em:0.5em’a>);0.5em(b0.5em:>0.5em<m0.5em:0.5em’a>)]But the types share a supertype.val0.5eml0.5em:0.5em<0.5emm0.5em:0.5emint0.5em>0.5emlist
Invariant parameters cannot be subtyped:
let0.5emf0.5emx0.5em=0.5em(x0.5em:0.5em<m0.5em:0.5emint;0.5emn0.5em:0.5emfloat>0.5emarray0.5em:>0.5em<m0.5em:0.5emint>0.5emarray)Error:0.5emType0.5em<0.5emm0.5em:0.5emint;0.5emn0.5em:0.5emfloat0.5em>0.5emarray0.5emis0.5emnot0.5ema0.5emsubtype0.5emof0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em<0.5emm0.5em:0.5emint0.5em>0.5emarray0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5emThe0.5emsecond0.5emobject0.5emtype0.5emhas0.5emno0.5emmethod0.5emn
Covariant parameters are subtyped in the same direction as the type:
let0.5emf0.5emx0.5em=0.5em(x0.5em:0.5em<m0.5em:0.5emint;0.5emn0.5em:0.5emfloat>0.5emlist0.5em:>0.5em<m0.5em:0.5emint>0.5emlist)val0.5emf0.5em:0.5em<0.5emm0.5em:0.5emint;0.5emn0.5em:0.5emfloat0.5em>0.5emlist0.5em->0.5em<0.5emm0.5em:0.5emint0.5em>0.5emlist
Contravariant parameters are subtyped in the opposite direction:
let0.5emf0.5emx0.5em=0.5em(x0.5em:0.5em<m0.5em:0.5emint;0.5emn0.5em:0.5emfloat>0.5em->0.5emfloat0.5em:>0.5em<m0.5em:0.5emint>0.5em->0.5emfloat)Error:0.5emType0.5em<0.5emm0.5em:0.5emint;0.5emn0.5em:0.5emfloat0.5em>0.5em->0.5emfloat0.5emis0.5emnot0.5ema0.5emsubtype0.5emof0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em<0.5emm0.5em:0.5emint0.5em>0.5em->0.5emfloat0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5emType0.5em<0.5emm0.5em:0.5emint0.5em>0.5emis0.5emnot0.5ema0.5emsubtype0.5emof0.5em<0.5emm0.5em:0.5emint;0.5emn0.5em:0.5emfloat0.5em>0.5emlet0.5emf0.5emx0.5em=0.5em(x0.5em:0.5em<m0.5em:0.5emint>0.5em->0.5emfloat0.5em:>0.5em<m0.5em:0.5emint;0.5emn0.5em:0.5emfloat>0.5em->0.5emfloat)val0.5emf0.5em:0.5em(<0.5emm0.5em:0.5emint0.5em>0.5em->0.5emfloat)0.5em->0.5em<0.5emm0.5em:0.5emint;0.5emn0.5em:0.5emfloat0.5em>0.5em->0.5emfloatObject Oriented Programming: Inheritance
.mli
files)ocamlc -i Objects.ml
etc. to see
inferred object and class types.OOP Non-solution: direct approach
lexpr_t
on other slides) cannot handle values from a
narrower one (e.g. corresponding to expr_t
).type0.5emvarname0.5em=0.5emstringlet0.5emgensym0.5em=0.5emlet0.5emn0.5em=0.5emref0.5em00.5emin0.5emfun0.5em()0.5em->0.5emincr0.5emn;0.5em”“0.5em0.5emstringofint0.5em!nclass0.5emvirtual0.5em[’lang]0.5emevaluable0.5em=The
abstract class for objects supporting the eval
method.object0.5emFor \lambda-calculus,
we need helper
functions:0.5em0.5emmethod0.5emvirtual0.5emeval0.5em:0.5em(varname0.5em0.5em’lang)0.5emlist0.5em->0.5em’lang0.5em0.5emmethod0.5emvirtual0.5emrename0.5em:0.5emvarname0.5em->0.5emvarname0.5em->0.5em'lang
renaming
of free
variables,0.5em0.5emmethod0.5emapply0.5em(arg0.5em:0.5em’lang)\beta-reduction if possible (fallback
otherwise).0.5em0.5em0.5em0.5em(fallback0.5em:0.5emunit0.5em->0.5em’lang)0.5em(subst0.5em:0.5em(varname0.5em0.5em’lang)0.5emlist)0.5em=0.5em0.5em0.5em0.5emfallback0.5em()endclass0.5em[’lang]0.5emvar0.5em(v0.5em:0.5emvarname)0.5em=object0.5em(self)We
name the current object
self
.0.5em0.5eminherit0.5em[’lang]0.5emevaluable0.5em0.5emval0.5emv0.5em=0.5emv0.5em0.5emmethod0.5emeval0.5emsubst0.5em=0.5em0.5em0.5em0.5emtry0.5emList.assoc0.5emv0.5emsubst0.5emwith0.5emNotfound0.5em->0.5emself0.5em0.5emmethod0.5emrename0.5emv10.5emv20.5em=Renaming
a
variable:0.5em0.5em0.5em0.5emif0.5emv0.5em=0.5emv10.5emthen0.5em{<0.5emv0.5em=0.5emv20.5em>}0.5emelse0.5emselfwe
clone the current object putting the new
name.endclass0.5em[’lang]0.5emabs0.5em(v0.5em:0.5emvarname)0.5em(body0.5em:0.5em’lang)0.5em=object0.5em(self)0.5em0.5eminherit0.5em[’lang]0.5emevaluable0.5em0.5emval0.5emv0.5em=0.5emv0.5em0.5emval0.5embody0.5em=0.5embody0.5em0.5emmethod0.5emeval0.5emsubst0.5em=We
do \alpha-conversion prior to
evaluation.0.5em0.5em0.5em0.5emlet0.5emv’0.5em=0.5emgensym0.5em()0.5eminAlternatively,
we could evaluate
with0.5em0.5em0.5em0.5em{<0.5emv0.5em=0.5emv’;0.5embody0.5em=0.5em(body#rename0.5emv0.5emv’)#eval0.5emsubst0.5em>}substitution
of v
0.5em0.5emmethod0.5emrename0.5emv10.5emv20.5em=by
v_inst v' : 'lang
similar to num_inst
below.0.5em0.5em0.5em0.5emif0.5emv0.5em=0.5emv10.5emthen0.5emself
Renaming
the free variable v1
, so no work if
v=v1
.0.5em0.5em0.5em0.5emelse0.5em{<0.5embody0.5em=0.5embody#rename0.5emv10.5emv20.5em>}0.5em0.5emmethod0.5emapply0.5emargsubst0.5em=0.5em0.5em0.5em0.5embody#eval0.5em((v,0.5emarg)::subst)endclass0.5em[’lang]0.5emapp0.5em(f0.5em:0.5em’lang)0.5em(arg0.5em:0.5em’lang)0.5em=object0.5em(self)0.5em0.5eminherit0.5em[’lang]0.5emevaluable0.5em0.5emval0.5emf0.5em=0.5emf0.5em0.5emval0.5emarg0.5em=0.5emarg0.5em0.5emmethod0.5emeval0.5emsubst0.5em=We
use apply
to differentiate between
f = abs
0.5em0.5em0.5em0.5emlet0.5emarg’0.5em=0.5emarg#eval0.5emsubst0.5emin
(\beta-redexes) and
f ≠ abs
.0.5em0.5em0.5em0.5emf#apply0.5emarg’0.5em(fun0.5em()0.5em->0.5em{<0.5emf0.5em=0.5emf#eval0.5emsubst;0.5emarg0.5em=0.5emarg’0.5em>})0.5emsubst0.5em0.5emmethod0.5emrename0.5emv10.5emv20.5em=Cloning
the object ensures that it will be a subtype of
'lang
0.5em0.5em0.5em0.5em{<0.5emf0.5em=0.5emf#rename0.5emv10.5emv2;0.5emarg0.5em=0.5emarg#rename0.5emv10.5emv20.5em>}rather
than just
'lang app
.endtype0.5emevaluablet0.5em=0.5emevaluablet0.5emevaluableThese
definitions only add nice-looking
types.let0.5emnewvar10.5emv0.5em:0.5emevaluablet0.5em=0.5emnew0.5emvar0.5emvlet0.5emnewabs10.5emv0.5em(body0.5em:0.5emevaluablet)0.5em:0.5emevaluablet0.5em=0.5emnew0.5emabs0.5emv0.5embodyclass0.5emvirtual0.5emcomputemixin0.5em=0.5emobjectFor
evaluating arithmetic expressions we
need0.5em0.5emmethod0.5emcompute0.5em:0.5emint0.5emoption0.5em=0.5emNone0.5em0.5ema
heper method
compute
.endclass0.5em[’lang]0.5emvarc0.5emv0.5em=0.5emobjectTo
use \lambda-expressions together with
arithmetic
expressions0.5em0.5eminherit0.5em[’lang]0.5emvar0.5emv
we
need to upgrade them with the helper
method.0.5em0.5eminherit0.5emcomputemixinendclass0.5em[’lang]0.5emabsc0.5emv0.5embody0.5em=0.5emobject0.5em0.5eminherit0.5em[’lang]0.5emabs0.5emv0.5embody0.5em0.5eminherit0.5emcomputemixinendclass0.5em[’lang]0.5emappc0.5emf0.5emarg0.5em=0.5emobject0.5em0.5eminherit0.5em[’lang]0.5emapp0.5emf0.5emarg0.5em0.5eminherit0.5emcomputemixinendclass0.5em[’lang]0.5emnum0.5em(i0.5em:0.5emint)0.5em=A
numerical
constant.object0.5em(self)0.5em0.5eminherit0.5em[’lang]0.5emevaluable0.5em0.5emval0.5emi0.5em=0.5emi0.5em0.5emmethod0.5emeval0.5emsubst0.5em=0.5emself0.5em0.5emmethod0.5emrename0.5em=0.5emself0.5em0.5emmethod0.5emcompute0.5em=0.5emSome0.5emiendclass0.5emvirtual0.5em[’lang]0.5emoperation
Abstract
class for evaluating arithmetic
operations.0.5em0.5em0.5em0.5em(numinst0.5em:0.5emint0.5em->0.5em’lang)0.5em(n10.5em:0.5em’lang)0.5em(n20.5em:0.5em’lang)0.5em=object0.5em(self)0.5em0.5eminherit0.5em[’lang]0.5emevaluable0.5em0.5emval0.5emn10.5em=0.5emn10.5em0.5emval0.5emn20.5em=0.5emn20.5em0.5emmethod0.5emeval0.5emsubst0.5em=0.5em0.5em0.5em0.5emlet0.5emself’0.5em=0.5em{<0.5emn10.5em=0.5emn1#eval0.5emsubst;0.5emn20.5em=0.5emn2#eval0.5emsubst0.5em>}0.5emin0.5em0.5em0.5em0.5emmatch0.5emself’#compute0.5emwith0.5em0.5em0.5em0.5em0.5emSome0.5emi0.5em->0.5emnuminst0.5emi
We
need to inject the integer as a constant that
is0.5em0.5em0.5em0.5em->0.5emself'
a subtype of
'lang
.0.5em0.5emmethod0.5emrename0.5emv10.5emv20.5em=0.5em{<0.5emn10.5em=0.5emn1#rename0.5emv10.5emv2;0.5emn20.5em=0.5emn2#rename0.5emv10.5emv20.5em>}endclass0.5em[’lang]0.5emadd0.5emnuminst0.5emn10.5emn20.5em=object0.5em(self)0.5em0.5eminherit0.5em[’lang]0.5emoperation0.5emnuminst0.5emn10.5emn20.5em0.5emmethod0.5emcompute0.5em=If
compute
is called by eval
, as
intended,0.5em0.5em0.5em0.5emmatch0.5emn1#compute,0.5emn2#compute0.5emwiththen
n1
and n2
are already
computed.0.5em0.5em0.5em0.5em0.5emSome0.5emi1,0.5emSome0.5emi20.5em->0.5emSome0.5em(i10.5em+0.5emi2)0.5em0.5em0.5em0.5em->0.5emNoneendclass0.5em[’lang]0.5emmult0.5emnuminst0.5emn10.5emn20.5em=object0.5em(self)0.5em0.5eminherit0.5em[’lang]0.5emoperation0.5emnuminst0.5emn10.5emn20.5em0.5emmethod0.5emcompute0.5em=0.5em0.5em0.5em0.5emmatch0.5emn1#compute,0.5emn2#compute0.5emwith0.5em0.5em0.5em0.5em0.5emSome0.5emi1,0.5emSome0.5emi20.5em->0.5emSome0.5em(i10.5em*0.5emi2)0.5em0.5em0.5em0.5em->0.5emNoneendclass0.5emvirtual0.5em[’lang]0.5emcomputable0.5em=This
class is defined merely to provide an object type,objectwe could also
define this object type ‘‘by
hand’’.0.5em0.5eminherit0.5em[’lang]0.5emevaluable0.5em0.5eminherit0.5emcomputemixinendtype0.5emcomputablet0.5em=0.5emcomputablet0.5emcomputableNice
types for all the
constructors.let0.5emnewvar20.5emv0.5em:0.5emcomputablet0.5em=0.5emnew0.5emvarc0.5emvlet0.5emnewabs20.5emv0.5em(body0.5em:0.5emcomputablet)0.5em:0.5emcomputablet0.5em=0.5emnew0.5emabsc0.5emv0.5embodylet0.5emnewapp20.5emv0.5em(body0.5em:0.5emcomputablet)0.5em:0.5emcomputablet0.5em=0.5emnew0.5emappc0.5emv0.5embodylet0.5emnewnum20.5emi0.5em:0.5emcomputablet0.5em=0.5emnew0.5emnum0.5emilet0.5emnewadd20.5em(n10.5em:0.5emcomputablet)0.5em(n20.5em:0.5emcomputablet)0.5em:0.5emcomputablet0.5em=0.5em0.5emnew0.5emadd0.5emnewnum20.5emn10.5emn2let0.5emnewmult20.5em(n10.5em:0.5emcomputablet)0.5em(n20.5em:0.5emcomputablet)0.5em:0.5emcomputablet0.5em=0.5em0.5emnew0.5emmult0.5emnewnum20.5emn10.5emn2OOP:
The Visitor Pattern
type0.5em’visitor0.5emvisitable0.5em=0.5em<0.5emaccept0.5em:0.5em’visitor0.5em->0.5emunit0.5em>The
variants need be visitable.We store the computation as side effect
because of the difficultytype0.5emvarname0.5em=0.5emstringto keep the
visitor polymorphic but have the result typedepend on the
visitor.class0.5em[’visitor]0.5emvar0.5em(v0.5em:0.5emvarname)0.5em=The
'visitor
will determine the
(sub)languageobject0.5em(self)to which a given var
variant
belongs.0.5em0.5emmethod0.5emv0.5em=0.5emv0.5em0.5emmethod0.5emaccept0.5em:0.5em’visitor0.5em->0.5emunit0.5em=The
visitor pattern inverts the
way0.5em0.5em0.5em0.5emfun0.5emvisitor0.5em->0.5emvisitor#visitVar0.5emselfpattern
matching proceeds: the variantendselects the pattern matching
branch.let0.5emnewvar0.5emv0.5em=0.5em(new0.5emvar0.5emv0.5em:>0.5em’a0.5emvisitable)Visitors
need to see the stored data,but distinct constructors need to belong to
the same
type.class0.5em[’visitor]0.5emabs0.5em(v0.5em:0.5emvarname)0.5em(body0.5em:0.5em’visitor0.5emvisitable)0.5em=object0.5em(self)0.5em0.5emmethod0.5emv0.5em=0.5emv0.5em0.5emmethod0.5embody0.5em=0.5embody0.5em0.5emmethod0.5emaccept0.5em:0.5em’visitor0.5em->0.5emunit0.5em=0.5em0.5em0.5em0.5emfun0.5emvisitor0.5em->0.5emvisitor#visitAbs0.5emselfendlet0.5emnewabs0.5emv0.5embody0.5em=0.5em(new0.5emabs0.5emv0.5embody0.5em:>0.5em’a0.5emvisitable)class0.5em[’visitor]0.5emapp0.5em(f0.5em:0.5em’visitor0.5emvisitable)0.5em(arg0.5em:0.5em’visitor0.5emvisitable)0.5em=object0.5em(self)0.5em0.5emmethod0.5emf0.5em=0.5emf0.5em0.5emmethod0.5emarg0.5em=0.5emarg0.5em0.5emmethod0.5emaccept0.5em:0.5em’visitor0.5em->0.5emunit0.5em=0.5em0.5em0.5em0.5emfun0.5emvisitor0.5em->0.5emvisitor#visitApp0.5emselfendlet0.5emnewapp0.5emf0.5emarg0.5em=0.5em(new0.5emapp0.5emf0.5emarg0.5em:>0.5em’a0.5emvisitable)class0.5emvirtual0.5em[’visitor]0.5emlambdavisit0.5em=This
abstract class has two uses:objectit defines the visitors for the
sub-langauge of \lambda-expressions,0.5em0.5emmethod0.5emvirtual0.5emvisitVar0.5em:0.5em’visitor0.5emvar0.5em->0.5emunitand
it will provide an early
check0.5em0.5emmethod0.5emvirtual0.5emvisitAbs0.5em:0.5em’visitor0.5emabs0.5em->0.5emunitthat
the visitor
classes0.5em0.5emmethod0.5emvirtual0.5emvisitApp0.5em:0.5em’visitor0.5emapp0.5em->0.5emunitimplement
all the
methods.endlet0.5emgensym0.5em=0.5emlet0.5emn0.5em=0.5emref0.5em00.5emin0.5emfun0.5em()0.5em->0.5emincr0.5emn;0.5em”“0.5em0.5emstringofint0.5em!nclass0.5em[’visitor]0.5emevallambda
0.5em0.5em(subst0.5em:0.5em(varname0.5em0.5em’visitor0.5emvisitable)0.5emlist)0.5em0.5em(result0.5em:0.5em’visitor0.5emvisitable0.5emref)0.5em=An
output argument, but also used internallyobject0.5em(self)to store
intermediate
results.0.5em0.5eminherit0.5em[’visitor]0.5emlambdavisit0.5em0.5emval0.5emmutable0.5emsubst0.5em=0.5emsubst
We
avoid threading the argument through the visit
methods.0.5em0.5emval0.5emmutable0.5embetaredex0.5em:0.5em(varname0.5em0.5em’visitor0.5emvisitable)0.5emoption0.5em=0.5emNoneWe
work around0.5em0.5emmethod0.5emvisitVar0.5emvar0.5em=the need to
differentiate between abs
and non-abs
values0.5em0.5em0.5em0.5embetaredex0.5em<-0.5emNone;of app#f inside
visitApp
.0.5em0.5em0.5em0.5emtry0.5emresult0.5em:=0.5emList.assoc0.5emvar#v0.5emsubst0.5em0.5em0.5em0.5emwith0.5emNotfound0.5em->0.5emresult0.5em:=0.5em(var0.5em:>0.5em’visitor0.5emvisitable)0.5em0.5emmethod0.5emvisitAbs0.5emabs0.5em=0.5em0.5em0.5em0.5emlet0.5emv’0.5em=0.5emgensym0.5em()0.5emin0.5em0.5em0.5em0.5emlet0.5emorigsubst0.5em=0.5emsubst0.5emin0.5em0.5em0.5em0.5emsubst0.5em<-0.5em(abs#v,0.5emnew_var0.5emv’)::subst;‘‘Pass’’
the updated
substitution0.5em0.5em0.5em0.5em(abs#body)#accept0.5emself;to the
recursive
call0.5em0.5em0.5em0.5emlet0.5embody’0.5em=0.5em!result0.5eminand
collect the result of the recursive
call.0.5em0.5em0.5em0.5emsubst0.5em<-0.5emorigsubst;0.5em0.5em0.5em0.5embetaredex0.5em<-0.5emSome0.5em(v’,0.5embody’);Indicate
that an abs
has just been
visited.0.5em0.5em0.5em0.5emresult0.5em:=0.5emnewabs0.5emv’0.5embody’0.5em0.5emmethod0.5emvisitApp0.5emapp0.5em=0.5em0.5em0.5em0.5emapp#arg#accept0.5emself;0.5em0.5em0.5em0.5emlet0.5emarg’0.5em=0.5em!result0.5emin0.5em0.5em0.5em0.5emapp#f#accept0.5emself;0.5em0.5em0.5em0.5emlet0.5emf’0.5em=0.5em!result0.5emin0.5em0.5em0.5em0.5emmatch0.5embetaredex0.5emwithPattern-match
on
app#f.0.5em0.5em0.5em0.5em0.5emSome0.5em(v’,0.5embody’)0.5em->0.5em0.5em0.5em0.5em0.5em0.5embetaredex0.5em<-0.5emNone;0.5em0.5em0.5em0.5em0.5em0.5emlet0.5emorigsubst0.5em=0.5emsubst0.5emin0.5em0.5em0.5em0.5em0.5em0.5emsubst0.5em<-0.5em(v’,0.5emarg’)::subst;0.5em0.5em0.5em0.5em0.5em0.5embody’#accept0.5emself;0.5em0.5em0.5em0.5em0.5em0.5emsubst0.5em<-0.5emorigsubst0.5em0.5em0.5em0.5em0.5emNone0.5em->0.5emresult0.5em:=0.5emnewapp0.5emf’0.5emarg’endclass0.5em[’visitor]0.5emfreevarslambda0.5em(result0.5em:0.5emvarname0.5emlist0.5emref)0.5em=object0.5em(self)We
use result
as an
accumulator.0.5em0.5eminherit0.5em[’visitor]0.5emlambdavisit0.5em0.5emmethod0.5emvisitVar0.5emvar0.5em=0.5em0.5em0.5em0.5emresult0.5em:=0.5emvar#v0.5em::0.5em!result0.5em0.5emmethod0.5emvisitAbs0.5emabs0.5em=0.5em0.5em0.5em0.5em(abs#body)#accept0.5emself;0.5em0.5em0.5em0.5emresult0.5em:=0.5emList.filter0.5em(fun0.5emv’0.5em->0.5emv’0.5em<>0.5emabs#v)0.5em!result0.5em0.5emmethod0.5emvisitApp0.5emapp0.5em=0.5em0.5em0.5em0.5emapp#arg#accept0.5emself;0.5emapp#f#accept0.5emselfendtype0.5emlambdavisitt0.5em=0.5emlambdavisitt0.5emlambdavisitVisitor
for the language of \lambda-expressions.type0.5emlambdat0.5em=0.5emlambdavisitt0.5emvisitablelet0.5emeval10.5em(e0.5em:0.5emlambdat)0.5emsubst0.5em:0.5emlambdat0.5em=0.5em0.5emlet0.5emresult0.5em=0.5emref0.5em(newvar0.5em”“)0.5eminThis
initial value will be
ignored.0.5em0.5eme#accept0.5em(new0.5emevallambda0.5emsubst0.5emresult0.5em:>0.5emlambdavisitt);0.5em0.5em!resultlet0.5emfreevars10.5em(e0.5em:0.5emlambdat)0.5em=0.5em0.5emlet0.5emresult0.5em=0.5emref0.5em[]0.5eminInitial
value of the
accumulator.0.5em0.5eme#accept0.5em(new0.5emfreevarslambda0.5emresult);0.5em0.5em!resultlet0.5emtest10.5em=0.5em0.5em(newapp0.5em(newabs0.5em”x”0.5em(newvar0.5em”x”))0.5em(newvar0.5em”y”)0.5em:>0.5emlambdat)let0.5emetest0.5em=0.5emeval10.5emtest10.5em[]let0.5emfvtest0.5em=0.5emfreevars10.5emtest1class0.5em[’visitor]0.5emnum0.5em(i0.5em:0.5emint)0.5em=object0.5em(self)0.5em0.5emmethod0.5emi0.5em=0.5emi0.5em0.5emmethod0.5emaccept0.5em:0.5em’visitor0.5em->0.5emunit0.5em=0.5em0.5em0.5em0.5emfun0.5emvisitor0.5em->0.5emvisitor#visitNum0.5emselfendlet0.5emnewnum0.5emi0.5em=0.5em(new0.5emnum0.5emi0.5em:>0.5em’a0.5emvisitable)class0.5emvirtual0.5em[’visitor]0.5emoperation0.5em0.5em(arg10.5em:0.5em’visitor0.5emvisitable)0.5em(arg20.5em:0.5em’visitor0.5emvisitable)0.5em=object0.5em(self)Shared
accessor
methods.0.5em0.5emmethod0.5emarg10.5em=0.5emarg10.5em0.5emmethod0.5emarg20.5em=0.5emarg2endclass0.5em[’visitor]0.5emadd0.5emarg10.5emarg20.5em=object0.5em(self)0.5em0.5eminherit0.5em[’visitor]0.5emoperation0.5emarg10.5emarg20.5em0.5emmethod0.5emaccept0.5em:0.5em’visitor0.5em->0.5emunit0.5em=0.5em0.5em0.5em0.5emfun0.5emvisitor0.5em->0.5emvisitor#visitAdd0.5emselfendlet0.5emnewadd0.5emarg10.5emarg20.5em=0.5em(new0.5emadd0.5emarg10.5emarg20.5em:>0.5em’a0.5emvisitable)class0.5em[’visitor]0.5emmult0.5emarg10.5emarg20.5em=object0.5em(self)0.5em0.5eminherit0.5em[’visitor]0.5emoperation0.5emarg10.5emarg20.5em0.5emmethod0.5emaccept0.5em:0.5em’visitor0.5em->0.5emunit0.5em=0.5em0.5em0.5em0.5emfun0.5emvisitor0.5em->0.5emvisitor#visitMult0.5emselfendlet0.5emnewmult0.5emarg10.5emarg20.5em=0.5em(new0.5emmult0.5emarg10.5emarg20.5em:>0.5em’a0.5emvisitable)class0.5emvirtual0.5em[’visitor]0.5emexprvisit0.5em=The
sub-language of arithmetic
expressions.object0.5em0.5emmethod0.5emvirtual0.5emvisitNum0.5em:0.5em’visitor0.5emnum0.5em->0.5emunit0.5em0.5emmethod0.5emvirtual0.5emvisitAdd0.5em:0.5em’visitor0.5emadd0.5em->0.5emunit0.5em0.5emmethod0.5emvirtual0.5emvisitMult0.5em:0.5em’visitor0.5emmult0.5em->0.5emunitendclass0.5em[’visitor]0.5emevalexpr0.5em0.5em(result0.5em:0.5em’visitor0.5emvisitable0.5emref)0.5em=object0.5em(self)0.5em0.5eminherit0.5em[’visitor]0.5emexprvisit0.5em0.5emval0.5emmutable0.5emnumredex0.5em:0.5emint0.5emoption0.5em=0.5emNoneThe
numeric result, if
any.0.5em0.5emmethod0.5emvisitNum0.5emnum0.5em=0.5em0.5em0.5em0.5emnumredex0.5em<-0.5emSome0.5emnum#i;0.5em0.5em0.5em0.5emresult0.5em:=0.5em(num0.5em:>0.5em’visitor0.5emvisitable)0.5em0.5emmethod0.5emprivate0.5emvisitOperation0.5emnewe0.5emop0.5eme0.5em=0.5em0.5em0.5em0.5em(e#arg1)#accept0.5emself;0.5em0.5em0.5em0.5emlet0.5emarg1’0.5em=0.5em!result0.5emand0.5emi10.5em=0.5emnumredex0.5emin0.5em0.5em0.5em0.5em(e#arg2)#accept0.5emself;0.5em0.5em0.5em0.5emlet0.5emarg2’0.5em=0.5em!result0.5emand0.5emi20.5em=0.5emnumredex0.5emin0.5em0.5em0.5em0.5emmatch0.5emi1,0.5emi20.5emwith0.5em0.5em0.5em0.5em0.5emSome0.5emi1,0.5emSome0.5emi20.5em->0.5em0.5em0.5em0.5em0.5em0.5emlet0.5emres0.5em=0.5emop0.5emi10.5emi20.5emin0.5em0.5em0.5em0.5em0.5em0.5emnumredex0.5em<-0.5emSome0.5emres;0.5emresult0.5em:=0.5emnewnum0.5emres0.5em0.5em0.5em0.5em->0.5em0.5em0.5em0.5em0.5em0.5emnumredex0.5em<-0.5emNone;0.5em0.5em0.5em0.5em0.5em0.5emresult0.5em:=0.5emnewe0.5emarg1’0.5emarg2’0.5em0.5emmethod0.5emvisitAdd0.5emadd0.5em=0.5emself#visitOperation0.5emnewadd0.5em(0.5em+0.5em)0.5emadd0.5em0.5emmethod0.5emvisitMult0.5emmult0.5em=0.5emself#visitOperation0.5emnewmult0.5em(0.5em*0.5em)0.5emmultendclass0.5em[’visitor]0.5emfreevarsexpr0.5em(result0.5em:0.5emvarname0.5emlist0.5emref)0.5em=Flow-through
classobject0.5em(self)for computing free
variables.0.5em0.5eminherit0.5em[’visitor]0.5emexprvisit0.5em0.5emmethod0.5emvisitNum=0.5em()0.5em0.5emmethod0.5emvisitAdd0.5emadd0.5em=0.5em0.5em0.5em0.5emadd#arg1#accept0.5emself;0.5emadd#arg2#accept0.5emself0.5em0.5emmethod0.5emvisitMult0.5emmult0.5em=0.5em0.5em0.5em0.5emmult#arg1#accept0.5emself;0.5emmult#arg2#accept0.5emselfendtype0.5emexprvisitt0.5em=0.5emexprvisitt0.5emexprvisitThe
language of arithmetic
expressionstype0.5emexprt0.5em=0.5emexprvisitt0.5emvisitable– in this
example without
variables.let0.5emeval20.5em(e0.5em:0.5emexprt)0.5em:0.5emexprt0.5em=0.5em0.5emlet0.5emresult0.5em=0.5emref0.5em(newnum0.5em0)0.5eminThis
initial value will be
ignored.0.5em0.5eme#accept0.5em(new0.5emevalexpr0.5emresult);0.5em0.5em!resultlet0.5emtest20.5em=0.5em0.5em(newadd0.5em(newmult0.5em(newnum0.5em3)0.5em(newnum0.5em3))0.5em(newnum0.5em1)0.5em:>0.5emexprt)let0.5emetest0.5em=0.5emeval20.5emtest2class0.5emvirtual0.5em[’visitor]0.5emlexprvisit0.5em=Combining
the variants /
constructors.object0.5em0.5eminherit0.5em[’visitor]0.5emlambdavisit0.5em0.5eminherit0.5em[’visitor]0.5emexprvisitendclass0.5em[’visitor]0.5emevallexpr0.5emsubst0.5emresult0.5em=Combining
the ‘‘pattern-matching
branches’’.object0.5em0.5eminherit0.5em[’visitor]0.5emevalexpr0.5emresult0.5em0.5eminherit0.5em[’visitor]0.5emevallambda0.5emsubst0.5emresultendclass0.5em[’visitor]0.5emfreevarslexpr0.5emresult0.5em=object0.5em0.5eminherit0.5em[’visitor]0.5emfreevarsexpr0.5emresult0.5em0.5eminherit0.5em[’visitor]0.5emfreevarslambda0.5emresultendtype0.5emlexprvisitt0.5em=0.5emlexprvisitt0.5emlexprvisitThe
language
combiningtype0.5emlexprt0.5em=0.5emlexprvisitt0.5emvisitable\lambda-expressions and arithmetic
expressions.let0.5emeval30.5em(e0.5em:0.5emlexprt)0.5emsubst0.5em:0.5emlexprt0.5em=0.5em0.5emlet0.5emresult0.5em=0.5emref0.5em(newnum0.5em0)0.5emin0.5em0.5eme#accept0.5em(new0.5emevallexpr0.5emsubst0.5emresult);0.5em0.5em!resultlet0.5emfreevars30.5em(e0.5em:0.5emlexprt)0.5em=0.5em0.5emlet0.5emresult0.5em=0.5emref0.5em[]0.5emin0.5em0.5eme#accept0.5em(new0.5emfreevarslexpr0.5emresult);0.5em0.5em!resultlet0.5emtest30.5em=0.5em0.5em(newadd0.5em(newmult0.5em(newnum0.5em3)0.5em(newvar0.5em”x”))0.5em(newnum0.5em1)0.5em:>0.5emlexprt)let0.5emetest0.5em=0.5emeval30.5emtest30.5em[]let0.5emfvtest0.5em=0.5emfreevars30.5emtest3let0.5emoldetest0.5em=0.5emeval30.5em(test20.5em:>0.5emlexprt)0.5em[]let0.5emoldfvtest0.5em=0.5emeval30.5em(test20.5em:>0.5emlexprt)0.5em[]Polymorphic
Variant Types: Subtyping
Polymorphic variants are to ordinary variants as objects are to records: both enable open types and subtyping, both allow different types to share the same components.
While object subtypes have more methods, polymorphic variant subtypes have less tags.
The > sign means “these tags or more”:
let0.5eml0.5em=0.5em[’Int0.5em3;0.5em‘Float0.5em4.];;val0.5eml0.5em:0.5em[>0.5em‘Float0.5emof0.5emfloat0.5em0.5em‘Int0.5emof0.5emint0.5em]0.5emlist0.5em=0.5em[’Int0.5em3;0.5em‘Float0.5em4.]
The < sign means “these tags or less”:
let0.5emispositive0.5em=0.5emfunction0.5em0.5em0.5em0.5em0.5em0.5em‘Int0.5em0.5em0.5emx0.5em->0.5emSome0.5em(x0.5em>0.5em0)0.5em0.5em0.5em0.5em0.5em0.5em‘Float0.5emx0.5em->0.5emSome0.5em(x0.5em>0.5em0.)0.5em0.5em0.5em0.5em0.5em0.5em‘Notanumber0.5em->0.5emNone;;val0.5emispositive0.5em:0.5em0.5em[<0.5em‘Float0.5emof0.5emfloat0.5em0.5em‘Int0.5emof0.5emint0.5em0.5em‘Notanumber0.5em]0.5em->0.5em
bool0.5emoption0.5em=0.5em
No sign means a closed type (similar to an object type without the ..)
Both an upper and a lower bound are sometimes inferred,see https://realworldocaml.org/v1/en/html/variants.html
List.filter0.5em0.5em(fun0.5emx0.5em->0.5emmatch0.5emispositive0.5emx0.5emwith0.5emNone0.5em->0.5emfalse0.5em0.5emSome0.5emb0.5em->0.5emb)0.5eml;;-0.5em:0.5em[<0.5em‘Float0.5emof0.5emfloat0.5em0.5em‘Int0.5emof0.5emint0.5em0.5em‘Notanumber0.5em>0.5em‘Float0.5em‘Int0.5em]0.5em list0.5em=[’Int0.5em3;0.5em‘Float0.5em4.]Polymorphic Variant Types: The Expression Problem
Because distinct polymorphic variant types can share the same tags, the solution to the Expression Problem is straightforward.
Penalty points:
Verdict: a flexible and concise solution, second-best place.
type0.5emvar0.5em=0.5em[’Var0.5emof0.5emstring]let0.5emevalvar0.5emsub0.5em(’Var0.5ems0.5emas0.5emv0.5em:0.5emvar)0.5em=0.5em0.5emtry0.5emList.assoc0.5ems0.5emsub0.5emwith0.5emNotfound0.5em->0.5emvtype0.5em’a0.5emlambda0.5em=0.5em0.5em[’Var0.5emof0.5emstring0.5em0.5em‘Abs0.5emof0.5emstring0.5em0.5em’a0.5em0.5em‘App0.5emof0.5em’a0.5em0.5em’a]let0.5emgensym0.5em=0.5emlet0.5emn0.5em=0.5emref0.5em00.5emin0.5emfun0.5em()0.5em->0.5emincr0.5emn;0.5em”“0.5em0.5emstringofint0.5em!nlet0.5emevallambda0.5emevalrec0.5emsubst0.5em:0.5em’a0.5emlambda0.5em->0.5em’a0.5em=0.5emfunction0.5em0.5em0.5em#var0.5emas0.5emv0.5em->0.5emevalvar0.5emsubst0.5emv
We
could also leave the type
open0.5em0.5em0.5em‘App0.5em(l1,0.5eml2)0.5em->rather than closing it
to
lambda
.0.5em0.5em0.5em0.5emlet0.5eml2’0.5em=0.5emevalrec0.5emsubst0.5eml20.5emin0.5em0.5em0.5em0.5em(match0.5emevalrec0.5emsubst0.5eml10.5emwith0.5em0.5em0.5em0.5em0.5em‘Abs0.5em(s,0.5embody)0.5em->0.5em0.5em0.5em0.5em0.5em0.5emevalrec0.5em[s,0.5eml2’]0.5embody0.5em0.5em0.5em0.5em0.5eml1’0.5em->0.5em‘App0.5em(l1’,0.5eml2’))0.5em0.5em0.5em‘Abs0.5em(s,0.5eml1)0.5em->0.5em0.5em0.5em0.5emlet0.5ems’0.5em=0.5emgensym0.5em()0.5emin0.5em0.5em0.5em0.5em‘Abs0.5em(s’,0.5emevalrec0.5em((s,0.5em‘Var0.5ems’)::subst)0.5eml1)let0.5emfreevarslambda0.5emfreevarsrec0.5em:0.5em’a0.5emlambda0.5em->0.5em’b0.5em=0.5emfunction0.5em0.5em0.5em‘Var0.5emv0.5em->0.5em[v]0.5em0.5em0.5em‘App0.5em(l1,0.5eml2)0.5em->0.5emfreevarsrec0.5eml10.5em@0.5emfreevarsrec0.5eml20.5em0.5em0.5em‘Abs0.5em(s,0.5eml1)0.5em->0.5em0.5em0.5em0.5emList.filter0.5em(fun0.5emv0.5em->0.5emv0.5em<>0.5ems)0.5em(freevarsrec0.5eml1)type0.5emlambdat0.5em=0.5emlambdat0.5emlambdalet0.5emrec0.5emeval10.5emsubst0.5eme0.5em:0.5emlambdat0.5em=0.5emevallambda0.5emeval10.5emsubst0.5emelet0.5emrec0.5emfreevars10.5em(e0.5em:0.5emlambdat)0.5em=0.5emfreevarslambda0.5emfreevars10.5emelet0.5emtest10.5em=0.5em(’App0.5em(’Abs0.5em(”x”,0.5em‘Var0.5em”x”),0.5em‘Var0.5em”y”)0.5em:>0.5emlambdat)let0.5emetest0.5em=0.5emeval10.5em[]0.5emtest1let0.5emfvtest0.5em=0.5emfreevars10.5emtest1type0.5em’a0.5emexpr0.5em=0.5em0.5em[’Var0.5emof0.5emstring0.5em0.5em‘Num0.5emof0.5emint0.5em0.5em‘Add0.5emof0.5em’a0.5em0.5em’a0.5em0.5em‘Mult0.5emof0.5em’a0.5em0.5em’a]let0.5emmapexpr0.5em(f0.5em:0.5em0.5em->0.5em’a)0.5em:0.5em’a0.5emexpr0.5em->0.5em’a0.5em=0.5emfunction0.5em0.5em0.5em#var0.5emas0.5emv0.5em->0.5emv0.5em0.5em0.5em‘Num0.5em0.5emas0.5emn0.5em->0.5emn0.5em0.5em0.5em‘Add0.5em(e1,0.5eme2)0.5em->0.5em‘Add0.5em(f0.5eme1,0.5emf0.5eme2)0.5em0.5em0.5em‘Mult0.5em(e1,0.5eme2)0.5em->0.5em‘Mult0.5em(f0.5eme1,0.5emf0.5eme2)let0.5emevalexpr0.5emevalrec0.5emsubst0.5em(e0.5em:0.5em’a0.5emexpr)0.5em:0.5em’a0.5em=0.5em0.5emmatch0.5emmapexpr0.5em(evalrec0.5emsubst)0.5eme0.5emwith0.5em0.5em0.5em#var0.5emas0.5emv0.5em->0.5emevalvar0.5emsubst0.5emv
Here
and elsewhere, we could also
factor-out0.5em0.5em0.5em‘
Add0.5em(’Num0.5emm,0.5em‘Num0.5emn)0.5em->0.5em‘Num0.5em(m0.5em+0.5emn)the
sub-language of
variables.0.5em0.5em0.5em‘Mult0.5em(’Num0.5emm,0.5em‘Num0.5emn)0.5em->0.5em‘Num0.5em(m0.5em*0.5emn)0.5em0.5em0.5eme0.5em->0.5emelet0.5emfreevarsexpr0.5emfreevarsrec0.5em:0.5em’a0.5emexpr0.5em->0.5em’b0.5em=0.5emfunction0.5em0.5em0.5em‘Var0.5emv0.5em->0.5em[v]0.5em0.5em0.5em‘Num0.5em0.5em->0.5em[]0.5em0.5em0.5em‘Add0.5em(e1,0.5eme2)0.5em0.5em‘Mult0.5em(e1,0.5eme2)0.5em->0.5emfreevarsrec0.5eme10.5em@0.5emfreevarsrec0.5eme2type0.5emexprt0.5em=0.5emexprt0.5emexprlet0.5emrec0.5emeval20.5emsubst0.5eme0.5em:0.5emexprt0.5em=0.5emevalexpr0.5emeval20.5emsubst0.5emelet0.5emrec0.5emfreevars20.5em(e0.5em:0.5emexprt)0.5em=0.5emfreevarsexpr0.5emfreevars20.5emelet0.5emtest20.5em=0.5em(’Add0.5em(’Mult0.5em(’Num0.5em3,0.5em‘Var0.5em”x”),0.5em‘Num0.5em1)0.5em:0.5emexprt)let0.5emetest20.5em=0.5emeval20.5em[“x”,0.5em‘Num0.5em2]0.5emtest2let0.5emfvtest20.5em=0.5emfreevars20.5emtest2type0.5em’a0.5emlexpr0.5em=0.5em[’a0.5emlambda0.5em0.5em’a0.5emexpr]let0.5emevallexpr0.5emevalrec0.5emsubst0.5em:0.5em’a0.5emlexpr0.5em->0.5em’a0.5em=0.5emfunction0.5em0.5em0.5em#lambda0.5emas0.5emx0.5em->0.5emevallambda0.5emevalrec0.5emsubst0.5emx0.5em0.5em0.5em#expr0.5emas0.5emx0.5em->0.5emevalexpr0.5emevalrec0.5emsubst0.5emxlet0.5emfreevarslexpr0.5emfreevarsrec0.5em:0.5em’a0.5emlexpr0.5em->0.5em’b0.5em=0.5emfunction0.5em0.5em0.5em#lambda0.5emas0.5emx0.5em->0.5emfreevarslambda0.5emfreevarsrec0.5emx0.5em0.5em0.5em#expr0.5emas0.5emx0.5em->0.5emfreevarsexpr0.5emfreevarsrec0.5emxtype0.5emlexprt0.5em=0.5emlexprt0.5emlexprlet0.5emrec0.5emeval30.5emsubst0.5eme0.5em:0.5emlexprt0.5em=0.5emevallexpr0.5emeval30.5emsubst0.5emelet0.5emrec0.5emfreevars30.5em(e0.5em:0.5emlexprt)0.5em=0.5emfreevarslexpr0.5emfreevars30.5emelet0.5emtest30.5em=0.5em0.5em(’App0.5em(’Abs0.5em(“x”,0.5em‘Add0.5em(’Mult0.5em(’Num0.5em3,0.5em‘Var0.5em”x”),0.5em‘Num0.5em1)),0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em‘Num0.5em2)0.5em:0.5emlexprt)let0.5emetest30.5em=0.5emeval30.5em[]0.5emtest3let0.5emfvtest30.5em=0.5emfreevars30.5emtest3let0.5emeoldtest0.5em=0.5emeval30.5em[]0.5em(test20.5em:>0.5emlexprt)let0.5emfvoldtest0.5em=0.5emfreevars30.5em(test20.5em:>0.5emlexprt)Polymorphic
Variants and Recursive Modules
We can conceive of open row types, e.g. [>
’Int0.5emof0.5emint0.5em0.5em‘String0.5emof0.5emstring] as using a
row variable, e.g. 'a
:
[’Int0.5emof0.5emint0.5em0.5em‘String0.5emof0.5emstring0.5em0.5em’a]
and then of private row types as abstracting the row variable:
type0.5emtrowtype0.5emt0.5em=0.5em[’Int0.5emof0.5emint0.5em0.5em‘String0.5emof0.5emstring0.5em0.5emtrow]
But the actual formalization of private row types is more complex.
type0.5emvar0.5em=0.5em[’Var0.5emof0.5emstring]let0.5emevalvar0.5emsubst0.5em(’Var0.5ems0.5emas0.5emv0.5em:0.5emvar)0.5em=0.5em0.5emtry0.5emList.assoc0.5ems0.5emsubst0.5emwith0.5emNotfound0.5em->0.5emvtype0.5em’a0.5emlambda0.5em=0.5em0.5em[’Var0.5emof0.5emstring0.5em0.5em‘Abs0.5emof0.5emstring0.5em0.5em’a0.5em0.5em‘App0.5emof0.5em’a0.5em0.5em’a]module0.5emtype0.5emEval0.5em=sig0.5emtype0.5emexp0.5emval0.5emeval0.5em:0.5em(string0.5em*0.5emexp)0.5emlist0.5em->0.5emexp0.5em->0.5emexp0.5emendmodule0.5emLF(X0.5em:0.5emEval0.5emwith0.5emtype0.5emexp0.5em=0.5emprivate0.5em[>0.5em’a0.5emlambda]0.5emas0.5em’a)0.5em=struct0.5em0.5emtype0.5emexp0.5em=0.5emX.exp0.5emlambda0.5em0.5emlet0.5emgensym0.5em=0.5em
let0.5emn0.5em=0.5emref0.5em00.5emin0.5emfun0.5em()0.5em->0.5emincr0.5emn;0.5em”“0.5em0.5emstringofint0.5em!n0.5em0.5emlet0.5emeval0.5emsubst0.5em:0.5emexp0.5em->0.5emX.exp0.5em=0.5emfunction0.5em0.5em0.5em0.5em0.5em#var0.5emas0.5emv0.5em->0.5emevalvar0.5emsubst0.5emv0.5em0.5em0.5em0.5em0.5em‘App0.5em(l1,0.5eml2)0.5em->0.5em0.5em0.5em0.5em0.5em0.5emlet0.5eml2’0.5em=0.5emX.eval0.5emsubst0.5eml20.5emin0.5em0.5em0.5em0.5em0.5em0.5em(match0.5emX.eval0.5emsubst0.5eml10.5emwith0.5em0.5em0.5em0.5em0.5em0.5em0.5em‘Abs0.5em(s,0.5embody)0.5em->0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5emX.eval0.5em[s,0.5eml2’]0.5embody0.5em0.5em0.5em0.5em0.5em0.5em0.5eml1’0.5em->0.5em‘App0.5em(l1’,0.5eml2’))0.5em0.5em0.5em0.5em0.5em‘Abs0.5em(s,0.5eml1)0.5em->0.5em0.5em0.5em0.5em0.5em0.5emlet0.5ems’0.5em=0.5emgensym0.5em()0.5emin0.5em0.5em0.5em0.5em0.5em0.5em‘Abs0.5em(s’,0.5emX.eval0.5em((s,0.5em‘Var0.5ems’)::subst)0.5eml1)endmodule0.5emrec0.5emLambda0.5em:0.5em(Eval0.5emwith0.5emtype0.5emexp0.5em=0.5emLambda.exp0.5emlambda)0.5em=0.5em0.5emLF(Lambda)module0.5emtype0.5emFreeVars0.5em=sig0.5emtype0.5emexp0.5emval0.5emfreevars0.5em:0.5emexp0.5em->0.5emstring0.5emlist0.5emendmodule0.5emLFVF(X0.5em:0.5emFreeVars0.5emwith0.5emtype0.5emexp0.5em=0.5emprivate0.5em[>0.5em’a0.5emlambda]0.5emas0.5em’a)0.5em=struct0.5em0.5emtype0.5emexp0.5em=0.5emX.exp0.5emlambda0.5em0.5emlet0.5emfreevars0.5em:0.5emexp0.5em->0.5em’b0.5em=0.5emfunction0.5em0.5em0.5em0.5em0.5em‘Var0.5emv0.5em->0.5em[v]0.5em0.5em0.5em0.5em0.5em‘App0.5em(l1,0.5eml2)0.5em->0.5emX.freevars0.5eml10.5em@0.5emX.freevars0.5eml20.5em0.5em0.5em0.5em0.5em‘Abs0.5em(s,0.5eml1)0.5em->0.5em0.5em0.5em0.5em0.5em0.5emList.filter0.5em(fun0.5emv0.5em->0.5emv0.5em<>0.5ems)0.5em(X.freevars0.5eml1)endmodule0.5emrec0.5emLambdaFV0.5em:0.5em(FreeVars0.5emwith0.5emtype0.5emexp0.5em=0.5emLambdaFV.exp0.5emlambda)0.5em=0.5em0.5emLFVF(LambdaFV)let0.5emtest10.5em=0.5em(’App0.5em(’Abs0.5em(”x”,0.5em‘Var0.5em”x”),0.5em‘Var0.5em”y”)0.5em:0.5emLambda.exp)let0.5emetest0.5em=0.5emLambda.eval0.5em[]0.5emtest1let0.5emfvtest0.5em=0.5emLambdaFV.freevars0.5emtest1type0.5em’a0.5emexpr0.5em=0.5em0.5em[’Var0.5emof0.5emstring0.5em0.5em‘Num0.5emof0.5emint0.5em0.5em‘Add0.5emof0.5em’a0.5em0.5em’a0.5em0.5em‘Mult0.5emof0.5em’a0.5em0.5em’a]module0.5emtype0.5emOperations0.5em=sig0.5eminclude0.5emEval0.5eminclude0.5emFreeVars0.5emwith0.5emtype0.5emexp0.5em:=0.5emexp0.5emendmodule0.5emEF(X0.5em:0.5emOperations0.5emwith0.5emtype0.5emexp0.5em=0.5emprivate0.5em[>0.5em’a0.5emexpr]0.5emas0.5em’a)0.5em=struct0.5em0.5emtype0.5emexp0.5em=0.5emX.exp0.5emexpr0.5em0.5emlet0.5emmapexpr0.5emf0.5em=0.5emfunction0.5em0.5em0.5em0.5em0.5em#var0.5emas0.5emv0.5em->0.5emv0.5em0.5em0.5em0.5em0.5em‘Num0.5em0.5emas0.5emn0.5em->0.5emn0.5em0.5em0.5em0.5em0.5em‘Add0.5em(e1,0.5eme2)0.5em->0.5em‘Add0.5em(f0.5eme1,0.5emf0.5eme2)0.5em0.5em0.5em0.5em0.5em‘Mult0.5em(e1,0.5eme2)0.5em->0.5em‘Mult0.5em(f0.5eme1,0.5emf0.5eme2)0.5em0.5emlet0.5emeval0.5emsubst0.5em(e0.5em:0.5emexp)0.5em:0.5emX.exp0.5em=0.5em0.5em0.5em0.5emmatch0.5emmapexpr0.5em(X.eval0.5emsubst)0.5eme0.5emwith0.5em0.5em0.5em0.5em0.5em#var0.5emas0.5emv0.5em->0.5emevalvar0.5emsubst0.5emv0.5em0.5em0.5em0.5em0.5em‘Add0.5em(’Num0.5emm,0.5em‘Num0.5emn)0.5em->0.5em‘Num0.5em(m0.5em+0.5emn)0.5em0.5em0.5em0.5em0.5em‘Mult0.5em(’Num0.5emm,0.5em‘Num0.5emn)0.5em->0.5em‘Num0.5em(m0.5em*0.5emn)0.5em0.5em0.5em0.5em0.5eme0.5em->0.5eme0.5em0.5emlet0.5emfreevars0.5em:0.5emexp0.5em->0.5em’b0.5em=0.5emfunction0.5em0.5em0.5em0.5em0.5em‘Var0.5emv0.5em->0.5em[v]0.5em0.5em0.5em0.5em0.5em‘Num0.5em0.5em->0.5em[]0.5em0.5em0.5em0.5em0.5em‘Add0.5em(e1,0.5eme2)0.5em0.5em‘Mult0.5em(e1,0.5eme2)0.5em->0.5emX.freevars0.5eme10.5em@0.5emX.freevars0.5eme2endmodule0.5emrec0.5emExpr0.5em:0.5em(Operations0.5emwith0.5emtype0.5emexp0.5em=0.5emExpr.exp0.5emexpr)0.5em=0.5em0.5emEF(Expr)let0.5emtest20.5em=0.5em(’Add0.5em(’Mult0.5em(’Num0.5em3,0.5em‘Var0.5em”x”),0.5em‘Num0.5em1)0.5em:0.5emExpr.exp)let0.5emetest20.5em=0.5emExpr.eval0.5em[“x”,0.5em‘Num0.5em2]0.5emtest2let0.5emfvstest20.5em=0.5emExpr.freevars0.5emtest2type0.5em’a0.5emlexpr0.5em=0.5em[’a0.5emlambda0.5em0.5em’a0.5emexpr]module0.5emLEF(X0.5em:0.5emOperations0.5emwith0.5emtype0.5emexp0.5em=0.5emprivate0.5em[>0.5em’a0.5emlexpr]0.5emas0.5em’a)0.5em=struct0.5em0.5emtype0.5emexp0.5em=0.5emX.exp0.5emlexpr0.5em0.5emmodule0.5emLambdaX0.5em=0.5emLF(X)0.5em0.5emmodule0.5emLambdaFVX0.5em=0.5emLFVF(X)0.5em0.5emmodule0.5emExprX0.5em=0.5emEF(X)0.5em0.5emlet0.5emeval0.5emsubst0.5em:0.5emexp0.5em->0.5emX.exp0.5em=0.5emfunction0.5em0.5em0.5em0.5em0.5em#LambdaX.exp0.5emas0.5emx0.5em->0.5emLambdaX.eval0.5emsubst0.5emx0.5em0.5em0.5em0.5em0.5em#ExprX.exp0.5emas0.5emx0.5em->0.5emExprX.eval0.5emsubst0.5emx0.5em0.5emlet0.5emfreevars0.5em:0.5emexp0.5em->0.5em’b0.5em=0.5emfunction0.5em0.5em0.5em0.5em0.5em#lambda0.5emas0.5emx0.5em->0.5emLambdaFVX.freevars0.5emxEither
of #lambda or #LambdaX.exp is
fine.0.5em0.5em0.5em0.5em
0.5em#expr0.5emas0.5emx0.5em->0.5emExprX.freevars0.5emxEither
of #expr or #ExprX.exp is
fine.endmodule0.5emrec0.5emLExpr0.5em:0.5em(Operations0.5emwith0.5emtype0.5emexp0.5em=0.5emLExpr.exp0.5emlexpr)0.5em=0.5em0.5emLEF(LExpr)let0.5emtest30.5em=0.5em0.5em(’App0.5em(’Abs0.5em(“x”,0.5em‘Add0.5em(’Mult0.5em(’Num0.5em3,0.5em‘Var0.5em”x”),0.5em‘Num0.5em1)),0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em‘Num0.5em2)0.5em:0.5emLExpr.exp)let0.5emetest30.5em=0.5emLExpr.eval0.5em[]0.5emtest3let0.5emfvtest30.5em=0.5emLExpr.freevars0.5emtest3let0.5emeoldtest0.5em=0.5emLExpr.eval0.5em[]0.5em(test20.5em:>0.5emLExpr.exp)let0.5emfvoldtest0.5em=0.5emLExpr.freevars0.5em(test20.5em:>0.5emLExpr.exp)Digression:
Parser Combinators
We have done parsing using external languages OCamlLex and Menhir, now we will look at parsers written directly in OCaml.
Language combinators are ways defining languages by composing definitions of smaller languages. For example, the combinators of the Extended Backus-Naur Form notation are:
Parsers implemented directly in a functional programming paradigm are functions from character streams to the parsed values. Algorithmically they are recursive descent parsers.
Parser combinators approach builds parsers as monad plus values:
Bind:
val (>>=) : 'a parser -> ('a -> 'b parser) -> 'b parser
p >>= f
is a parser that first parses
p
, and makes the result available for parsing
f
.Return: val return : 'a -> 'a parser
return x
parses an empty string, symbolically S = \lbrace \epsilon \rbrace, and returns
x
.MZero: val fail : 'a parser
fail
fails to parse anything, symbolically S = \varnothing = \lbrace \rbrace.MPlus: either
val <|> : 'a parser -> 'a parser -> 'a parser
,
or
val <|> : 'a parser -> 'b parser -> ('a, 'b) choice parser
p <|> q
tries p
, and if
p
succeeds, its result is returned, otherwise the parser
q
is used.The only non-monad-plus operation that has to be built into the monad is some way to consume a single character from the input stream, for example:
val satisfy : (char -> bool) -> char parser
satisfy (fun c -> c = 'a')
consumes the character
“a” from the input stream and returns it; if the input stream starts
with a different character, this parser fails.Ordinary monadic recursive descent parsers do not allow left-recursion: if a cycle of calls not consuming any character can be entered when a parse failure should occur, the cycle will keep repeating.
The parser monad is actually a composition of two monads:
Alternatively, one can split the state monad into a reader monad with the parsed string, and a state monad with the parsing position.
Recall Lecture 8, especially slides 54-63.
On my new OPAM installation of OCaml, I run the parsing example with:
ocamlbuild Plugin1.cmxs -pp "camlp4o /home/lukstafi/.opam/4.02.1/lib/monad-custom/pa_monad.cmo"
ocamlbuild Plugin2.cmxs -pp "camlp4o /home/lukstafi/.opam/4.02.1/lib/monad-custom/pa_monad.cmo"
ocamlbuild PluginRun.native -lib dynlink -pp "camlp4o ~/.opam/4.02.1/lib/monad-custom/pa_monad.cmo" -- "(3*(6+1))" _build/Plugin1.cmxs _build/Plugin2.cmxs
We experiment with a different approach to monad-plus. The merits of this approach (or lack thereof) is left as an exercise. lazy-monad-plus:
val0.5emmplus0.5em:0.5em’a0.5emmonad ->0.5em’a0.5emmonad0.5emLazy.t0.5em->0.5em’a0.5emmonad
Parser Combinators: Implementation of lazy-monad-plus
Monad.ml
. First an operation from
MonadPlusOps.0.5em0.5emlet0.5emmsummap0.5emf0.5eml0.5em=0.5em0.5em0.5em0.5emList.foldleft
Folding
left reversers the apparent order of
composition,0.5em0.5em0.5em0.5em0.5em0.5em
(fun0.5emacc0.5ema0.5em->0.5emmplus0.5emacc0.5em(lazy0.5em(f0.5ema)))0.5emmzero0.5emlorder
from l
is preserved.
type0.5em’a0.5emllist0.5em=0.5emLNil0.5em0.5emLCons0.5emof0.5em’a0.5em0.5em’a0.5emllist0.5emLazy.tlet0.5emrec0.5emltake0.5emn0.5em=0.5emfunction0.5em0.5emLCons0.5em(a,0.5eml)0.5emwhen0.5emn0.5em>0.5em10.5em->0.5ema::(ltake0.5em(n-1)0.5em(Lazy.force0.5eml))0.5em0.5emLCons0.5em(a,0.5eml)0.5emwhen0.5emn0.5em=0.5em10.5em->0.5em[a]Avoid
forcing the tail if not
needed.0.5em->0.5em[]let0.5emrec0.5emlappend0.5eml10.5eml20.5em=0.5em0.5emmatch0.5eml10.5emwith0.5emLNil0.5em->0.5emLazy.force0.5eml2
0.5em0.5em0.5emLCons0.5em(hd,0.5emtl)0.5em->
LCons0.5em(hd,0.5emlazy0.5em(lappend0.5em(Lazy.force0.5emtl)0.5eml2))let0.5emrec0.5emlconcatmap0.5emf0.5em=0.5emfunction0.5em0.5em0.5emLNil0.5em->0.5emLNil0.5em0.5em0.5emLCons0.5em(a,0.5eml)0.5em->0.5emlappend0.5em(f0.5ema)0.5em(lazy0.5em(lconcatmap0.5emf0.5em(Lazy.force0.5eml)))module0.5emLListM0.5em=0.5emMonadPlus0.5em(struct0.5em0.5emtype0.5em’a0.5emt0.5em=0.5em’a0.5emllist0.5em0.5emlet0.5embind0.5ema0.5emb0.5em=0.5emlconcatmap0.5emb0.5ema0.5em0.5emlet0.5emreturn0.5ema0.5em=0.5emLCons0.5em(a,0.5emlazy0.5emLNil)0.5em0.5emlet0.5emmzero0.5em=0.5emLNil0.5em0.5emlet0.5emmplus0.5em=0.5emlappendend)Parser
Combinators: the Parsec* Monad
Parsec.ml
:open0.5emMonadmodule0.5emtype0.5emPARSE0.5em=0.5emsig0.5em0.5emtype0.5em'a0.5embacktrackingmonad
Name
for the underlying
monad-plus.0.5em0.5emtype0.5em’a0.5emparsingstate0.5em=0.5emint0.5em->0.5em(‘a0.5em0.5emint)0.5embacktrackingmonad
Processing
state –
position.0.5em0.5emtype0.5em’a0.5emt0.5em=0.5emstring0.5em->0.5em'a0.5emparsingstate
Reader
for the parsed
text.0.5em0.5eminclude0.5emMONADPLUSOPS0.5em0.5emval0.5em(<>)0.5em:0.5em’a0.5emmonad0.5em->0.5em’a0.5emmonad0.5emLazy.t0.5em->0.5em'a0.5emmonad
A
synonym for
mplus
.0.5em0.5emval0.5emrun0.5em:0.5em’a0.5emmonad0.5em->0.5em’a0.5emt0.5em0.5emval0.5emrunT0.5em:0.5em’a0.5emmonad0.5em->0.5emstring0.5em->0.5emint0.5em->0.5em’a0.5embacktrackingmonad0.5em0.5emval0.5emsatisfy0.5em:0.5em(char0.5em->0.5embool)0.5em->0.5emchar0.5emmonad
Consume
a character of the specified
class.0.5em0.5emval0.5emendoftext0.5em:0.5emunit0.5emmonadCheck for end
of the processed
text.endmodule0.5emParseT0.5em(MP0.5em:0.5emMONADPLUSOPS)0.5em:0.5em0.5emPARSE0.5emwith0.5emtype0.5em’a0.5embacktrackingmonad0.5em:=0.5em’a0.5emMP.monad0.5em=struct0.5em0.5emtype0.5em’a0.5embacktrackingmonad0.5em=0.5em’a0.5emMP.monad0.5em0.5emtype0.5em’a0.5emparsingstate0.5em=0.5emint0.5em->0.5em(’a0.5em0.5emint)0.5emMP.monad0.5em0.5emmodule0.5emM0.5em=0.5emstruct0.5em0.5em0.5em0.5emtype0.5em’a0.5emt0.5em=0.5emstring0.5em->0.5em’a0.5emparsingstate0.5em0.5em0.5em0.5em0.5emlet0.5emreturn0.5ema0.5em=0.5emfun0.5ems0.5emp0.5em->0.5emMP.return0.5em(a,0.5emp)0.5em0.5em0.5em0.5emlet0.5embind0.5emm0.5emb0.5em=0.5emfun0.5ems0.5emp0.5em->0.5em0.5em0.5em0.5em0.5em0.5emMP.bind0.5em(m0.5ems0.5emp)0.5em(fun0.5em(a,0.5emp’)0.5em->0.5emb0.5ema0.5ems0.5emp’)0.5em0.5em0.5em0.5emlet0.5emmzero0.5em=0.5emfun0.5em0.5em_0.5em->0.5emMP.mzero0.5em0.5em0.5em0.5emlet0.5emmplus0.5emma0.5emmb0.5em=0.5emfun0.5ems0.5emp0.5em->0.5em0.5em0.5em0.5em0.5em0.5emMP.mplus0.5em(ma0.5ems0.5emp)0.5em(lazy0.5em(Lazy.force0.5emmb0.5ems0.5emp))0.5em0.5emend0.5em0.5eminclude0.5emM0.5em0.5eminclude0.5emMonadPlusOps(M)0.5em0.5emlet0.5em(<>)0.5emma0.5emmb0.5em=0.5emmplus0.5emma0.5emmb0.5em0.5emlet0.5emrunT0.5emm0.5ems0.5emp0.5em=0.5emMP.lift0.5emfst0.5em(m0.5ems0.5emp)0.5em0.5emlet0.5emsatisfy0.5emf0.5ems0.5emp0.5em=0.5em0.5em0.5em0.5emif0.5emp0.5em<0.5emString.length0.5ems0.5em&&0.5emf0.5ems.[p]Consuming
a character means accessing
it0.5em0.5em0.5em0.5emthen0.5emMP.return0.5em(s.[p],0.5emp0.5em+0.5em1)0.5emelse0.5emMP.mzero
and
advancing the parsing
position.0.5em0.5emlet0.5emendoftext0.5ems0.5emp0.5em=0.5em0.5em0.5em0.5emif0.5emp0.5em>=0.5emString.length0.5ems0.5emthen0.5emMP.return0.5em((),0.5emp)0.5emelse0.5emMP.mzeroendmodule0.5emtype0.5emPARSEOPS0.5em=0.5emsig0.5em0.5eminclude0.5emPARSE0.5em0.5emval0.5emmany0.5em:0.5em’a0.5emmonad0.5em->0.5em’a0.5emlist0.5emmonad0.5em0.5emval0.5emopt0.5em:0.5em’a0.5emmonad0.5em->0.5em’a0.5emoption0.5emmonad0.5em0.5emval0.5em(?)0.5em:0.5em’a0.5emmonad0.5em->0.5em’a0.5emoption0.5emmonad0.5em0.5emval0.5emseq0.5em:0.5em’a0.5emmonad0.5em->0.5em’b0.5emmonad0.5emLazy.t0.5em->0.5em(‘a0.5em0.5em’b)0.5emmonad
Exercise:
why laziness
here?0.5em0.5emval0.5em(<>)0.5em:0.5em’a0.5emmonad0.5em->0.5em’b0.5emmonad0.5emLazy.t0.5em->0.5em(’a0.5em0.5em’b)0.5emmonad
Synonym
for
seq
.0.5em0.5emval0.5emlowercase0.5em:0.5emchar0.5emmonad0.5em0.5emval0.5emuppercase0.5em:0.5emchar0.5emmonad0.5em0.5emval0.5emdigit0.5em:0.5emchar0.5emmonad0.5em0.5emval0.5emalpha0.5em:0.5emchar0.5emmonad0.5em0.5emval0.5emalphanum0.5em:0.5emchar0.5emmonad0.5em0.5emval0.5emliteral0.5em:0.5emstring0.5em->0.5emunit0.5emmonad
Consume
characters of the given
string.0.5em0.5emval0.5em(<<>)0.5em:0.5emstring0.5em->0.5em’a0.5emmonad0.5em->0.5em'a0.5emmonad
Prefix
and postfix
keywords.0.5em0.5emval0.5em(<>>)0.5em:0.5em’a0.5emmonad0.5em->0.5emstring0.5em->0.5em’a0.5emmonadendmodule0.5emParseOps0.5em(R0.5em:0.5emMONADPLUSOPS)0.5em0.5em(P0.5em:0.5emPARSE0.5emwith0.5emtype0.5em’a0.5embacktrackingmonad0.5em:=0.5em’a0.5emR.monad)0.5em:0.5em0.5emPARSEOPS0.5emwith0.5emtype0.5em’a0.5embacktrackingmonad0.5em:=0.5em’a0.5emR.monad0.5em=struct0.5em0.5eminclude0.5emP0.5em0.5emlet0.5emrec0.5emmany0.5emp0.5em=0.5em0.5em0.5em0.5em(perform0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5emr0.5em<–0.5emp;0.5emrs0.5em<–0.5emmany0.5emp;0.5emreturn0.5em(r::rs))0.5em0.5em0.5em0.5em++0.5emlazy0.5em(return0.5em[])0.5em0.5emlet0.5emopt0.5emp0.5em=0.5em(p0.5em>>=0.5em(fun0.5emx0.5em->0.5emreturn0.5em(Some0.5emx)))0.5em++0.5emlazy0.5em(return0.5emNone)0.5em0.5emlet0.5em(?)0.5emp0.5em=0.5emopt0.5emp0.5em0.5emlet0.5emseq0.5emp0.5emq0.5em=0.5emperform0.5em0.5em0.5em0.5em0.5em0.5emx0.5em<–0.5emp;0.5emy0.5em<–0.5emLazy.force0.5emq;0.5emreturn0.5em(x,0.5emy)0.5em0.5emlet0.5em(<>)0.5emp0.5emq0.5em=0.5emseq0.5emp0.5emq0.5em0.5emlet0.5emlowercase0.5em=0.5emsatisfy0.5em(fun0.5emc0.5em->0.5emc0.5em>=0.5em’a’0.5em&&0.5emc0.5em<=0.5em’z’)0.5em0.5emlet0.5emuppercase0.5em=0.5emsatisfy0.5em(fun0.5emc0.5em->0.5emc0.5em>=0.5em’A’0.5em&&0.5emc0.5em<=0.5em’Z’)0.5em0.5emlet0.5emdigit0.5em=0.5emsatisfy0.5em(fun0.5emc0.5em->0.5emc0.5em>=0.5em’0’0.5em&&0.5emc0.5em<=0.5em’9’)0.5em0.5emlet0.5emalpha0.5em=0.5emlowercase0.5em++0.5emlazy0.5emuppercase0.5em0.5emlet0.5emalphanum0.5em=0.5emalpha0.5em++0.5emlazy0.5emdigit0.5em0.5emlet0.5emliteral0.5eml0.5em=0.5em0.5em0.5em0.5emlet0.5emrec0.5emloop0.5empos0.5em=0.5em0.5em0.5em0.5em0.5em0.5emif0.5empos0.5em=0.5emString.length0.5eml0.5emthen0.5emreturn0.5em()0.5em0.5em0.5em0.5em0.5em0.5emelse0.5emsatisfy0.5em(fun0.5emc0.5em->0.5emc0.5em=0.5eml.[pos])0.5em>>-0.5emloop0.5em(pos0.5em+0.5em1)0.5emin0.5em0.5em0.5em0.5emloop0.5em00.5em0.5emlet0.5em(<<>)0.5embra0.5emp0.5em=0.5emliteral0.5embra0.5em>>-0.5emp0.5em0.5emlet0.5em(<>>)0.5emp0.5emket0.5em=0.5emp0.5em>>=0.5em(fun0.5emx0.5em->0.5emliteral0.5emket0.5em>>-0.5emreturn0.5emx)endParser
Combinators: Tying the Recursive Knot
PluginBase.ml
:module0.5emParseM0.5em=0.5em0.5emParsec.ParseOps0.5em(Monad.LListM)0.5em(Parsec.ParseT0.5em(Monad.LListM))open0.5emParseMlet0.5emgrammarrules0.5em:0.5em(int0.5emmonad0.5em->0.5emint0.5emmonad)0.5emlist0.5emref0.5em=0.5emref0.5em[]let0.5emgetlanguage0.5em()0.5em:0.5emint0.5emmonad0.5em=0.5em0.5emlet0.5emrec0.5emresult0.5em=0.5em0.5em0.5em0.5emlazy0.5em0.5em0.5em0.5em0.5em0.5em(List.foldleft0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em(fun0.5emacc0.5emlang0.5em->0.5emacc0.5em<>0.5emlazy0.5em(lang0.5em(Lazy.force0.5emresult)))0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5emmzero0.5em!grammarrules)0.5eminEnsure we parse the whole text.0.5em0.5emperform0.5emr0.5em<–0.5emLazy.force0.5emresult;0.5emendoftext;0.5emreturn0.5emrParser Combinators: Dynamic Code Loading
PluginRun.ml
:let0.5emloadplug0.5emfname0.5em:0.5emunit0.5em=0.5em0.5emlet0.5emfname0.5em=0.5emDynlink.adaptfilename0.5emfname0.5emin0.5em0.5emif0.5emSys.fileexists0.5emfname0.5emthen0.5em0.5em0.5em0.5emtry0.5emDynlink.loadfile0.5emfname0.5em0.5em0.5em0.5emwith0.5em0.5em0.5em0.5em0.5em0.5em(Dynlink.Error0.5emerr)0.5emas0.5eme0.5em->0.5em0.5em0.5em0.5em0.5em0.5emPrintf.printf0.5em”0.5emloading0.5emplugin:0.5em%s%!“0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em(Dynlink.errormessage0.5emerr);0.5em0.5em0.5em0.5em0.5em0.5emraise0.5eme0.5em0.5em0.5em0.5em0.5eme0.5em->0.5emPrintf.printf0.5em”0.5emerror0.5emwhile0.5emloading0.5emplugin%!“0.5em0.5emelse0.5em(0.5em0.5em0.5em0.5emPrintf.printf0.5em”0.5emfile0.5em%s0.5emdoes0.5emnot0.5emexist%!“0.5emfname;0.5em0.5em0.5em0.5emexit0.5em(-1))let0.5em()0.5em=0.5em0.5emfor0.5emi0.5em=0.5em20.5emto0.5emArray.length0.5emSys.argv0.5em-0.5em10.5emdo0.5em0.5em0.5em0.5emloadplug0.5emSys.argv.(i)0.5emdone;0.5em0.5emlet0.5emlang0.5em=0.5emPluginBase.getlanguage0.5em()0.5emin0.5em0.5emlet0.5emresult0.5em=0.5em0.5em0.5em0.5emMonad.LListM.run0.5em0.5em0.5em0.5em0.5em0.5em(PluginBase.ParseM.runT0.5emlang0.5emSys.argv.(1)0.5em0)0.5emin0.5em0.5emmatch0.5emMonad.ltake0.5em10.5emresult0.5emwith0.5em0.5em0.5em[]0.5em->0.5emPrintf.printf0.5em”0.5emerror%!“0.5em0.5em0.5emr::0.5em->0.5emPrintf.printf0.5em”:0.5em%d%!“0.5emrParser Combinators: Toy Example
Plugin1.ml
:open0.5emPluginBase.ParseMlet0.5emdigitofchar0.5emd0.5em=0.5emintofchar0.5emd0.5em-0.5emintofchar0.5em’0’let0.5emnumber=0.5em0.5emlet0.5emrec0.5emnum0.5em=Numbers: N := D N | D where D is digits.0.5em0.5em0.5em0.5emlazy0.5em(0.5em0.5em(perform0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5emd0.5em<–0.5emdigit;0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em(n,0.5emb)0.5em<–0.5emLazy.force0.5emnum;0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5em0.5emreturn0.5em(digitofchar0.5emd0.5em0.5emb0.5em+0.5emn,0.5emb0.5em0.5em10))0.5em0.5em0.5em0.5em0.5em0.5em<>0.5emlazy0.5em(digit0.5em>>=0.5em(fun0.5emd0.5em->0.5emreturn0.5em(digitofchar0.5emd,0.5em10))))0.5emin0.5em0.5emLazy.force0.5emnum0.5em>>0.5emfstlet0.5emaddition0.5emlang0.5em=Addition rule: S \rightarrow (S + S).0.5em0.5emperformRequiring a parenthesis ( turns the rule into non-left-recursive.0.5em0.5em0.5em0.5emliteral0.5em”(“;0.5emn10.5em<–0.5emlang;0.5emliteral0.5em”+“;0.5emn20.5em<–0.5emlang;0.5emliteral0.5em”)“;0.5em0.5em0.5em0.5emreturn0.5em(n10.5em+0.5emn2)let0.5em()0.5em= PluginBase.(grammarrules0.5em:=0.5emnumber0.5em::0.5emaddition0.5em::0.5em!grammarrules)
Plugin2.ml
:open0.5emPluginBase.ParseMlet0.5emmultiplication0.5emlang0.5em=0.5em0.5emperformMultiplication rule: S \rightarrow (S \ast S).0.5em0.5em0.5em0.5emliteral0.5em”(“;0.5emn10.5em<–0.5emlang;0.5emliteral0.5em”“;0.5emn20.5em<–0.5emlang;0.5emliteral0.5em”)“;0.5em0.5em0.5em0.5emreturn0.5em(n10.5em0.5emn2)let0.5em()0.5em= PluginBase.(grammarrules0.5em:=0.5emmultiplication0.5em::0.5em!grammarrules)
Functional ProgrammingŁukasz Stafiniak
The Expression Problem
Exercise 1: Implement
the string_of_
functions or methods, covering all data
cases, corresponding to the eval_
functions in at least two
examples from the lecture, including both an object-based example and a
variant-based example (either standard, or polymorphic, or extensible
variants).
Exercise 2: Split at least one of the examples from the previous exercise into multiple files and demonstrate separate compilation.
Exercise 3: Can we drop the tags
Lambda_t
, Expr_t
and LExpr_t
used
in the examples based on standard variants (file
FP_ADT.ml
)? When using polymorphic variants, such tags are
not needed.
Exercise 4: Factor-out the sub-language consisting
only of variables, thus eliminating the duplication of tags
VarL
, VarE
in the examples based on standard
variants (file FP_ADT.ml
).
Exercise 5: Come up with a scenario where the extensible variant types-based solution leads to a non-obvious or hard to locate bug.
Exercise 6: * Re-implement the direct object-based
solution to the expression problem (file Objects.ml
) to
make it more satisfying. For example, eliminate the need for some of the
rename
, apply
, compute
methods.
Exercise 7: Re-implement the visitor pattern-based
solution to the expression problem (file Visitor.ml
) in a
functional way, i.e. replace the mutable fields subst
and
beta_redex
in the eval_lambda
class with a
different solution to the problem of treating abs
and
non-abs
expressions differently.
** See if you can replace the reference cells result
in
evalN
and freevarsN
functions (for
N=1,2,3
) with a different solution to the problem of
polymorphism wrt. the type of the computed values.*
Exercise 8: Extend the sub-language
expr_visit
with variables, and add to arguments of the
evaluation constructor eval_expr
the substitution. Handle
the problem of potentially duplicate fields subst
. (One
approach might be to use ideas from exercise 6.)
Exercise 9: Impement the following modifications to
the example from the file PolyV.ml
:
*var*
type.*eval3*
,*freevars3*
and other functions as
required, so that explicit subtyping, e.g. in
eval30.5em[]0.5em(test20.5em:>0.5emlexprt),
is not necessary.*eval_lexpr*
and
*freevars_lexpr*
, by implementing a cascading design
rather than a “divide-and-conquer” design.Exercise 10: Streamline the solution
PolyRecM.ml
by extending the language of \lambda-expressions with arithmetic
expressions, rather than defining the sub-languages separately and then
merging them. See slide on page 15 of Jacques Garrigue Structural
Types, Recursive Modules, and the Expression Problem.
Exercise 11: Transform a parser monad, or rewrite the parser monad transformer, by adding state for the line and column numbers.
** How to implement a monad transformer transformer in OCaml?*
Exercise 12: Implement _of_string
functions as parser combinators on top of the example
PolyRecM.ml
. Sections 4.3 and 6.2 of Monadic Parser
Combinators by Graham Hutton and Erik Meijer might be helpful.
Split the result into multiple files as in Exercise 2 and demonstrate dynamic loading of code.
Exercise 13: What are the benefits and drawbacks of our lazy-monad-plus (built on top of odd lazy lists) approach, as compared to regular monad-plus built on top of even lazy lists? To additionally illustrate your answer:
Exercise 1.
Give types of the following expressions, either by guessing or inferring them by hand:
Exercise 2.
Assume that the corresponding expression from previous exercise is
bound to name foo
. What are the values computed for the
expressions (compute in your head or derive on paper):
Exercise 3.
Give example expressions that have the following types (without using type constraints):
(int -> int) -> bool
'a option -> 'a list
Exercise 4.
Write function that returns the list of all lists containing elements from the input list, preserving order from the input list, but without two elements.
Exercise 5.
Write a breadth-first-search function that returns an element from a
binary tree for which a predicate holds, or None
if no such
element exists. The function should have signature:
val bfs : ('a -> bool) -> 'a btree -> 'a option
Exercise 6.
Solve the n-queens problem using backtracking based on lists.
Available functions: from_to
, concat_map
,
concat_foldl
, unique
.
Hint functions (asking for hint each loses one point):
valid_queens
, add_queen
,
find_queen
, find_queens
. Final function
solve
takes n as an
argument. Each function, other than valid_queens
that takes
3 lines, fits on one line.
Exercise 7.
Provide an algebraic specification and an implementation for first-in-first-out queues (lecture 5 exercise 9).
Functional ProgrammingFebruary 5th 2013
Exam set 1
Exercise 1: (Blue.) What is the type of the
subexpression y
as part of the expression below assuming
that the whole expression has the type given?
y |
))*
: (’a -> ’b -> ’b) -> ’a -> ’b -> ’b
Exercise 2: (Blue.) Write an example function with type:
*((int -> int) -> bool) -> int*
Tell “in your words” what it does.
Exercise 3: (Green.) Write a function
last : 'a list -> 'a option
that returns the last
element of a list.
Exercise 4: (Green.) Duplicate the elements of a list.
Exercise 5: (Yellow.) Drop every N’th element from a list.
Exercise 6: (Yellow.) Construct completely balanced binary trees of given depth.
In a completely balanced binary tree, the following property holds for every node: The number of nodes in its left subtree and the number of nodes in its right subtree are almost equal, which means their difference is not greater than one.
Write a function cbal_tree
to construct completely
balanced binary trees for a given number of nodes. The function should
generate the list of all solutions (e.g. via backtracking). Put the
letter 'x'
as information into all nodes of the
tree.
Exercise 7: (White.) Due to Yaron Minsky.
Consider a datatype to store internet connection information. The
time when_initiated
marks the start of connecting and is
not needed after the connection is established (it is only used to
decide whether to give up trying to connect). The ping information is
available for established connection but not straight away.
type connectionstate = | Connecting | Connected | Disconnectedtype connectioninfo = { state : connectionstate; server : Inetaddr.t; lastpingtime : Time.t option; lastpingid : int option; sessionid : string option; wheninitiated : Time.t option; whendisconnected : Time.t option;}
(The types Time.t and Inetaddr.t come from the library Core*
used where Yaron Minsky works. You can replace them with
float
and Unix.inet_addr. Load the Unix library in the
interactive toplevel by #load "unix.cma";;
.) Rewrite the
type definitions so that the datatype will contain only reasonable
combinations of information.*
Exercise 8: (White.) Design an algebraic specification and write a signature for first-in-first-out queues. Provide two implementations: one straightforward using a list, and another one using two lists: one for freshly added elements providing efficient queueing of new elements, and “reversed” one for efficient popping of old elements.
Exercise 9: (Orange.) Implement
while_do
in terms of repeat_until
.
Exercise 10: (Orange.) Implement a map from keys to values (a dictionary) using only functions (without data structures like lists or trees).
Exercise 11: (Purple.) One way to express constraints on a polymorphic function is to write its type in the form: \forall \alpha _{1} \ldots \alpha _{n} [C] . \tau, where \tau is the type of the function, \alpha _{1} \ldots \alpha _{n} are the polymorphic type variables, and C are additional constraints that the variables \alpha _{1} \ldots \alpha _{n} have to meet. Let’s say we allow “local variables” in C: for example C = \exists \beta . \alpha _{1} \dot{=} \operatorname{list} (\beta). Why the general form \forall \beta [C] . \beta is enough to express all types of the general form \forall \alpha _{1} \ldots \alpha _{n} [C] . \tau?
Exercise 12: (Purple.) Define a type that corresponds to a set with a googleplex of elements (i.e. 10^{10^{100}} elements).
Exercise 13: (Red.) In a height-balanced binary tree, the following property holds for every node: The height of its left subtree and the height of its right subtree are almost equal, which means their difference is not greater than one. Consider a height-balanced binary tree of height h. What is the maximum number of nodes it can contain? Clearly, \operatorname{maxN}= 2 h - 1. However, finding the minimum number \operatorname{minN} is more difficult.
Construct all the height-balanced binary trees with a given nuber
of nodes. hbal_tree_nodes n
returns a list of all
height-balanced binary tree with n
nodes.
Find out how many height-balanced trees exist for n
= 15.
Exercise 14: (Crimson.) To construct a Huffman code for symbols with probability/frequency, we can start by building a binary tree as follows. The algorithm uses a priority queue where the node with lowest probability is given highest priority:
Label each left edge by 0
and right edge by
1
. The final binary code assigns the string of bits on the
path from root to the symbol as its code.
We suppose a set of symbols with their frequencies, given as a
list of Fr(S,F) terms. Example:
fs = [Fr(a,45); Fr(b,13); Fr(c,12); Fr(d,16); Fr(e,9); Fr(f,5)]
.
Our objective is to construct a list Hc(S,C)
terms, where
C
is the Huffman code word for the symbol S
.
In our example, the result could be
hs = [Hc(a,'0'); Hc(b,'101'); Hc(c,'100'); Hc(d,'111'); Hc(e,'1101'); Hc(f,'1100')]
[Hc(a,'01')
,…etc.]. The task shall be performed by the
function huffman defined as follows: huffman(fs)
returns
the Huffman code table for the frequency table fs
.
Exercise 15: (Black.) Implement the Gaussian Elimination algorithm for solving linear equations and inverting square invertible matrices.
Functional ProgrammingFebruary 5th 2013
Exam set 2
Exercise 1: (Blue.) What is the type of the
subexpression f
as part of the expression below assuming
that the whole expression has the type given?
(f y))*
: (’a -> ’b -> ’b) -> ’a -> ’b -> ’b
Exercise 2: (Blue.) Write an example function with type:
*(int -> int list) -> bool*
Tell “in your words” what it does.
Exercise 3: (Green.) Find the number of elements of a list.
Exercise 4: (Green.) Split a list into two parts; the length of the first part is given.
Exercise 5: (Yellow.) Rotate a list N places to the left.
Exercise 6: (Yellow.) Let us call a binary tree
symmetric if you can draw a vertical line through the root node and then
the right subtree is the mirror image of the left subtree. Write a
function is_symmetric
to check whether a given binary tree
is symmetric.
Exercise 7: (White.) By “traverse a tree” we mean: write a function that takes a tree and returns a list of values in the nodes of the tree. Traverse a tree in breadth-first order – first values in more shallow nodes.
Exercise 8: (White.) Generate all combinations of K distinct elements chosen from the N elements of a list.
Exercise 9: (Orange.) Implement a topological sort of a graph: write a function that either returns a list of graph nodes in topological order or informs (via exception or option type) that the graph has a cycle.
Exercise 10: (Orange.) Express
fold_left
in terms of fold_right
. Hint:
continuation passing style.
Exercise 11: (Purple.) Show why for a monomorphic specification, if datastructures d_{1} and d_{2} have the same behavior under all operations, then they have the same representation d_{1} = d_{2} in all implementations.
Exercise 12: (Purple.) append
for lazy
lists returns in constant time. Where has its linear-time complexity
gone? Explain how you would account for this in a time complexity
analysis.
Exercise 13: (Red.) Write a function
ms_tree graph
to construct the minimal spanning
tree of a given weighted graph. A weighted graph will be
represented as follows:
*type 'a weighted_graph = {nodes : 'a list; edges : ('a * 'a * int) list}*
The labels identify the nodes 'a
uniquely and there
is at most one edge between a pair of nodes. A triple
(a,b,w)
inside edges
corresponds to edge
between a
and b
with weight w
.
The minimal spanning tree is a subset of edges
that forms
an undirected tree, covers all nodes of the graph, and has the minimal
sum of weights.
Exercise 14: (Crimson.) Von Koch’s conjecture. Given a tree with N nodes (and hence N-1 edges). Find a way to enumerate the nodes from 1 to N and, accordingly, the edges from 1 to N-1 in such a way, that for each edge K the difference of its node numbers equals to K. The conjecture is that this is always possible.
For small trees the problem is easy to solve by hand. However, for larger trees, and 14 is already very large, it is extremely difficult to find a solution. And remember, we don’t know for sure whether there is always a solution!
Write a function that calculates a numbering scheme for a given tree. What is the solution for the larger tree pictured above?
Exercise 15: (Black.) Based on our search engine implementation, write a function that for a list of keywords returns three best “next keyword” suggestions (in some sense of “best”, e.g. occurring in most of documents containing the given words).
Functional ProgrammingFebruary 5th 2013
Exam set 3
Exercise 1: (Blue.) What is the type of the
subexpression f y
as part of the expression below assuming
that the whole expression has the type given?
))*
: (’a -> ’b -> ’b) -> ’a -> ’b -> ’b
Exercise 2: (Blue.) Write an example function with type:
*(int -> int -> bool option) -> bool list*
Tell “in your words” what it does.
Exercise 3: (Green.) Find the k’th element of a list.
Exercise 4: (Green.) Insert an element at a given position into a list.
Exercise 5: (Yellow.) Group the elements of a set into disjoint subsets. Represent sets as lists, preserve the order of elements. The required sizes of subsets are given as a list of numbers.
Exercise 6: (Yellow.) A complete binary tree with height H is defined as follows: The levels 1, 2, 3, \ldots, H - 1 contain the maximum number of nodes (i.e 2^{i - 1} at the level i, note that we start counting the levels from 1 at the root). In level H, which may contain less than the maximum possible number of nodes, all the nodes are “left-adjusted”. This means that in a levelorder tree traversal all internal nodes come first, the leaves come second, and empty successors (the nil’s which are not really nodes!) come last.
We can assign an address number to each node in a complete binary
tree by enumerating the nodes in levelorder, starting at the root with
number 1. In doing so, we realize that for every node X with address A
the following property holds: The address of X’s left and right
successors are 2A and 2A+1, respectively, supposed the
successors do exist. This fact can be used to elegantly construct a
complete binary tree structure. Write a function
is_complete_binary_tree
with the following specification:
is_complete_binary_tree n t
returns true iff t
is a complete binary tree with n
nodes.
Exercise 7: (White.) Write two sorting algorithms, working on lists: merge sort and quicksort.
Exercise 8: (White.) Express in terms of
fold_left
or fold_right
, i.e. with all
recursion contained in the call to one of these functions, run-length
encoding of a list (exercise 10 from 99 Problems).
*encode [‘a;‘a;‘a;‘a;‘b;‘c;‘c;‘a;‘a;‘d] = [4,‘a; 1,‘b; 2,‘c; 2,‘a; 1,‘d]*
Exercise 9: (Orange.) Implement Priority Queue module that is an abstract data type for polymorphic queues parameterized by comparison function: the empty queue creation has signature
val make_empty : leq:('a -> 'a -> bool) -> 'a prio_queue
Provide only functions: make_empty
,
add
, min
, delete_min
. Is this
data structure “safe”?
Implement the heap as a heap-ordered tree, i.e. in which the element at each node is no larger than the elements at its children. Unbalanced binary trees are OK.
Exercise 10: (Orange.) Write a function that transposes a rectangular matrix represented as a list of lists.
Exercise 11: (Purple.) Find the bijective functions between the types corresponding to a (a^b + c) and a^{b + 1} + ac (in OCaml).
Exercise 12: (Purple.) Show the monad-plus laws for
OptionM
monad.
Exercise 13: (Red.) As a preparation for drawing the tree, a layout algorithm is required to determine the position of each node in a rectangular grid. Several layout methods are conceivable, one of them is shown in the illustration below.
In this layout strategy, the position of a node v is obtained by the following two rules:
In order to store the position of the nodes, we redefine the OCaml type representing a node (and its successors) as follows:
type 'a pos_binary_tree =
| E (* represents the empty tree *)
| N of 'a * int * int * 'a pos_binary_tree * 'a pos_binary_tree
N(w,x,y,l,r)
represents a (non-empty) binary tree
with root w “positioned” at (x,y)
, and subtrees
l
and r
. Write a function
layout_binary_tree
with the following specification:
layout_binary_tree t
returns the “positioned” binary tree
obtained from the binary tree t
.
An alternative layout method is depicted in the illustration:
Find out the rules and write the corresponding function.
Hint: On a given level, the horizontal distance between neighboring nodes is constant.
Exercise 14: (Crimson.) Nonograms. Each row and column of a rectangular bitmap is annotated with the respective lengths of its distinct strings of occupied cells. The person who solves the puzzle must complete the bitmap given only these lengths.
Problem statement: Solution:
|_|_|_|_|_|_|_|_| 3 |_|X|X|X|_|_|_|_| 3
|_|_|_|_|_|_|_|_| 2 1 |X|X|_|X|_|_|_|_| 2 1
|_|_|_|_|_|_|_|_| 3 2 |_|X|X|X|_|_|X|X| 3 2
|_|_|_|_|_|_|_|_| 2 2 |_|_|X|X|_|_|X|X| 2 2
|_|_|_|_|_|_|_|_| 6 |_|_|X|X|X|X|X|X| 6
|_|_|_|_|_|_|_|_| 1 5 |X|_|X|X|X|X|X|_| 1 5
|_|_|_|_|_|_|_|_| 6 |X|X|X|X|X|X|_|_| 6
|_|_|_|_|_|_|_|_| 1 |_|_|_|_|X|_|_|_| 1
|_|_|_|_|_|_|_|_| 2 |_|_|_|X|X|_|_|_| 2
1 3 1 7 5 3 4 3 1 3 1 7 5 3 4 3
2 1 5 1 2 1 5 1
For the example above, the problem can be stated as the two lists
[[3];[2;1];[3;2];[2;2];[6];[1;5];[6];[1];[2]]
and
[[1;2];[3;1];[1;5];[7;1];[5];[3];[4];[3]]
which give the
“solid” lengths of the rows and columns, top-to-bottom and
left-to-right, respectively. Published puzzles are larger than this
example, e.g. 2520, and apparently always have unique
solutions.*
Exercise 15: (Black.) Leftist heaps are heap-ordered binary trees that satisfy the leftist property: the rank of any left child is at least as large as the rank of its right sibling. The rank of a node is defined to be the length of its right spine, i.e. the rightmost path from the node in question to an empty node. Implement O (\log n) worst case time complexity Priority Queues based on leftist heaps. Each node of the tree should contain its rank.
Note that the elements along any path through a heap-ordered tree are stored in sorted order. The key insight behind leftist heaps is that two heaps can be merged by merging their right spines as you would merge two sorted lists, and then swapping the children of nodes along this path as necessary to restore the leftist property.