21

I have this code:

 l = lambda { a }
 def some_function
     a = 1
 end

I just want to access a by the lambda and a special scope which has defined a already somewhere like inside some_function in the example, or just soon later in the same scope as:

 l = lambda { a }
 a = 1
 l.call

Then I found when calling l, it is still using its own binding but not the new one where it was called.

And then I tried to use it as:

 l.instance_eval do
     a = 1
     call
 end

But this also failed, it is strange that I can't explain why.

I know the one of the solution is using eval, in which I could special a binding and executing some code in text, but I really do not want to use as so.

And, I know it is able to use a global variable or instance variable. However, actually my code is in a deeper embedded environment, so I don't want to break the completed parts if not quite necessary.

I have referred the Proc class in the documentation, and I found a function names binding that referred to the Proc's context. While the function only provided a way to access its binding but cannot change it, except using Binding#eval. It evaluate text also, which is exactly what I don't like to do.

Now the question is, do I have a better (or more elegant) way to implement this? Or using eval is already the regular manner?

Edit to reply to @Andrew:
Okay, this is a problem which I met when I'm writing a lexical parser, in which I defined a array with fixed-number of items, there including at least a Proc and a regular expression. My purpose is to matching the regular expressions and execute the Procs under my special scope, where the Proce will involved some local variables that should be defined later. And then I met the problem above.
Actually I suppose it is not same completely to that question, as mine is how to pass in binding to a Proc rather than how to pass it out.

@Niklas: Got your answer, I think that is what exactly I want. It has solved my problem perfectly.

shouya
  • 2,863
  • 1
  • 24
  • 45
  • 4
    Perhaps a good question to ask is *why* do you want to do this, or, rather, what are you trying to ultimately achieve? – Andrew Marshall Apr 07 '12 at 22:53
  • 2
    @Andrew: I don't know. I'd be interested in an answer to this exact question, rather than a solution to the underlying problem :) – Niklas B. Apr 07 '12 at 22:59
  • 1
    @NiklasB. I agree, but knowing the end-goal can help understand the problem better. And I'm pretty sure I answered a question similar to this a while back, and I'm trying to find it but can't `:(`. – Andrew Marshall Apr 07 '12 at 23:07
  • 1
    @Andrew: I'd be very interested in that! As it happens, I was facing a very similar problem related to dynamic binding today. – Niklas B. Apr 07 '12 at 23:07
  • @NiklasB. Ah it was the reverse of this it seems: trying to use a variables in a method that were defined in a block. ["Is it possible to access block's scope in method?"](http://stackoverflow.com/questions/9245947/is-it-possible-to-access-blocks-scope-in-method/9246066#9246066) (oh and what do you know look at the first comment… deja vu!) – Andrew Marshall Apr 07 '12 at 23:10
  • @Andrew: Yeah, the other way round it's very simple: `eval('varname', p.binding)` – Niklas B. Apr 07 '12 at 23:11

4 Answers4

26

You can try the following hack:

class Proc
  def call_with_vars(vars, *args)
    Struct.new(*vars.keys).new(*vars.values).instance_exec(*args, &self)
  end
end

To be used like this:

irb(main):001:0* lambda { foo }.call_with_vars(:foo => 3)
=> 3
irb(main):002:0> lambda { |a| foo + a }.call_with_vars({:foo => 3}, 1)
=> 4

This is not a very general solution, though. It would be better if we could give it Binding instance instead of a Hash and do the following:

l = lambda { |a| foo + a }
foo = 3
l.call_with_binding(binding, 1)  # => 4

Using the following, more complex hack, this exact behaviour can be achieved:

class LookupStack
  def initialize(bindings = [])
    @bindings = bindings
  end

  def method_missing(m, *args)
    @bindings.reverse_each do |bind|
      begin
        method = eval("method(%s)" % m.inspect, bind)
      rescue NameError
      else
        return method.call(*args)
      end
      begin
        value = eval(m.to_s, bind)
        return value
      rescue NameError
      end
    end
    raise NoMethodError
  end

  def push_binding(bind)
    @bindings.push bind
  end

  def push_instance(obj)
    @bindings.push obj.instance_eval { binding }
  end

  def push_hash(vars)
    push_instance Struct.new(*vars.keys).new(*vars.values)
  end

  def run_proc(p, *args)
    instance_exec(*args, &p)
  end
end

class Proc
  def call_with_binding(bind, *args)
    LookupStack.new([bind]).run_proc(self, *args)
  end
end

Basically we define ourselves a manual name lookup stack and instance_exec our proc against it. This is a very flexible mechanism. It not only enables the implementation of call_with_binding, it can also be used to build up much more complex lookup chains:

l = lambda { |a| local + func(2) + some_method(1) + var + a }

local = 1
def func(x) x end

class Foo < Struct.new(:add)
  def some_method(x) x + add end
end

stack = LookupStack.new
stack.push_binding(binding)
stack.push_instance(Foo.new(2))
stack.push_hash(:var => 4)

p stack.run_proc(l, 5)

This prints 15, as expected :)

UPDATE: Code is now also available at Github. I use this for one my projects too now.

Niklas B.
  • 92,950
  • 18
  • 194
  • 224
2
class Proc
    def call_with_obj(obj, *args)
        m = nil
        p = self
        Object.class_eval do
            define_method :a_temp_method_name, &p
            m = instance_method :a_temp_method_name; remove_method :a_temp_method_name
        end
        m.bind(obj).call(*args)
    end
end

And then use it as:

class Foo
    def bar
        "bar"
    end
end

p = Proc.new { bar }

bar = "baz"

p.call_with_obj(self) # => baz
p.call_with_obj(Foo.new) # => bar
shouya
  • 2,863
  • 1
  • 24
  • 45
  • 2
    What's the point? Those could just be written as `instance_exec(&p)` or `Foo.new.instance_exec(&p)`. I think you just reinvented the wheel.. My answer was meant to be a lot more general than that, but if `instance_exec` is sufficient, then go for it :) The approach using a temporary method is not particularly elegant, because it's not reentrant (as in, it can't be used in a nested manner, nor can it be used across threads). – Niklas B. May 20 '12 at 01:12
  • 2
    So in a nutshell, I don't consider this "cool", but if it works for you, that's nice :) – Niklas B. May 20 '12 at 01:20
  • @NiklasB. Oh you are right. I used to just considered about whether I can get rid of string evaluation. I got known the instance_exec after read your answer, but... I'm just really reinventing the wheel. – shouya May 20 '12 at 05:20
1

Perhaps you don't actually need to define a later, but instead only need to set it later.

Or (as below), perhaps you don't actually need a to be a local variable (which itself references an array). Instead, perhaps you can usefully employ a class variable, such as @@a. This works for me, by printing "1":

class SomeClass
  def l
    @l ||= lambda { puts @@a }
  end

  def some_function
    @@a = 1
    l.call
  end
end
SomeClass.new.some_function
MarkDBlackwell
  • 1,994
  • 18
  • 27
0

a similar way:

class Context
  attr_reader :_previous, :_arguments

  def initialize(_previous, _arguments)
    @_previous = _previous
    @_arguments = _arguments
  end
end

def _code_def(_previous, _arguments = [], &_block)
  define_method("_code_#{_previous}") do |_method_previous, _method_arguments = []|
    Context.new(_method_previous, _method_arguments).instance_eval(&_block)
  end
end

_code_def('something') do
  puts _previous
  puts _arguments
end
localhostdotdev
  • 1,795
  • 16
  • 21