190 likes | 292 Views
Closure and Environment. Compiler Baojian Hua bjhua@ustc.edu.cn. Higher-order functions (HOF). Higher-order functions are not just for call can be passed as arguments can be returned as results can be stored in data structures objects! we ’ d discuss later
E N D
Closure and Environment Compiler Baojian Hua bjhua@ustc.edu.cn
Higher-order functions (HOF) • Higher-order functions are not just for call • can be passed as arguments • can be returned as results • can be stored in data structures • objects! we’d discuss later • If functions don’t nest, then the implementation is simple • a simple code address • e.g., the “function pointer” in C • What about nesting with HOF?
Nesting (* ML code. *) (* Staging. Harper, section 11.5: *) val add = fn x => (fn y => x + y) val inc = add 1 (* fn y => 1 + y *) (* Can be more abstract: *) val bop = fn f => (fn x => (fn y => f (x, y))) val add = fn (op +) val inc = add 1
Nesting // C code. // GNU C extension supports this. But its // implementation is rather limited and incorrect! int (*(add)(int x))(int) { int f (int y) { return x + y; } return f; } // Don’t expect GCC behaves normally, hummmm… int (*inc) (int) = add (1);
Moral • Nested HOL is a key feature in modern functional programming languages • And now has grown into other language • e.g., C# and Java7 • Key observation: function arguments and locals can NOT be reclaimed after the call • fn x => (fn y => x + y) • they may be used in the returned nested function!
Closure • A closure is a data structure to represent a function at runtime • A closure is essentially a heap-allocated struct/tuple containing a code pointer, as well as a (L-)values for the function’s free variables (environment) • The process of converting a function to a closure is called closure conversion
Lambda again e -> n -> x -> \x.e -> e e // or in ML: datatype e = Int of int | Var of string | Lam of string * e | App of e * e
Rules C (n) = n C (x) = #x env C (\x.e) = let fun f (env_t, x) = let val x1 = #x1 env_t … val xn = #xn env_t val env’ = {x=x, x1=x1, …, xn=xn} in C (e) end in (f, env) end C (e1 e2) = C(e1) C(e2)
Example #1 // for code: \x.x // the initial call: C (\x.x) = let fun f (env_t, x) = let val env’ = {x = x} in C (x) end in (f, env) end
Recursive transformation // for code: \x.x // the initial call: C (\x.x) = let fun f (env_t, x) = let val env = {x = x} in #x env end in (f, env) end
Hoisting // for code: \x.x // hoist all code to top-level: fun f (env_t, x) = let val env = {x = x} in #x env end (f, {})
Function call // consider the code: (\x.x) 5 // \x.x as before: fun f (env_t, x) = let val env’ = {x = x} in #x env end (f, env) // Leave it to you to verify the whole becomes: (f, env) 5 // and the call becomes: f (env, 5)
Summary so far • Three steps in closure conversion: • apply the conversion rules to make every function closed • a function become a closure: (code, env) • Hoisting: • all functions at top-level • like those in C • function call become closure application • (code, env) x ==> code (env, x)
Example #2 // code: \x.\y.x+y // conversion: C (\x.\y.x+y) = let fun f1 (env_t, x) = let val env = {x = x} in C (\y.x+y) end in (f1, env) end // Leave to you to finish other steps!
Example #2 // code: \x.\y.x+y // final result: fun f2 (ent_t, y) = let val x = #x ent_t val env = {x=x, y=y} in #x env + #y env end fun f1 (env_t, x) = let val env = {x = x} in (f2, env) end (f1, env)
In picture // for \x.\y.\z.x+y+z f1 /\ f2 f3
Linked vs flatten closure • The flatten model of closure is bad: • it evolves too much “copy” operations • it’s space inefficient • Instead, we could make the closure linked by sharing environment • just as the static link
Linked Environment // revised rules: C (\x.e) = let fun f (env_t, x) = let val env = Cons (env_t, x) in C (e) end in (f, env) end
In picture // for \x.\y.\z.x+y+z f1 /\ f2 f3