Lecture 10
Function-Closure Idioms
Programming Languages
UW CSE 341 - Spring 2023
More idioms
Partial but wide-ranging list:
Currying
Example
let sorted3 = fun x -> fun y -> fun z ->
z >= y && y >= x
let t = ((sorted3 7) 9) 11
Syntactic sugar, part 1
let sorted3 = fun x -> fun y -> fun z ->
z >= y && y >= x
let t = ((sorted3 7) 9) 11
let t = sorted3 7 9 11
Syntactic sugar, part 2
means let [rec] f p1 = fun p2 -> fun p3 -> … -> e
let sorted3 = fun x -> fun y -> fun z ->
z >= y && y >= x
let sorted3 x y z = z >= y && y >= x
let t = sorted3 7 9 11
Final version
As elegant syntactic sugar (even fewer characters than tupling) for:
let sorted3 x y z = z >= y && y >= x
let t = sorted3 7 9 11
let sorted3 = fun x -> fun y -> fun z ->
z >= y && y >= x
let t = ((sorted3 7) 9) 11
Curried fold
A more useful example and a call to it (will improve call next)
let rec fold_left f acc xs =
match xs with
| [] -> acc
| x :: xs' -> fold_left f (f acc x) xs'
let sum_bad_style xs =
fold_left (fun acc x -> acc + x) 0 xs
More unnecessary function wrapping
As we already know, fold_left (fun acc x -> acc + x) 0
evaluates to a closure that given xs, evaluates the match-expression with f bound to (fun acc x -> acc + x) and acc bound to 0
let fold_left f acc xs =
match xs with
| [] -> acc
| x :: xs' -> fold_left f (f acc x) xs'
let sum_bad_style xs =
fold_left (fun acc x -> acc + x) 0 xs
More unnecessary function wrapping
let sum_bad_style xs =
fold_left (fun acc x -> acc + x) 0 xs
let sum_good_style =
fold_left (fun acc x -> acc + x) 0
“Too Few Arguments”
Iterators redux
Partial application is particularly nice for iterators
let remove_negs_meh xs = List.filter (fun x -> x >= 0) xs
let remove_negs = List.filter (fun x -> x >= 0)
let remove_all n = List.filter (fun x -> x <> n)
let remove_zeros = remove_all 0
In fact (!)
let rec append xs ys = (* 'a list -> 'a list -> 'a list *)
match xs with
| [] -> ys
| x::xs' -> x :: append xs' ys
Efficiency / convenience
So which is faster/better: tupling or currying multiple-arguments?
The Value Restriction Appears ☹
If you use partial application to create a polymorphic function, it may not work due to the value restriction
More idioms
Partial but wide-ranging list:
Combine functions
Canonical example is function composition:
let compose f g = fun x -> f (g x)
let (%) = compose
Example
Third version “best” using our infix function
let sqrt_of_abs i = sqrt (float_of_int (abs i))
let sqrt_of_abs i = (sqrt % float_of_int % abs) i
let sqrt_of_abs = sqrt % float_of_int % abs
Left-to-right or right-to-left
As in math, function composition is “right to left”
“Pipelines” of functions are common in functional programming and many programmers prefer left-to-right
let sqrt_of_abs = sqrt % float_of_int % abs
let (|>) x f = f x
let sqrt_of_abs i = i |> abs |> float_of_int |> sqrt
Another example
As is often the case with higher-order functions, the types hint at what the function does:
('a -> 'b option) -> ('b -> 'c option)
->
'a -> 'c option
CSE341: Programming Languages
20
Spring 2019
let pipeline_option f g =
fun x ->
match f x with
| None -> None
| Some y -> g y
More higher-order functions
It is easy to write higher-order wrapper functions
let curried_of_paired f x y = f (x, y)
let paired_of_curried f (x, y) = f x y
let swap_tupled f (x, y) = f (y, x)
let swap_curried f x y = f y x
More idioms
Partial but wide-ranging list:
Digression: OCaml has (separate) mutation
References
References example
x
z
y
let x = ref 42 (* : int ref *)
let y = ref 42
let z = x
let _ = x := 43
let w = (!y) + (!z) (* 85 *)
(* x + 1 does not type-check *)
Callbacks
A common idiom: Library takes functions to apply later, when an event occurs – examples:
A library may accept multiple callbacks
Mutable state
While it’s not absolutely necessary, mutable state is reasonably appropriate here...
… We really do want the “collection of callbacks registered” to change when a function to register a callback is called
Example call-back library
Library maintains mutable state for “what callbacks are there” and provides a function for accepting new ones
So the entire public library interface would be the function for registering new callbacks:
val onKeyEvent : (int -> unit) -> unit
(Because callbacks are executed for side-effect, they may also need mutable state)
Library implementation
let callbacks : (int -> unit) list ref = ref []
let on_key_event f =
callbacks := f :: !callbacks
let do_key_event i =
List.iter (fun f -> f i) !callbacks
Clients
Can only register an int -> unit, so if any other data is needed, must be in closure’s environment
let times_pressed = ref 0
let _ = on_key_event (fun _ ->
times_pressed := !times_pressed + 1
let print_if_pressed i =
on_key_event (fun j ->
if i = j
then print_endline ("pressed " ^ string_of_int i)
else ())
More idioms
Partial but wide-ranging list:
Optional: Implementing an ADT
As our last idiom, closures can implement abstract data types
See code for an implementation of immutable integer sets with operations insert, member, and size
The actual code is advanced/clever/tricky, but has no new features