Binding of the Caller (redux)

I’m trying to use Florian’s Binding.of_caller, and it ain’t working. In
the code below, if I uncomment the Binding.of_caller line, I get the
error:

./libs/binding_of_caller.rb:61:in `of_caller’: Binding.of_caller used
in non-method context or trailing statements of method using it aren’t
in the block. (ArgumentError)

Is there a better way to automatically store a reference to the method
which invoked the current method? If not, can someone see what might be
wrong? (I’d like to remove the second parameter from #dispatch_event.)

#!/usr/local/bin/ruby

require ‘libs/binding_of_caller’

module EventTarget
def add_event_listener( name, callback )
callbacks = ((@registered_events ||= {})[ name.to_s ] ||= []);
callbacks.delete( callback )
callbacks << callback
end
def dispatch_event( evt, source )
callbacks = (@registered_events ||= {})[ evt.name ];
return unless callbacks
#evt.source = Binding.of_caller{ |b| eval(“self”, b) }
evt.source = source
callbacks.each{ |m|
m.call( evt )
}
end
end

class Event
attr_accessor :name, :source, :change
def initialize( name, change=nil )
@name = name.to_s
@change = change
end
end

class HTMLInput
include EventTarget

attr_reader :value
def value=( v )
	@value = v
	evt = Event.new( 'change', v )
	dispatch_event( evt, self )
end

end

inp = HTMLInput.new
inp.add_event_listener( ‘change’, Proc.new{ |e| puts “#{e.source} just
changed.” } )
inp.add_event_listener( ‘change’, Proc.new{ |e| puts “The new value is
#{e.change}.” } )
inp.value = 12

#=> #HTMLInput:0x1c58e4 just changed.
#=> The new value is 12

···


(-, /\ / / //

I'm trying to use Florian's Binding.of_caller, and it ain't working. In
the code below, if I uncomment the Binding.of_caller line, I get the
error:

./libs/binding_of_caller.rb:61:in `of_caller': Binding.of_caller used
in non-method context or trailing statements of method using it aren't
in the block. (ArgumentError)

The message is quite explicit *g*

#!/usr/local/bin/ruby

require 'libs/binding_of_caller'

module EventTarget
  def add_event_listener( name, callback )
    callbacks = ((@registered_events ||= {})[ name.to_s ] ||=
    );
    callbacks.delete( callback )
    callbacks << callback
  end
  def dispatch_event( evt, source )
    callbacks = (@registered_events ||= {})[ evt.name ];
    return unless callbacks
    #evt.source = Binding.of_caller{ |b| eval("self", b) }
    evt.source = source
    callbacks.each{ |m|
      m.call( evt )
    }

try
        Binding.of_caller do |b|
      evt.source = eval("self", b)
      callbacks.each{|m| m.call evt }
    end
  end

···

On Tue, Sep 28, 2004 at 12:56:25PM +0900, Gavin Kistner wrote:
  

  end
end

class Event
  attr_accessor :name, :source, :change
  def initialize( name, change=nil )
    @name = name.to_s
    @change = change
  end
end

class HTMLInput
  include EventTarget
  
  attr_reader :value
  def value=( v )
    @value = v
    evt = Event.new( 'change', v )
    dispatch_event( evt, self )
  end
end

inp = HTMLInput.new
inp.add_event_listener( 'change', Proc.new{ |e| puts "#{e.source} just
changed." } )
inp.add_event_listener( 'change', Proc.new{ |e| puts "The new value is
#{e.change}." } )
inp.value = 12

#=> #<HTMLInput:0x1c58e4> just changed.
#=> The new value is 12

--
(-, /\ \/ / /\/

--
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

./libs/binding_of_caller.rb:61:in `of_caller': Binding.of_caller used
in non-method context or trailing statements of method using it aren't
in the block. (ArgumentError)

The message is quite explicit *g*

Perhaps, but confusing to me :slight_smile:
(The phrase "Either your zyloxens aren't grimbled or you tweedled outside of a borklub" is also explicit, but that don't mean I have a clue how to solve the problem :wink:

Binding.of_caller do |b|
    evt.source = eval("self", b)
    callbacks.each{|m| m.call evt }
  end
end

That worked, thanks.

So does "... trailing statements of method using it aren't in the block" mean "you can't place any statements after the Binding.of_caller invocation"? (Like it irrevocably changes the context and can't get you back to where you were or something?)

···

On Sep 28, 2004, at 2:16 AM, Mauricio Fernández wrote:

On Tue, Sep 28, 2004 at 12:56:25PM +0900, Gavin Kistner wrote:

--
(-, /\ \/ / /\/

>>./libs/binding_of_caller.rb:61:in `of_caller': Binding.of_caller used
>>in non-method context or trailing statements of method using it aren't
>>in the block. (ArgumentError)
>
>The message is quite explicit *g*

Perhaps, but confusing to me :slight_smile:
(The phrase "Either your zyloxens aren't grimbled or you tweedled
outside of a borklub" is also explicit, but that don't mean I have a
clue how to solve the problem :wink:

>Binding.of_caller do |b|
> evt.source = eval("self", b)
> callbacks.each{|m| m.call evt }
> end
>end

That worked, thanks.

So does "... trailing statements of method using it aren't in the
block" mean "you can't place any statements after the Binding.of_caller
invocation"?

Exactly.

(Like it irrevocably changes the context and can't get you
back to where you were or something?)

That's because Florian's Binding.of_caller works as follows:
* a continuation for the block is created
* of_caller returns; *the enclosing method returns*
* the next event notified to the proc_func is the 'return'; at that
   point the binding of the caller is captured
* the continuation is called, and the block is given the Binding that
   was obtained previously
* when the block has been executed, the method returns *for the second
   time*, with the value returned by the block

It is possible to implement Binding.of_caller without callcc, which
makes it much faster, but that comes at a cost:
* ugly interface
* the value returned is a proxy for the real value (can be fixed with
  evil.rb of course for many kinds of objects)

batsman@tux-chan:/tmp$ cat binding_of_caller.rb

# UGLY 'binding of caller' implementation, which doesn't use callcc.
# Based on Florian Groß' original.

# This method allows you to grab the binding of the method that called your
# method. Don't use it when you're not inside a method.

···

On Tue, Sep 28, 2004 at 09:14:02PM +0900, Gavin Kistner wrote:

On Sep 28, 2004, at 2:16 AM, Mauricio Fernández wrote:
>On Tue, Sep 28, 2004 at 12:56:25PM +0900, Gavin Kistner wrote:

#
# It's used like this:
# def inc_counter
# Binding.of_caller(self, r = lambda do
# binding = eval("binding_of_caller", r)
# eval("counter += 1", binding)
# end)
# end
# counter = 0
# 2.times { inc_counter }
# counter # => 2
#
# You will have to put the whole rest of your method into the
# block that you pass into this method. If you don't do this
# an Exception will be raised. Because of the way that this is
# implemented it has to be done this way.
def Binding.of_caller(oldself, block)
  old_critical = Thread.critical
  Thread.critical = true
  count = 0

  result = nil
  retvalue = Object.new
  #FIXME: which methods do we want
  #class << retvalue; self end.send(:define_method, :to_s){ result }
  class << retvalue; self end.send(:define_method, :value){ result }

  tracer = lambda do |*args|
      #p args
    type, context = args[0], args[4]
    if type == "return"
      count += 1
      # First this method and then calling one will return --
      # the trace event of the second event gets the context
      # of the method which called the method that called this
      # method.
      if count == 2
        # It would be nice if we could restore the trace_func
        # that was set before we swapped in our own one, but
        # this is impossible without overloading set_trace_func
        # in current Ruby.
        set_trace_func(nil)
        eval("binding_of_caller = nil; lambda{|binding_of_caller|}", block).call(context)
        result = oldself.instance_eval(&block)
        Thread.critical = old_critical
      end
    elsif type != "line"
      set_trace_func(nil)
      error_msg = "Binding.of_caller used in non-method context or " +
        "trailing statements of method using it aren't in the block."
      raise(ArgumentError, error_msg)
    end
  end

  set_trace_func(tracer)
  retvalue
end

def foo
    a = "some string"
    bar
end

def bar
    Binding.of_caller(self, r = lambda do
        binding_of_caller = eval("binding_of_caller", r)
        eval("a", binding_of_caller)
    end)
end

# we can get rid of the proxy obj with Object#become or whatever
p foo.value
batsman@tux-chan:/tmp$ ruby -v binding_of_caller.rb
ruby 1.8.2 (2004-09-22) [i686-linux]
"some string"

Both implementations have the same problem: you lose the previous
trace_func. It could be restored transparently if Ruby exposed it.

--
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

Gavin Kistner wrote:

./libs/binding_of_caller.rb:61:in `of_caller': Binding.of_caller used
in non-method context or trailing statements of method using it aren't
in the block. (ArgumentError)

The message is quite explicit *g*

Perhaps, but confusing to me :slight_smile:
(The phrase "Either your zyloxens aren't grimbled or you tweedled outside of a borklub" is also explicit, but that don't mean I have a clue how to solve the problem :wink:

So does "... trailing statements of method using it aren't in the block" mean "you can't place any statements after the Binding.of_caller invocation"? (Like it irrevocably changes the context and can't get you back to where you were or something?)

It is this way because otherwise the statements would get executed twice. That would be quite confusing and possibly even dangerous.

I tried to make the interface obvious in the documentation (see the sample), but I seem to have failed. Do you have any suggestions for how I could fix this?

Regards,
Florian Gross

Er, to which sample are you referring?

I think you could make it more clear by simply stating that Binding.of_caller must be in a method, and it must be the last statement in that method (but thanks to the magic of closures, you can put the rest of your code in the block).

I'd include this both in the documentation (worded like above) and also in the example, like:

def foo
  statement1
  statement2
  Binding.of_caller{ |binding|
    statement3_that_uses_binding
    statement4_that_clearly_uses_the_method_scope
  }
  # No other statements can go here!
end

- Gavin, who looks forward to a version of Ruby which has a simple "#caller" method that points to the instance which invoked the current method.

···

On Sep 28, 2004, at 10:29 AM, Florian Gross wrote:

Gavin Kistner wrote:

So does "... trailing statements of method using it aren't in the block" mean "you can't place any statements after the Binding.of_caller invocation"? (Like it irrevocably changes the context and can't get you back to where you were or something?)

[...]
I tried to make the interface obvious in the documentation (see the sample), but I seem to have failed. Do you have any suggestions for how I could fix this?

Gavin Kistner wrote:

···

On Sep 28, 2004, at 10:29 AM, Florian Gross wrote:

Gavin Kistner wrote:

So does "... trailing statements of method using it aren't in the block" mean "you can't place any statements after the Binding.of_caller invocation"? (Like it irrevocably changes the context and can't get you back to where you were or something?)

I tried to make the interface obvious in the documentation (see the sample), but I seem to have failed. Do you have any suggestions for how I could fix this?

Er, to which sample are you referring?

The old one, but it might have been a bit unclear.

I have changed the documentation according to your suggestions, thank you. Here's the new documentation:

# This method returns the binding of the method that called your
# method. It will raise an Exception when you're not inside a method.
#
# It's used like this:
# def inc_counter(amount = 1)
# Binding.of_caller do |binding|
# # Create a lambda that will increase the variable 'counter'
# # in the caller of this method when called.
# inc = eval("lambda { |arg| counter += arg }", binding)
# # We can refer to amount from inside this block safely.
# inc.call(amount)
# end
# # No other statements can go here. Put them inside the block.
# end
# counter = 0
# inc_counter(2)
# counter # => 2
#
# Binding.of_caller must be the last statement in the method.
# This means that you will have to put everything you want to
# do after the call to Binding.of_caller into the block of it.
# This should be no problem however, because Ruby has closures.
# If you don't do this an Exception will be raised. Because of
# the way that Binding.of_caller is implemented it has to be
# done this way.

- Gavin, who looks forward to a version of Ruby which has a simple "#caller" method that points to the instance which invoked the current method.

Yup, I also think that all this ought to be easier in a future Ruby. This is the best I can do for now, unfortunately.

Regards,
Florian Gross

+1

T.

···

On Wednesday 29 September 2004 09:27 am, Gavin Kistner wrote:

- Gavin, who looks forward to a version of Ruby which has a simple
"#caller" method that points to the instance which invoked the current
method.