CSCI 2041

ADVANCED PROGRAMMING PRINCIPLES

Programmer-Defined Types I: records and enumerated unions

OCaml types

So far we’ve seen:

Simple types:

Function types:

Product types:

List types:

int, float, string, char, bool, unit

τ1 -> τ2

'a * 'b, 'a * 'b * 'c, …

'a list

Most computing problems involve data with more interesting structure: trees, graphs, matrices, vectors, tables, documents…

Type Declaration

The OCaml type system lets us create new types that represent the operations and relationships in the data, and new values corresponding to these types.

Simplest case: a new name for an existing type

type name = type-expr

Examples:

type jastring = string
type ipair = int * int
type scorelist = (string * int) list

This simple form doesn’t create new values, just a different name for an existing type.

Type abbreviations can be parameterized:

type 'a pair = 'a * 'a

Resulting in a type constructor, which takes a type as an argument, and produces a type:

int pair, string pair, (float list) pair

Multiple parameters are separated with commas:

type ('k, 'v) pair = 'k * 'v

We still haven’t introduced new types or values…

Record Types

Record types are similar to “structs” or “classes” without inheritance:

type rname = { f1 : τ1 ; ... ; f𝓃 : τ𝓃 }

Records are “product” types, but components of the tuple (fields) have names.

Example: phone book records

type phone_rec = { name : string; phone_number: string }
let nick = { name = "Dr. Nick" ; phone_number = "555-555-1212" }
nick.phone_number

Example: inventory record

type inv = { upc : string ; qty : int ; price : float}
let mystery_item = { upc = "072179235989"; qty = 123; price = 39.99}
mystery_item.price *. 1.07

Using records

Records can be destructured / matched by patterns:

type player = { name: string ; exp: int ; hp: int ; weapons: string list}
let level p = match p with { exp = 1 } -> 0 | _ -> 1 + (p.exp / 10)

We can construct a new record from an old one:

let levelup p epoints = {p with exp = p.exp + epoints}

Field names can only be used once per module:

type magical_item = { name: string; effect: player -> player }
let player_name p = p.name

Type Unions

Enumerated type unions

An enumerated type declaration introduces a new type and new values:

type name = Value1 | Value2 | ... | Value𝓀

For example:

type size = Tall | Grande | Venti

Introduces the new type size, and three values* that only belong to the type size: Tall, Grande, Venti.

* technically: value constructors. More next time…

We can use these values in pattern matching, just like built-in constants, e.g.

# let enough_caffeine sz = match sz with
  | Tall -> false
  | Grande -> false
  | Venti -> true ;;

val enough_caffeine : size -> bool
let size_to_oz sz = match sz with
  | Tall -> 12
  | Grande -> 16
  | Venti -> 20

Example

Define an enumerated type hogwarts_house, and a function that maps a hogwarts_house to a string describing its mascot.

type hogwarts_house = Gryffindor |
  Slytherin | Hufflepuff | Ravenclaw
let house_mascot h = match h with
  | Gryffindor -> "Lion"
  | Slytherin -> "Serpent"
  | Hufflepuff -> "Badger"
  | Ravenclaw -> "Eagle"

Comparisons

Values of an enumerated type can be used with =, <>, <, >

Gryffindor < Ravenclaw;;
Hufflepuff > Slytherin;;

Results are dependent on the order of the type declaration, and shouldn’t be relied on.

Define hcompare : hogwarts_house -> hogwarts_house -> int that returns:

  • -1 if h1 < h2,
  • 0 if h1 = h2 and
  • +1 if h1 > h2,

where Hufflepuff < Gryffindor < Ravenclaw < Slytherin:

let rec hcompare h1 h2 = match (h1,h2) with
| _ when h1=h2 -> 0
| (Hufflepuff, _) -> -1
| (Gryffindor, Ravenclaw) | (Gryffindor, Slytherin) -> -1
| (Ravenclaw, Slytherin) -> -1
| _ -> -(hcompare h2 h1)

cs2041.org

// reveal.js plugins