CSCI 2041

ADVANCED PROGRAMMING PRINCIPLES

intro Ocaml: tuples and matching

OCaml programs

Expressions

A program is a sequence of expressions that are evaluated one after another.

So far we have seen:

  • primitive types and operators
  • let expressions and conditionals
  • functional values and recursive declarations
  • tuples: let f (b,s) = if b then s else ""

Structured Data

Construct, Destruct

The (a,b) operation constructs a tuple.

Suppose we want to access the first element of a tuple:

let m = ("UMTC", "CSCI", 1200)
let first_of_m = (* what goes here? *)

OCaml programs destructure, or destruct data, using pattern matching:

let first_of_m = match m with (m1,m2,m3) -> m1

Match expression

match input_expr with
  p1 -> e1
| p2 -> e2
| ...


}


clauses

Each match clause has the form pattern -> body:

let grade_points g = match g with
| 'A' -> 4.0
| 'B' -> 3.0
| 'C' -> 2.0

Patterns can be constants, names, wild card _, or constructed from patterns.

How did the definition let pow (b,n) = ... work?

let f arg = body

let f = fun arg -> body

arg -> body

match clause

Another shorthand:

fun x -> match x with (* clauses *)function (* clauses *)
let rec fib = function
| 0 -> 0
| 1 -> 1
| n -> fib (n-1) + fib (n-2)

let rec sumup = function 0 -> 0 | n -> n + (sumup (n-1))

“as” pattern

When a pattern has multiple possibilities we can bind a name to the expression with as:

let rec leet_char = function
'A'..'Z' as c -> leet_char (char_of_int ((int_of_char c) + 32))
| 'l' -> '1'
| 'e' -> '3'
| 't' -> '7'
| s -> s
let imo = function
("Vikings" | "Gophers" | "Twins") as team -> team ^ " rule!"
| losers -> losers ^ " drool!"

Note: match statements (and the function... shorthand) use the first matching pattern:

let rec fib = function
  n -> fib (n-1) + fib (n-2)
| 0 -> 0
| 1 -> 1

We can use patterns to combine multiple checks succinctly, e.g.:

let fizzorbuzz n = match (n mod 3, n mod 5) with
(0,0) -> "FizzBuzz\n"
| (0,_) -> "Fizz\n"
| (_,0) -> "Buzz\n"
| _ -> (string_of_int n)^"\n"

let rec fizzbuzz = function
| n when n <= 100 -> (fizzorbuzz n) ^ (fizzbuzz (n+1))
| 101 -> ""
| _ -> invalid_arg "fizzbuzz"
let rec strnums = function
| (m,n) when m < n -> (string_of_int m) ^ "," ^ (strnums (m+1,n))
| (m,n) when m = n -> string_of_int m
| _ -> ""

(Notice the pattern guardwhen condition”…)

Patterns, patterns, everywhere…

Patterns can also be used anywhere a name might be bound:

  • let (x,y) = (2, "dos") in y
  • let (x,_) = (4, print_string "Hello!\n")
  • let f (0|1 as b) = 1-b
  • let (0 | 1 | 2) = read_int () in 42

Patterns and types

Pattern types

What happens if we evaluate:

  • match "anystring" with 0 -> 1 | _ -> 2 ?
  • (function 0 -> true | _ -> false) 1.0 ?
  • function 0 -> 'a' | 1 -> "b" | n -> n+1 ?
  • function 0 -> 0 | "abracadabra" -> 0 | _ -> 1 ?

In the expression match e with p1 -> e1 | ... | pk -> ek:

  • The expression e and patterns p1pk must all have “compatible” types;
  • The result can be any of e1ek so the types of e1ek must be the same.
  • The type of the expression is the type of the result.

Suppose we compile the following definition:

let first_of_triple (x,_,_) = x

What type will it have?

val first_of_triple : 'a * 'b * 'c -> 'a = <fun>


'a, 'b, and so on are type variables that can match any type, so

  • first_of_triple (1,"uno",1.0) : int
  • first_of_triple ("person","woman","man...") : string

Types involving type variables are called polymorphic.

cs2041.org

// reveal.js plugins