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:
- 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
-
why the difference between direct access and eval?
-
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).
-
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 #Object:0x401ae9ac
–
_ _
__ __ | | ___ _ __ ___ __ _ _ __
'_ \ /| __/ __| '_
_ \ / ` | ’ \
) | (| | |__ \ | | | | | (| | | | |
.__/ _,|_|/| || ||_,|| |_|
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