CMSC 330: Organization of Programming Languages
Closures (Implementing Higher Order Functions)
CMSC 330 - Spring 2020 1 Returning Functions as Results
In OCaml you can pass functions as arguments • to map, fold, etc. and you can return functions as results # let pick_fn n = let plus_three x = x + 3 in let plus_four x = x + 4 in if n > 0 then plus_three else plus_four val pick_fn : int -> (int->int) =
Consider a rewriting of the prior code (above)
let pick_fn n = if n > 0 then (fun x->x+3) else (fun x->x+4) Here’s another version
let pick_fn n = (fun x -> if n > 0 then x+3 else x+4) … the shorthand for which is just
let pick_fn n x = if n > 0 then x+3 else x+4 I.e., a multi-argument function!
CMSC 330 - Spring 2020 3 Currying
We just saw a way for a function to take multiple arguments! • I.e., no separate concept of multi-argument functions – can encode one as a function that takes a single argument and returns a function that takes the rest
This encoding is called currying the function • Named after the logician Haskell B. Curry • But Schönfinkel and Frege discovered it Ø So maybe it should be called Schönfinkelizing or Fregging
CMSC 330 - Spring 2020 4 Curried Functions In OCaml
OCaml syntax defaults to currying. E.g., let add x y = x + y • is identical to all of the following: let add = (fun x -> (fun y -> x + y)) let add = (fun x y -> x + y) let add x = (fun y -> x+y) Thus: • add has type int -> (int -> int) • add 3 has type int -> int Ø add 3 is a function that adds 3 to its argument • (add 3) 4 = 7 This works for any number of arguments CMSC 330 - Spring 2020 5 Syntax Conventions for Currying
Because currying is so common, OCaml uses the following conventions: • -> associates from the right Ø Thus int -> int -> int is the same as Ø int -> (int -> int)
• function application associates from the left Ø Thus add 3 4 is the same as Ø (add 3) 4
CMSC 330 - Spring 2020 7 Quiz 1: Which f definition is equivalent?
let f a b = a / b;;
A. let f b = fun a -> a / b;; B. let f = fun a | b -> a / b;; C. let f (a, b) = a / b;; D. let f = fun a -> (fun b -> a / b);;
CMSC 330 - Spring 2020 8 Quiz 1: Which f definition is equivalent?
let f a b = a / b;;
A. let f b = fun a -> a / b;; B. let f = fun a | b -> a / b;; C. let f (a, b) = a / b;; D. let f = fun a -> (fun b -> a / b);;
CMSC 330 - Spring 2020 9 Quiz 2: What is enabled by currying?
A. Passing functions as arguments B. Passing only a portion of the expected arguments C. Naming arguments D. Recursive functions
CMSC 330 - Spring 2020 10 Quiz 2: What is enabled by currying?
A. Passing functions as arguments B. Passing only a portion of the expected arguments C. Naming arguments D. Recursive functions
CMSC 330 - Spring 2020 11 Multiple Arguments, Partial Application
Another way you could encode support for multiple arguments is using tuples • let f (a,b) = a / b (* int*int -> int *) • let f a b = a / b (* int-> int-> int *)
Is there a benefit to using currying instead? • Supports partial application – useful when you want to provide some arguments now, the rest later • let add a b = a + b;; • let addthree = add 3;; • addthree 4;; (* evaluates to 7 *)
CMSC 330 - Spring 2020 13 Currying is Standard In OCaml
Pretty much all functions are curried • Like the standard library map, fold, etc. • See /usr/local/ocaml/lib/ocaml on Grace Ø In particular, look at the file list.ml for standard list functions Ø Access these functions using List.
OCaml works hard to make currying efficient • Because otherwise it would do a lot of useless allocation and destruction of closures • What are those, you ask? Let’s see …
CMSC 330 - Spring 2020 14 Closure
CMSC 330 - Spring 2020 18 Java Example public class Test{ public void doSomething(){ int a = 10; //must be final Runnable runnable = new Runnable(){ Needed later, public void run(){ makes copy of a int b = a + 1; System.out.println(b); } }; (new Thread(runnable)).start(); //runs later //a = 100; //not allowed } public static void main(String[] args){ Test t = new Test(); t.doSomething(); } }// a=10 is removed from the stack here CMSC 330 - Spring 2020 19 OCaml Example let foo x = let bar y = x + y in bar ;;
foo 10 = ?
(fun y -> x + y) ?
Where is x?
CMSC 330 - Spring 2020 20 Another Example let x = 1 in let f = fun y -> x in let x = 2 in f 0
What does this expression should evaluate to?
A. 1 B. 2
CMSC 330 - Spring 2020 21 Another Example let x = 1 in let f = fun y -> x in let x = 2 in f 0
What does this expression should evaluate to?
A. 1 B. 2
CMSC 330 - Spring 2020 22 Scope
Dynamic scope • The body of a function is evaluated in the current dynamic environment at the time the function is called, not the old dynamic environment that existed at the time the function was defined.
Lexical scope • The body of a function is evaluated in the old dynamic environment that existed at the time the function was defined, not the current environment when the function is called.
CMSC 330 - Spring 2020 23 Closure
let foo x = let x = 1 in let bar y = x + y in let f = fun y -> x in bar ;; let x = 2 in f 0 bar 3 Closure Closure
Function Environment Function Environment
CMSC 330 - Spring 2020 24 Closures Implement Static Scoping
An environment is a mapping from variable names to values • Just like a stack frame
A closure is a pair (f, e) consisting of function code f and an environment e
When you invoke a closure, f is evaluated using e to look up variable bindings
CMSC 330 - Spring 2020 25 Example – Closure 1
let add x = (fun y -> x + y)
(add 3) 4 →
Closure
Function Environment
CMSC 330 - Spring 2020 26 Example – Closure 2
let mult_sum (x, y) = let z = x + y in fun w -> w * z
(mult_sum (3, 4)) 5 →
CMSC 330 - Spring 2020 27 Quiz 3: What is x?
let a = 1;; let a = 0;; let b = 10;; let f () = a + b;; let b = 5;; let x = f ();;
A. 10 B. 1 C. 15 D. Error - variable name conflicts
CMSC 330 - Spring 2020 28 Quiz 3: What is x?
let a = 1;; let a = 0;; let b = 10;; let f () = a + b;; let b = 5;; let x = f ();;
A. 10 B. 1 C. 15 D. Error - variable name conflicts
CMSC 330 - Spring 2020 29 Quiz 4: What is z?
let f x = fun y -> x – y in let g = f 2 in let x = 3 in let z = g 4 in z;; A. 7 B. -2 C. -1 D. Type Error – insufficient arguments
CMSC 330 - Spring 2020 30 Quiz 4: What is z?
let f x = fun y -> x – y in let g = f 2 in let x = 3 in let z = g 4 in z;; A. 7 B. -2 C. -1 D. Type Error – insufficient arguments
CMSC 330 - Spring 2020 31 Quiz 5: What does this evaluate to?
let f x = x+1 in let g = f in g (fun i -> i+1) 1
A. Type Error B. 1 C. 2 D. 3
CMSC 330 - Spring 2020 32 Quiz 5: What does this evaluate to?
let f x = x+1 in let g = f in (g (fun i -> i+1)) 1
A. Type Error – Too many arguments passed to g (application is left associative) B. 1 C. 2 D. 3
CMSC 330 - Spring 2020 33 Higher-Order Functions in C
C supports function pointers
typedef int (*int_func)(int); void app(int_func f, int *a, int n) { for (int i = 0; i < n; i++) a[i] = f(a[i]); } int add_one(int x) { return x + 1; } int main() { int a[] = {5, 6, 7}; app(add_one, a, 3); }
CMSC 330 - Spring 2020 34 Higher-Order Functions in C (cont.)
C does not support closures • Since no nested functions allowed • Unbound symbols always in global scope int y = 1; void app(int(*f)(int), n) { return f(n); } int add_y(int x) { return x + y; } int main() { app(add_y, 2); } CMSC 330 - Spring 2020 35 Higher-Order Functions in C (cont.)
Cannot access non-local variables in C OCaml code let add x y = x + y Equivalent code in C is illegal int (* add(int x))(int) { return add_y; } int add_y(int y) { return x + y; /* error: x undefined */ }
CMSC 330 - Spring 2020 36 Higher-Order Functions in C (cont.)
OCaml code let add x y = x + y Works if C supports nested functions • Not in ISO C, but in gcc; but not allowed to return them
int (* add(int x))(int) { int add_y(int y) { return x + y; } return add_y; } • Does not allocate closure, so x popped from stack and add_y will get garbage (potentially) when called
CMSC 330 - Spring 2020 37 Java 8 Supports Lambda Expressions
Ocaml’s fun (a, b) -> a + b Is like the following in Java 8
(a, b) -> a + b
Java 8 supports closures, and variations on this syntax
CMSC 330 - Spring 2020 38 Java 8 Example public class Calculator { interface IntegerMath { int operation(int a, int b); } public int operateBinary(int a, int b, IntegerMath op) { return op.operation(a, b); } public static void main(String... args) { Calculator myApp = new Calculator(); IntegerMath addition = (a, b) -> a + b; Lambda IntegerMath subtraction = (a, b) -> a - b; expressions System.out.println("40 + 2 = " + myApp.operateBinary(40, 2, addition)); System.out.println("20 - 10 = " + myApp.operateBinary(20, 10, subtraction)); } } CMSC 330 - Spring 2020 39