INRIA
IHP, April 2014
1 / 83
Jonathan Protzenko, Thibaut Balabonski, Henri Chataing, Armaël Guéneau, Cyprien Mangin.
2 / 83
Two lectures on Mezzo.
• April 29th, 2pm: motivation and examples.
• April 30th, 4pm: type soundness, data race freedom.
3 / 83
Write-once references: usage Mezzo: design principles Mezzo: motivation
Write-once references: interface & implementation
Algebraic data structures Sharing mutable data Conclusion
4 / 83
Write-once references: usage
5 / 83
A write-once reference:
• can be writtenat mostonce;
• can be read onlyafterit has been written.
Let us look at a concrete example of use...
6 / 83
Usage
.
frozen
get
.
open woref
val () = set (r1, 3);
(* r1 @ frozen int * r2 = r1 *) val x2 = get r2
(* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2)
(* r1 @ frozen int * r2 = r1 * x2 @ int
* rs @ (=r1, =r2) *)
(* rs @ (frozen int, frozen int) *)
. .
7 / 83
Usage
. writable...
new
get
.
open woref
val r1 = new () (* r1 @ writable *)
(* r1 @ frozen int * r2 = r1 *) val x2 = get r2
(* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2)
(* r1 @ frozen int * r2 = r1 * x2 @ int
* rs @ (=r1, =r2) *)
(* rs @ (frozen int, frozen int) *)
. .
7 / 83
Usage
. writable...
new
get
.
open woref
val r1 = new () (* r1 @ writable *) val r2 = r1
(* r1 @ writable * r2 = r1 *)
(* r1 @ frozen int * r2 = r1 *) val x2 = get r2
(* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2)
(* r1 @ frozen int * r2 = r1 * x2 @ int
* rs @ (=r1, =r2) *)
(* rs @ (frozen int, frozen int) *)
. .
7 / 83
Usage
. writable...
new
.
frozen .
set
get
.
open woref
val r1 = new () (* r1 @ writable *) val r2 = r1
(* r1 @ writable * r2 = r1 *) val () = set (r1, 3);
(* r1 @ frozen int * r2 = r1 *)
val x2 = get r2
(* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2)
(* r1 @ frozen int * r2 = r1 * x2 @ int
* rs @ (=r1, =r2) *)
(* rs @ (frozen int, frozen int) *)
. .
7 / 83
Usage
. writable...
new
.
frozen .
set
.
get
.
open woref
val r1 = new () (* r1 @ writable *) val r2 = r1
(* r1 @ writable * r2 = r1 *) val () = set (r1, 3);
(* r1 @ frozen int * r2 = r1 *) val x2 = get r2
(* r1 @ frozen int * r2 = r1 * x2 @ int *)
val rs = (r1, r2)
(* r1 @ frozen int * r2 = r1 * x2 @ int
* rs @ (=r1, =r2) *)
(* rs @ (frozen int, frozen int) *)
. .
7 / 83
Usage
. writable...
new
.
frozen .
set
.
get
.
open woref
val r1 = new () (* r1 @ writable *) val r2 = r1
(* r1 @ writable * r2 = r1 *) val () = set (r1, 3);
(* r1 @ frozen int * r2 = r1 *) val x2 = get r2
(* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2)
(* r1 @ frozen int * r2 = r1 * x2 @ int
* rs @ (=r1, =r2) *)
(* rs @ (frozen int, frozen int) *)
. .
7 / 83
Usage
. writable...
new
.
frozen .
set
.
get
.
open woref
val r1 = new () (* r1 @ writable *) val r2 = r1
(* r1 @ writable * r2 = r1 *) val () = set (r1, 3);
(* r1 @ frozen int * r2 = r1 *) val x2 = get r2
(* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2)
(* r1 @ frozen int * r2 = r1 * x2 @ int
* rs @ (=r1, =r2) *)
(* rs @ (frozen int, frozen int) *)
. .
7 / 83
. writable....
frozen .
set
.
get
. val r1 = new ()
(* r1 @ writable *) val r2 = r1
(* r1 @ writable * r2 = r1 *) val () = set (r1, 3);
(* r1 @ frozen int * r2 = r1 *) val x2 = get r2
(* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2)
(* r1 @ frozen int * r2 = r1 * x2 @ int
* rs @ (=r1, =r2) *)
(* rs @ (frozen int, frozen int) *)
. .
Demo!
7 / 83
Mezzo: design principles
8 / 83
Like a program logic, the static discipline isflow-sensitive.
• Acurrent(set of)permission(s) existsat each program point.
• Differentpermissions exist at different points.
Permissions do not exist at runtime.
9 / 83
Thus, there is no such thing asthetype of a variablex. Instead,
• at each program pointin the scope ofx,
• there may bezero, one, or morepermissions to usex
in certain ways.
10 / 83
Permissions havelayoutandownershipreadings.
• e.g.,r @ writable
x @ tdescribes theshape and extentof a heap fragment, rooted atx, and describes certainaccess rightsfor it.
“To know aboutx” is “to have access tox” is “to ownx”.
11 / 83
Every permission is either duplicable or affine.
At first,
• Immutabledata isduplicable, i.e., shareable.
• Mutabledata isaffine, i.e., uniquely owned.
• Mutable data can become immutable; not the converse.
12 / 83
• Writinglet x = y in ... gives rise to an equationx = y.
• It is a permission: x @ =y, where=yis asingleton type.
• In its presence,x @ tandy @ tare interconvertible.
• Thus,any name is as good as any other.
• The same idea applies tolet x = xs.head in ....
13 / 83
A value can be copied (always). No permission is required.
(* empty *)
let y = (x, x) in (* y @ (=x, =x) *)
14 / 83
Value ̸ = permission
A duplicable permissioncanbe copied. This is implicit.
(* x @ int *) let y = (x, x) in
(* x @ int * y @ (=x, =x) *)
15 / 83
A duplicable permissioncanbe copied. This is implicit.
(* x @ int *) let y = (x, x) in
(* x @ int * y @ (=x, =x) *) (* x @ int * y @ (int, int) *)
15 / 83
Value ̸ = permission
An affine permissioncannotbe copied.
(* x @ ref int *) let y = (x, x) in
(* x @ ref int * y @ (=x, =x) *)
16 / 83
An affine permissioncannotbe copied.
(* x @ ref int *) let y = (x, x) in
(* x @ ref int * y @ (=x, =x) *)
assert y @ (ref int, ref int) (* WRONG! *)
In other words, mutable data cannot be shared.
16 / 83
• x @ list intis duplicable: read access can be shared.
• x = yis duplicable: equalities are forever.
• x @ mlist intandx @ list (ref int)are affine: they give exclusive access to part of the heap.
17 / 83
x @ ref int * y @ ref intimpliesxandyare distinct.
Conjunction isseparatingat mutable data.
z @ (t, u)meansz @ (=x, =y) * x @ t * y @ u, forx,yfresh.
Hence, product is separating.
18 / 83
The same principle applies to records.
Hence,list (ref int)denotes a list ofdistinctreferences.
Mutable data must betree-structured.
• thoughx @ ref (=x)can be written and constructed.
19 / 83
Mezzo: motivation
20 / 83
The types of OCaml, Haskell, Java, C#, etc.:
• describe thestructureof data,
• but do not distinguishtreesandgraphs,
• and do not control who haspermissionto read or write.
21 / 83
Could a more ambitious static discipline:
• rule outmore programming errors,
• andenablenew programming idioms,
• while remaining reasonablysimpleandflexible?
22 / 83
The uniqueness of read/write permissions:
• rules out, or helps rule out, several categories of errors:
• data races;
• representation exposure;
• violations of object protocols.
• allowsthe type of an object to vary with time, which enables:
• explicit memory re-use;
• gradual initialization;
• the description of object protocols.
23 / 83
This discipline is restrictive.
Fortunately,
• there isno restrictionon the use of immutable data;
• there areseveral waysof sharing mutable data:
• (static) nesting & regions;
• (dynamic) adoption & abandon;
• (dynamic) locks.
24 / 83
A few desirable idioms become clumsy or downright impossible.
• e.g., temporarily borrowing anaffineelement from a container (an array; a region; a user-defined data structure; …).
Work-arounds: see previous slide.
25 / 83
Write-once references: interface & implementation
26 / 83
A usage protocol can be described in a module signature:
• Astateis a (user-defined) type.
• Atransitionis a (user-defined) function.
27 / 83
This protocol has two states and four transitions.
abstract writable ..
abstract frozen a ..
fact duplicable (frozen a) ..
val new: () -> writable ..
val set: [a] (consumes r:..writable,.. x: a | duplicable..a)
-> (| r @ frozen a) ..
val get: [a] frozen a -> a ..
.
28 / 83
This protocol has two states and four transitions.
abstract writable ..
abstract frozen a ..
fact duplicable (frozen a) ..
val new: () -> writable ..
val set: [a] (consumes r:..writable,.. x: a | duplicable..a)
-> (| r @ frozen a) ..
val get: [a] frozen a -> a ..
. .
a state
28 / 83
This protocol has two states and four transitions.
abstract writable ..
abstract frozen a ..
fact duplicable (frozen a) ..
val new: () -> writable ..
val set: [a] (consumes r:..writable,.. x: a | duplicable..a)
-> (| r @ frozen a) ..
val get: [a] frozen a -> a ..
. .
another state
28 / 83
This protocol has two states and four transitions.
abstract writable ..
abstract frozen a ..
fact duplicable (frozen a) ..
val new: () -> writable ..
val set: [a] (consumes r:..writable,.. x: a | duplicable..a)
-> (| r @ frozen a) ..
val get: [a] frozen a -> a ..
. .
implicit transition from frozen to frozen * frozen
28 / 83
This protocol has two states and four transitions.
abstract writable ..
abstract frozen a ..
fact duplicable (frozen a) ..
val new: () -> writable ..
val set: [a] (consumes r:..writable,.. x: a | duplicable..a)
-> (| r @ frozen a) ..
val get: [a] frozen a -> a ..
. .
explicit transition into writable
28 / 83
This protocol has two states and four transitions.
abstract writable ..
abstract frozen a ..
fact duplicable (frozen a) ..
val new: () -> writable ..
val set: [a] (consumes r:..writable,.. x: a | duplicable..a)
-> (| r @ frozen a) ..
val get: [a] frozen a -> a ..
. .
set requires r (dynamic) and r @ writable (static)
28 / 83
This protocol has two states and four transitions.
abstract writable ..
abstract frozen a ..
fact duplicable (frozen a) ..
val new: () -> writable ..
val set: [a] (consumes r:..writable,.. x: a | duplicable..a)
-> (| r @ frozen a) ..
val get: [a] frozen a -> a ..
. .
consumes keyword means r @ writable NOT returned
28 / 83
This protocol has two states and four transitions.
abstract writable ..
abstract frozen a ..
fact duplicable (frozen a) ..
val new: () -> writable ..
val set: [a] (consumes r:..writable,.. x: a | duplicable..a)
-> (| r @ frozen a) ..
val get: [a] frozen a -> a ..
. .
duplicable a is a permission
28 / 83
This protocol has two states and four transitions.
abstract writable ..
abstract frozen a ..
fact duplicable (frozen a) ..
val new: () -> writable ..
val set: [a] (consumes r:..writable,.. x: a | duplicable..a)
-> (| r @ frozen a) ..
val get: [a] frozen a -> a ..
. .
explicit transition from writable to frozen
28 / 83
This protocol has two states and four transitions.
abstract writable ..
abstract frozen a ..
fact duplicable (frozen a) ..
val new: () -> writable ..
val set: [a] (consumes r:..writable,.. x: a | duplicable..a)
-> (| r @ frozen a) ..
val get: [a] frozen a -> a ..
. .
get r requires r @ frozen a
28 / 83
Writable { contents: () } data frozen a =
Frozen { contents:..(a | duplicable a) }
val new () : writable = Writable { contents = () }
val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =
..r.contents <- x; ..
tag of r <- Frozen (* this is a no-op *)..
val get [a] (r: frozen a) : a = r.contents
.
29 / 83
Writable { contents: () } data frozen a =
Frozen { contents:..(a | duplicable a) }
val new () : writable = Writable { contents = () }
val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =
..r.contents <- x; ..
tag of r <- Frozen (* this is a no-op *)..
val get [a] (r: frozen a) : a = r.contents
. .
29 / 83
Writable { contents: () } data frozen a =
Frozen { contents:..(a | duplicable a) }
val new () : writable = Writable { contents = () }
val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =
..r.contents <- x; ..
tag of r <- Frozen (* this is a no-op *)..
val get [a] (r: frozen a) : a = r.contents
. .
29 / 83
Writable { contents: () } data frozen a =
Frozen { contents:..(a | duplicable a) }
val new () : writable = Writable { contents = () }
val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =
..r.contents <- x; ..
tag of r <- Frozen (* this is a no-op *)..
val get [a] (r: frozen a) : a = r.contents
. .
29 / 83
Writable { contents: () } data frozen a =
Frozen { contents:..(a | duplicable a) }
val new () : writable = Writable { contents = () }
val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =
..r.contents <- x; ..
tag of r <- Frozen (* this is a no-op *)..
val get [a] (r: frozen a) : a = r.contents
. .
29 / 83
Writable { contents: () } data frozen a =
Frozen { contents:..(a | duplicable a) }
val new () : writable = Writable { contents = () }
val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =
..r.contents <- x; ..
tag of r <- Frozen (* this is a no-op *)..
val get [a] (r: frozen a) : a = r.contents
. .
29 / 83
Writable { contents: () } data frozen a =
Frozen { contents:..(a | duplicable a) }
val new () : writable = Writable { contents = () }
val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =
..r.contents <- x; ..
tag of r <- Frozen (* this is a no-op *)..
val get [a] (r: frozen a) : a = r.contents
. .
29 / 83
Writable { contents: () } data frozen a =
Frozen { contents:..(a | duplicable a) }
val new () : writable = Writable { contents = () }
val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =
..r.contents <- x; ..
tag of r <- Frozen (* this is a no-op *)..
val get [a] (r: frozen a) : a = r.contents
. .
29 / 83
Writable { contents: () } data frozen a =
Frozen { contents:..(a | duplicable a) }
val new () : writable = Writable { contents = () }
val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =
..r.contents <- x; ..
tag of r <- Frozen (* this is a no-op *)..
val get [a] (r: frozen a) : a = r.contents
. .
29 / 83
Algebraic data structures Principles
Computing the length of a list Melding mutable lists
Concatenating immutable lists
Sharing mutable data Conclusion
30 / 83
Principles
31 / 83
The algebraic data type of immutable lists is defined as in ML:
data list a =
| Nil
| Cons { head: a; tail: list a }
32 / 83
To define a type of mutable lists, one adds a keyword:
data mutable mlist a =
| MNil
| MCons { head: a; tail: mlist a }
33 / 83
For instance,
• x @ list intprovides (read) access to an immutable list of integers, rooted atx.
• x @ mlist intprovides (exclusive, read/write) access to a mutable list of integers atx.
• x @ list (ref int)offers read access to the spine and read/write access to the elements, which are distinct cells.
34 / 83
match xs with
| MNil ->
..
...
| MCons ->
..
let x = xs.head in
..
...
end
In contrast, traditional separation logic hasuntaggedunion.
.
35 / 83
match xs with
| MNil ->
..
...
| MCons ->
..
let x = xs.head in
..
...
end
In contrast, traditional separation logic hasuntaggedunion.
. .
xs @ mlist a
35 / 83
match xs with
| MNil ->
..
...
| MCons ->
..
let x = xs.head in
..
...
end
In contrast, traditional separation logic hasuntaggedunion.
. .
xs @ MNil
35 / 83
match xs with
| MNil ->
..
...
| MCons ->
..
let x = xs.head in
..
...
end
In contrast, traditional separation logic hasuntaggedunion.
. .
xs @ MCons { head: a; tail: mlist a }
35 / 83
match xs with
| MNil ->
..
...
| MCons ->
..
let x = xs.head in
..
...
end
In contrast, traditional separation logic hasuntaggedunion.
. .
xs @ MCons { head: (=h); tail: (=t) }
* h @ a
* t @ mlist a
35 / 83
match xs with
| MNil ->
..
...
| MCons ->
..
let x = xs.head in
..
...
end
In contrast, traditional separation logic hasuntaggedunion.
. .
xs @ MCons { head = h; tail = t }
* h @ a
* t @ mlist a
35 / 83
match xs with
| MNil ->
..
...
| MCons ->
..
let x = xs.head in
..
...
end
In contrast, traditional separation logic hasuntaggedunion.
. .
xs @ MCons { head = h; tail = t }
* h @ a
* t @ mlist a
* x = h
35 / 83
This illustrates two mechanisms:
• A nominal permission can beunfoldedandrefined, yielding a structural permission.
• A structural permission can bedecomposed,
yielding separate permissions for the block and its fields.
These reasoning steps are implicit and reversible.
36 / 83
Computing the length of a list
37 / 83
Here is the type of thelengthfunction for mutable lists.
val length: [a] mlist a -> int
It should be understood as follows:
• lengthrequires one argumentxs,
along with the permissionxs @ mlist a.
• lengthreturns one resultn,
along with the permissionxs @ mlist a * n @ int.
38 / 83
match xs with..
| MNil ->..
accu..
| MCons ->..
length_aux (accu + 1, xs.tail)..
end
val length [a] (xs: mlist a) : int = length_aux (0, xs)
.
39 / 83
match xs with..
| MNil ->..
accu..
| MCons ->..
length_aux (accu + 1, xs.tail)..
end
val length [a] (xs: mlist a) : int = length_aux (0, xs)
. .
initially:
xs @ mlist a
39 / 83
match xs with..
| MNil ->..
accu..
| MCons ->..
length_aux (accu + 1, xs.tail)..
end
val length [a] (xs: mlist a) : int = length_aux (0, xs)
. .
upon entry into the first branch:
xs @ MNil
39 / 83
match xs with..
| MNil ->..
accu..
| MCons ->..
length_aux (accu + 1, xs.tail)..
end
val length [a] (xs: mlist a) : int = length_aux (0, xs)
. .
upon exit of the first branch:
xs @ MNil
39 / 83
match xs with..
| MNil ->..
accu..
| MCons ->..
length_aux (accu + 1, xs.tail)..
end
val length [a] (xs: mlist a) : int = length_aux (0, xs)
. .
upon exit of the first branch:
xs @ mlist a
39 / 83
match xs with..
| MNil ->..
accu..
| MCons ->..
length_aux (accu + 1, xs.tail)..
end
val length [a] (xs: mlist a) : int = length_aux (0, xs)
. .
upon entry into the second branch:
xs @ MCons { head = h; tail = t } h @ a
t @ mlist a
39 / 83
match xs with..
| MNil ->..
accu..
| MCons ->..
length_aux (accu + 1, xs.tail)..
end
val length [a] (xs: mlist a) : int = length_aux (0, xs)
. .
after the call, nothing has changed:
xs @ MCons { head = h; tail = t } h @ a
t @ mlist a
39 / 83
match xs with..
| MNil ->..
accu..
| MCons ->..
length_aux (accu + 1, xs.tail)..
end
val length [a] (xs: mlist a) : int = length_aux (0, xs)
. .
thus, by recombining:
xs @ MCons { head: a; tail: mlist a }
39 / 83
match xs with..
| MNil ->..
accu..
| MCons ->..
length_aux (accu + 1, xs.tail)..
end
val length [a] (xs: mlist a) : int = length_aux (0, xs)
. .
thus, by folding:
xs @ mlist a
39 / 83
• This is atail-recursivefunction, i.e., a loop in disguise.
• As we go, there is alistahead of us and alist segmentbehind us.
• Ownership of the latter isimplicit, i.e., framed out.
Recursive reasoning, iterative execution.
(Now skipping ahead...)
40 / 83
Melding mutable lists
41 / 83
val rec meld_aux [a]
(xs:..MCons { head: a; tail: mlist a },
consumes..ys: mlist a) : () = match xs.tail with
| MNil ->..
xs.tail <- ys..
| MCons ->..
meld_aux (xs.tail, ys)..
end
.
42 / 83
val rec meld_aux [a]
(xs:..MCons { head: a; tail: mlist a },
consumes..ys: mlist a) : () = match xs.tail with
| MNil ->..
xs.tail <- ys..
| MCons ->..
meld_aux (xs.tail, ys)..
end
. .
42 / 83
val rec meld_aux [a]
(xs:..MCons { head: a; tail: mlist a },
consumes..ys: mlist a) : () = match xs.tail with
| MNil ->..
xs.tail <- ys..
| MCons ->..
meld_aux (xs.tail, ys)..
end
. .
42 / 83
val rec meld_aux [a]
(xs:..MCons { head: a; tail: mlist a },
consumes..ys: mlist a) : () = match xs.tail with
| MNil ->..
xs.tail <- ys..
| MCons ->..
meld_aux (xs.tail, ys)..
end
. .
42 / 83
val rec meld_aux [a]
(xs:..MCons { head: a; tail: mlist a },
consumes..ys: mlist a) : () = match xs.tail with
| MNil ->..
xs.tail <- ys..
| MCons ->..
meld_aux (xs.tail, ys)..
end
. .
42 / 83
val rec meld_aux [a]
(xs:..MCons { head: a; tail: mlist a },
consumes..ys: mlist a) : () = match xs.tail with
| MNil ->..
xs.tail <- ys..
| MCons ->..
meld_aux (xs.tail, ys)..
end
. .
42 / 83
val rec meld_aux [a]
(xs:..MCons { head: a; tail: mlist a },
consumes..ys: mlist a) : () = match xs.tail with
| MNil ->..
xs.tail <- ys..
| MCons ->..
meld_aux (xs.tail, ys)..
end
. .
42 / 83
val rec meld_aux [a]
(xs:..MCons { head: a; tail: mlist a },
consumes..ys: mlist a) : () = match xs.tail with
| MNil ->..
xs.tail <- ys..
| MCons ->..
meld_aux (xs.tail, ys)..
end
. .
42 / 83
val rec meld_aux [a]
(xs:..MCons { head: a; tail: mlist a },
consumes..ys: mlist a) : () = match xs.tail with
| MNil ->..
xs.tail <- ys..
| MCons ->..
meld_aux (xs.tail, ys)..
end
. .
42 / 83
val rec meld_aux [a]
(xs:..MCons { head: a; tail: mlist a },
consumes..ys: mlist a) : () = match xs.tail with
| MNil ->..
xs.tail <- ys..
| MCons ->..
meld_aux (xs.tail, ys)..
end
. .
42 / 83
val rec meld_aux [a]
(xs:..MCons { head: a; tail: mlist a },
consumes..ys: mlist a) : () = match xs.tail with
| MNil ->..
xs.tail <- ys..
| MCons ->..
meld_aux (xs.tail, ys)..
end
. .
42 / 83
val meld [a] (consumes xs: mlist a,
consumes ys: mlist a) : mlist a = match xs with
| MNil -> ys
| MCons -> meld_aux (xs, ys); xs end
43 / 83
Concatenating immutable lists
44 / 83
tail • type:MCons { head: a; tail: () }
. .
Cons head..
tail
An isolatedConscell:
• immutable,
• notthe start of a well-formed list,
• type:Cons { head: a; tail = t }
. .
Cons head..
tail
Alistcell:
• immutable,
• the start of a well-formed list,
• typelist a
45 / 83
. .
head..
tail
.
Cons
.
head
.
tail
.
Cons
.
head
.
tail
.
Cons
.
head
.
tail
.
Cons
.
head
.
tail
.
Cons
.
head
.
tail
.
xs
.
ys
46 / 83
. .
head..
tail
.
. head
.
tail
.
Cons
.
head
.
tail
.
Cons
.
head
.
tail
.
Cons
.
head
.
tail
.
Cons
.
head
.
tail
.
Cons
.
head
.
tail
.
xs
.
ys
46 / 83