Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Closure and Environment Compiler Baojian Hua [email protected] 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 If functions don’t nest, then the implementation is simple objects! we’d discuss later 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 val bop = val add = val inc = more abstract: *) fn f => (fn x => (fn y => f (x, y))) fn (op +) 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 Hoisting: a function become a closure: (code, env) 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 /\