From: "Mauricio Fernández" Date: 2003-05-26T17:30:38+09:00 Subject: Closures, capturing variables and evilness In Ruby, a closure encapsulates the whole environment (aka. binding or FRAME) it is defined in, regardless of the variables it really uses. However, you can only access variables directly if they were defined earlier; otherwise, eval is needed. Questions: 1) are these semantics intentional or an implementation artifact? ie. will the following work in Ruby "forever"? def make_closure p = proc { puts eval("bla") } #... bla = "foo" p end puts make_closure.call 2) why the difference between direct access and eval? 3) if the answer to (1) is "implementation artifact", would the following semantics make sense in the future? def make_closure p = proc { puts eval("bla") } ... bla = "foo" p end make_closure.call # => NameError: undefined local variable or method 'bla' def make_closure p = proc { puts bla } ... bla = "foo" p end make_closure.call # => NameError: undefined local variable or method 'bla' ie. only capture explicitly referenced vars declared previously, which disallows use of eval to get captured locals. This allows the use of lambda lifting or some other optimization (and it *would* require the implementation to change if an exception is to be raised). 4) Not having read the source code very carefully, the following seems to indicate that Ruby uses static links from one FRAME to its parent: extern struct FRAME { VALUE self; int argc; VALUE *argv; ID last_func; ID orig_func; VALUE last_class; VALUE cbase; struct FRAME *prev; /* <--- THIS */ struct FRAME *tmp; /* side question: what's that for? */ struct RNode *node; int iter; int flags; } *ruby_frame; I understand this is the simplest way to implement the current semantics, but this means that all environments are encapsulated when nesting closures, doesn't it? I know this is minor point, cause we don't really do that a lot and the usage pattern to experience problems would be quite strange. Summing up ========== Somebody could "easily" get bitten by exceedingly high memory needs as things stand right now. IMHO closures are "too powerful" wrt. eval and accessing vars. If that is forbidden, closures can be analyzed on creation so that only the required locals are encapsulated. Is this something further work can be done on? Some code to increase my Ruby/English ratio :) batsman@tux-chan:/tmp$ expand -t2 am.rb def create_garbage(times) bla = "a" * times # create (possibly) huge object p = proc do puts "Destroying frame." begin puts "With eval" eval %{puts "bla == #{bla}"} eval %{puts "bla2 == #{bla}"} puts "Directly" puts %{bla == #{bla}} puts %{bla2 == #{bla2}} rescue Exception => e puts e end end ObjectSpace.define_finalizer( bla, p ) captured = 1 bla2 = "b" * times proc { captured } end a = create_garbage(10) puts "Variable captured by the closure. Frame not released." GC.start a = nil puts "Released variable. Frame disappears." GC.start batsman@tux-chan:/tmp$ ruby am.rb Variable captured by the closure. Frame not released. Released variable. Frame disappears. Destroying frame. With eval bla == aaaaaaaaaa bla2 == aaaaaaaaaa Directly bla == aaaaaaaaaa undefined local variable or method `bla2' for # -- _ _ | |__ __ _| |_ ___ _ __ ___ __ _ _ __ | '_ \ / _` | __/ __| '_ ` _ \ / _` | '_ \ | |_) | (_| | |_\__ \ | | | | | (_| | | | | |_.__/ \__,_|\__|___/_| |_| |_|\__,_|_| |_| Running Debian GNU/Linux Sid (unstable) batsman dot geo at yahoo dot com Linux! Guerrilla UNIX Development Venimus, Vidimus, Dolavimus. -- Mark A. Horton KA4YBR, mah@ka4ybr.com