Lecture 05
Records, Variants, Pattern Matching
Programming Languages
UW CSE 341 - Winter 2021
Updates 2021-01-10
Updates 2021-01-15
Data
Building Types
Building Types
Building Types
Show me your flowchart and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won't usually need your flowchart; it'll be obvious.
-- Fred Brooks in The Mythical Man Month (1975)
Building Types
Show me your flowchart and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won't usually need your flowchart; it'll be obvious.
-- Fred Brooks in The Mythical Man Month (1975)
Good programmers worry more about data structures and their relationships and less about code.
Anatomy of a Datatype in ANY Language
Anatomy of a Datatype in ANY Language
The ability to nest and mix-and-match makes these building blocks even more powerful!
😍 Composition 😍
Example Types From Building Blocks
Example Types From Building Blocks
Types provide a compact language for expressing the “shape” of data!
Today: Records and Variants
Records : Define
Syntax: type t = { f1 : t1; … ; fN : tN }
Adds new (record) type t and fields f1, …, fN
Must define a record type before you can build or use
Records : Define
Syntax: type t = { f1 : t1; … ; fN : tN }
Adds new (record) type t and fields f1, …, fN
Must define a record type before you can build or use
There are ≈ 11 name spaces in OCaml 😳
Records : Build
Syntax: { f1 : e1; … ; fN : eN }
Type Checking
Evaluation
Records : Build
Syntax: { f1 : e1; … ; fN : eN }
Type Checking
Evaluation
Elements separated by semicolons “ ; ” ⚠️
Get very weird error messages otherwise ⚠️
Records : Build
Syntax: { f1 : e1; … ; fN : eN }
Type Checking
Evaluation
Records of values are values.
Elements separated by semicolons “ ; ” ⚠️
Get very weird error messages otherwise ⚠️
Records : Use
Syntax: e.f
Type Checking
Evaluation
Records : Use
Syntax: e.f
Type Checking
Evaluation
We can access any part of a record!
Record Example
type lava_lamp =
{ height : float
; color_liquid : string
; color_lava : string }
let my_lamp =
{ height = 13.5 +. 1.0
; color_liquid = "bl" ^ "ue"
; color_lava = "" ^ "green" ^ "" }
let a = my_lamp.height
let b = my_lamp.color_liquid
let c = my_lamp.color_lava
Record Example
type lava_lamp =
{ height : float
; color_liquid : string
; color_lava : string }
let my_lamp =
{ height = 13.5 +. 1.0
; color_liquid = "bl" ^ "ue"
; color_lava = "" ^ "green" ^ "" }
let a = my_lamp.height
let b = my_lamp.color_liquid
let c = my_lamp.color_lava
type lava_lamp = {
height : float;
color_liquid : string;
color_lava : string;
}
val my_lamp : lava_lamp = {
height = 14.5;
color_liquid = "blue";
color_lava = "green"
}
val a : float = 14.5
val b : string = "blue"
val c : string = "green"
Record Example
type lava_lamp =
{ height : float
; color_liquid : string
; color_lava : string }
let my_lamp =
{ height = 13.5 +. 1.0
; color_liquid = "bl" ^ "ue"
; color_lava = "" ^ "green" ^ "" }
let a = my_lamp.height
let b = my_lamp.color_liquid
let c = my_lamp.color_lava
type lava_lamp = {
height : float;
color_liquid : string;
color_lava : string;
}
val my_lamp : lava_lamp = {
height = 14.5;
color_liquid = "blue";
color_lava = "green"
}
val a : float = 14.5
val b : string = "blue"
val c : string = "green"
Style: keep field names unique for each record type
Can get really confusing error message otherwise ⚠️
Tuples vs. Records
Today: Records and Variants
Variant Examples : Enumerations (SI Units)
type si_unit =
| Second | Meter | Kilogram | Ampere
| Kelvin | Mole | Candela
let string_of_si_unit (u: si_unit) : string =
match u with
| Second -> "second"
| Meter -> "meter"
| Kilogram -> "kilogram"
| Ampere -> "ampere"
| Kelvin -> "kelvin"
| Mole -> "mole"
| Candela -> "candela"
let s = string_of_si_unit Ampere
Variant Examples : Enumerations (SI Units)
type si_unit =
| Second | Meter | Kilogram | Ampere
| Kelvin | Mole | Candela
let string_of_si_unit (u: si_unit) : string =
match u with
| Second -> "second"
| Meter -> "meter"
| Kilogram -> "kilogram"
| Ampere -> "ampere"
| Kelvin -> "kelvin"
| Mole -> "mole"
| Candela -> "candela"
let s = string_of_si_unit Ampere
type si_unit =
Second | Meter | Kilogram | Ampere |
Kelvin | Mole | Candela
val string_of_si_unit :
si_unit -> string = <fun>
val s : string = "ampere"
Variant Examples : Enumerations (SI Units)
type si_unit =
| Second | Meter | Kilogram | Ampere
| Kelvin | Mole | Candela
let string_of_si_unit (u: si_unit) : string =
match u with
| Second -> "second"
| Meter -> "meter"
| Kilogram -> "kilogram"
| Ampere -> "ampere"
| Kelvin -> "kelvin"
| Mole -> "mole"
| Candela -> "candela"
let s = string_of_si_unit Ampere
type si_unit =
Second | Meter | Kilogram | Ampere |
Kelvin | Mole | Candela
val string_of_si_unit :
si_unit -> string = <fun>
val s : string = "ampere"
First pipe (“|”) is optional
Use with pattern match
Build with constructor
Variant Examples : Enumerations (SI Prefixes)
type si_prefix =
| Giga | Mega | Kilo
| Milli | Micro | Nano
let scale (p : si_prefix) : float =
match p with
| Giga -> 1e9
| Mega -> 1e6
| Kilo -> 1e3
| Milli -> 1e-3
| Micro -> 1e-6
| Nano -> 1e-9
let s = scale Giga
type si_prefix =
Giga | Mega | Kilo |
Milli | Micro | Nano
val scale :
si_prefix -> float = <fun>
val s : float = 1000000000.
Variant Examples : Shapes From Points
type pt = float * float
type shape =
| Circle of pt * float
| Rectangle of pt * pt
| Triangle of pt * pt * pt
let area (s : shape) : float = match s with
| Circle (center, radius) ->
Float.pi *. radius *. radius
| Rectangle ((x1, y1), (x2, y2)) ->
Float.abs ((x2 -. x1) *. (y2 -. y1))
| Triangle ((x1, y1), (x2, y2), (x3, y3)) ->
let a = x1 *. (y2 -. y3) in
let b = x2 *. (y3 -. y1) in
let c = x3 *. (y1 -. y2) in
Float.abs ((a +. b +. c) /. 2.0)
type pt = float * float
type shape =
Circle of pt * float
| Rectangle of pt * pt
| Triangle of pt * pt * pt
val area : shape -> float = <fun>
val a : float = 4.5
let a = area (Triangle ( (0.0, 0.0)
, (0.0, 3.0)
, (3.0, 0.0)))
Variant Examples : Shapes From Points
type pt = float * float
type shape =
| Circle of pt * float
| Rectangle of pt * pt
| Triangle of pt * pt * pt
let area (s : shape) : float = match s with
| Circle (center, radius) ->
Float.pi *. radius *. radius
| Rectangle ((x1, y1), (x2, y2)) ->
Float.abs ((x2 -. x1) *. (y2 -. y1))
| Triangle ((x1, y1), (x2, y2), (x3, y3)) ->
let a = x1 *. (y2 -. y3) in
let b = x2 *. (y3 -. y1) in
let c = x3 *. (y1 -. y2) in
Float.abs ((a +. b +. c) /. 2.0)
type pt = float * float
type shape =
Circle of pt * float
| Rectangle of pt * pt
| Triangle of pt * pt * pt
val area : shape -> float = <fun>
val a : float = 4.5
let a = area (Triangle ( (0.0, 0.0)
, (0.0, 3.0)
, (3.0, 0.0)))
Constructors just “tag” their arguments
Variants : Define
Syntax: type t = C1 of t1 | … | CN of tN
Adds new (variant) type t and constructors C1, …, CN
Must define a variant type before you can build or use
Variants : Define
Syntax: type t = C1 of t1 | … | CN of tN
Adds new (variant) type t and constructors C1, …, CN
Must define a variant type before you can build or use
Constructor parameters are optional.
Variants : Define
Syntax: type t = C1 of t1 | … | CN of tN
Adds new (variant) type t and constructors C1, …, CN
Must define a variant type before you can build or use
Constructor parameters are optional.
Constructors that don’t take any arguments are just values of their type.
Variants : Build
Syntax: C e
Type Checking
Evaluation
Variants : Build
Syntax: C e
Type Checking
Evaluation
Values “tagged with a constructor” are values!
Variants : Build
Syntax: C e
Type Checking
Evaluation
Values “tagged with a constructor” are values!
a “tag” that can make some kind of data also belong to t.
Variants : Build (Constructor with No Arguments)
Syntax: C
Type Checking
Evaluation
Variants : Build (Constructor with No Arguments)
Syntax: C
Type Checking
Evaluation
Again, this only for constructors with no arguments!
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
A pattern is one of (syntax)
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
A pattern is one of (syntax)
Patterns are NOT expressions!
(though they kinda look like expressions)
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Like conditionals, all branches must have the same type t!
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Need to define:
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Need to define:
Notice pattern (😂): we keep breaking language features down into smaller pieces that are easier to precisely define!
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Need to define:
Rules for matching pattern p against value v of type tV:
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Need to define:
Rules for matching pattern p against value v of type tV:
Match for compound patterns
is defined recursively !
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Need to define:
Rules for matching pattern p against value v of type tV:
Must have same length,
and all sub-patterns must match!
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Need to define:
Rules for matching pattern p against value v of type tV:
Must be same constructor C !
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Need to define:
Rules for matching pattern p against value v of type tV:
Patterns can only be one of a few things:
For any pattern and value of any type:
These are (basically) all the rules!
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Need to define:
Rules for matching pattern p against value v of type tV:
Patterns can only be one of a few things:
For any pattern and value of any type:
These are (basically) all the rules!
Some extensions that “work as expected”:
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Need to define:
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Need to define:
Rules for type checking case P -> e :
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Need to define:
Rules for type checking case P -> e :
In case P -> e, the scope of bindings added by matching is only for the expression e.
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Need to define:
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Type Checking
Need to define:
And it gets better! OCaml will also check that:
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Evaluation
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Evaluation
Try patterns in order till one matches
Variants : Use
Syntax: match e with P1 -> e1 | … | PN -> eN
Evaluation
Try patterns in order till one matches
Semantics pretty easy once we carefully defined what it means to match a pattern 😎
Variants : Polymorphism
Variant Examples : Our Own “Option” Type
type 'a maybe =
| Nothing
| Just of 'a
let is_nothing (m : 'a maybe) : bool =
match m with
| Nothing -> true
| Just _ -> false
let maybe_get (m : 'a maybe) : 'a =
match m with
| Nothing -> failwith "maybe_get: Nothing"
| Just a -> a
type 'a maybe =
Nothing | Just of 'a
val is_nothing :
'a maybe -> bool = <fun>
val maybe_get :
'a maybe -> 'a = <fun>
Variant Examples : Our Own “List” Type
type 'a llist =
| Nil
| Cons of 'a * 'a llist
let rec llength (ll : 'a llist) : int =
match ll with
| Nil -> 0
| Cons (x, xs) -> 1 + llength xs
let rec append (ll1 : 'a llist) (ll2 : 'a llist) : 'a llist =
match ll1 with
| Nil -> ll2
| Cons (x, xs) -> Cons (x, append xs ll2)
type 'a llist =
Nil | Cons of 'a * 'a llist
val llength :
'a llist -> int = <fun>
val append :
'a llist -> 'a llist -> 'a llist = <fun>
Variant Examples : Parameterizing Multiple Types
type ('a, 'b) either = Left of 'a | Right of 'b
let mk_left (a : 'a) : ('a, 'b) either = Left a
let mk_right (b : 'b) : ('a, 'b) either = Right b
let rec split_lr (ll : ('a, 'b) either list) : 'a list * 'b list =
match ll with
| [] -> ([], [])
| x :: xs ->
let (ls, rs) = split_lr xs in
match x with
| Left a -> (a :: ls, rs)
| Right b -> (ls, b :: rs)
type ('a, 'b) either =
Left of 'a | Right of 'b
val mk_left :
'a -> ('a, 'b) either = <fun>
val mk_right :
'b -> ('a, 'b) either = <fun>
val split_lr :
('a, 'b) either list -> 'a list * 'b list = <fun>
Whew…
(take a breath)
So What?
Today: Records and Variants
Today: Records and Variants
Later in the quarter, we’ll see one-of types in OOP
Programming
Languages
What is a Programming Language?
What is a Programming Language?
We will mostly focus on semantics and idioms
Syntactic Sugar
Syntactic Sugar
Syntactic sugar helps us keep the “core language” small and orthogonal (thus making it easier to understand and implement) while still providing convenient “features” that really just desugar into the core language.