eigenclass logo
MAIN  Index  Search  Changes  PageRank  Login

Cheaper alternative to Binding.of_caller

Here's a trick to modify local variables in the caller scope without using Binding.of_caller. It's not as general, but much faster when it applies (when you're using a "literal" block):

def foo(a, &block)
  raise "Want a block" unless block_given?
  eval("lambda{|x| whatever = x}", block).call(1)
  yield a + 1

whatever = nil
a = foo(1){|x| 2 * x }
whatever                          # => 1
a                                 # => 4

Why use

  eval("lambda{|x| whatever = x}", block).call(1)

instead of simply

  eval("whatever = 1", block)

? The example is admittedly stupid, since in a less simplistic (but rare!) situation you'd have something more like

def bar(*args, &block)
  raise "Want a block" unless block_given?
  # ...
  eval("local_variables", block).select{|x| condition(x)}.each do |var|
    # compute the value to be assigned to the local var, in the general
    # case a function of the args passed to bar, the current value and the
    # name
    val = value(*args, eval(var, block), var)
    # assign a computed value to the var
    eval("lambda{|x| #{var} = x}", block).call(val)
  # ...

In that situation, just interpolating the value in the string to be eval'ed wouldn't work in general, since most objects cannot be represented the way you can with literals.

I learned this when wrapping methods in Regexp. When you match against a Regexp, the magic variables $', $`, $1 and friends correspond to the values held in $~, which is what is actually modified. Even though they look global, they are actually thread-local and method-local. This means that if you call Regexp#match inside some method, $~ will not be set for the caller. In the case of Regexp#gsub, the above technique could handle that. For other methods that don't take a block (e.g. #===), you'd need Binding.of_caller as illustrated in ruby-talk:150292.

Just another trick for those bending the language to create their DSLs. It's hard to justify its use, but it's there if some convoluted DSL requires it.

No Title - chris2 (2006-07-21 (Fri) 03:50:28)

Be careful not to overwrite variables of the same name as the block parameter.

def hack_x(binding)
  eval("lambda { x = 10 }", binding).call

def hack!(var, binding)
  eval("lambda { |x| #{var} = x }", binding).call(42)

def bla
  x = y = 5
  x                             # => 5
  x                             # => 10

  hack!("y", binding)
  y                             # => 42
  x                             # => 42


mfp 2006-07-21 (Fri) 05:00:31

Good point. I think I once used a gensym with that, but I've been wary of them as of late; ~70 bytes per call is just too much. So

 eval("lambda { |____unique_var_bleh_0xdeadbeef | #{var} = ____unique_var_bleh_0xdeadbeef  }", binding).call

for now :)

Last modified:2006/07/20 05:38:09
Keyword(s):[blog] [ruby] [frontpage] [binding] [of_caller] [DSL]