Binding of the Caller (redux)

G

Gavin Kistner

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
 
M

Mauricio Fernández

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
 
G

Gavin Kistner

The message is quite explicit *g*

Perhaps, but confusing to me :)
(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 ;)
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?)
 
M

Mauricio Fernández

Perhaps, but confusing to me :)
(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 ;)


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.
#
# 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.
 
F

Florian Gross

Gavin said:
Perhaps, but confusing to me :)
(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 ;)

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
 
G

Gavin Kistner

Gavin said:
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?

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.
 
F

Florian Gross

Gavin said:
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
 
T

trans. (T. Onoma)

- 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.

+1

T.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,763
Messages
2,569,562
Members
45,038
Latest member
OrderProperKetocapsules

Latest Threads

Top