Any way to get the calling method's binding?

G

Gavin Sinclair

Something I've wanted to do on a few occasions recently is to evaluate
an expression in the context of the calling method. A simple example
is this:

def trace(expr, _binding)
LOG.debug "#{expr} = #{eval expr, _binding)}"
end

def something
x = 5
y = 7
trace "x + y", binding
end

The effect of calling something() now is that the message "x + y = 12"
gets logged.

It Would Be Nice if I could do this instead:

def trace(expr)
_binding = somehow_get_calling_methods_binding()
LOG.debug "#{expr} = #{eval expr, _binding)}"
end

def something
x = 5
y = 7
trace "x + y"
end

Is there a substitute for "somehow_get_calling_methods_binding()" in
the code above?

Thanks,
Gavin
 
A

Austin McDonald

In the meantime, you could do this:

def trace (expr)
LOG.debug "#{expr} = #{yield expr}"
end

def something
x=5
x=7
trace "x+y", {|n| eval n}
end

something => 12

I'll admit that it'd be nice if the block passing could be automated;
not sure how to do that off the top of my head. This reminds me of
dynamic scoping; anybody got any clue about how to do away with the
block? Maybe there's a way to do this with an extension?

Austin
 
J

Joel VanderWerf

Austin said:
In the meantime, you could do this:

def trace (expr)
LOG.debug "#{expr} = #{yield expr}"
end

def something
x=5
x=7
trace "x+y", {|n| eval n}
end

something => 12

def trace(&block)
expr = block.call
puts "#{expr} = #{eval(expr, block)}"
end

def something
x = 5
y = 7
trace {"x + y"}
end

something # ==> x + y = 12
 
A

Austin McDonald

Excellent; I had the right idea, but yours is much cleaner.

For giggles, and maybe b/c it's a little more explicit, you could use
Kernel#binding to make it obvious what's going on:

def trace(expr, b)
LOG.debug "#{expr} = #{eval(expr, b)}"
end

def something()
x=5
y=7
trace "x+y", binding
end

something # ==> x+y = 12

Although I like Joel's version better, I think this is a little clearer.

Austin
 
C

Charles Comstock

Excellent; I had the right idea, but yours is much cleaner.

For giggles, and maybe b/c it's a little more explicit, you could use
Kernel#binding to make it obvious what's going on:

def trace(expr, b)
LOG.debug "#{expr} = #{eval(expr, b)}"
end

def something()
x=5
y=7
trace "x+y", binding
end

something # ==> x+y = 12

Although I like Joel's version better, I think this is a little clearer.

Austin

Umm that's exactly what gavin was trying to avoid, explicitely passing the
binding...

Charles Comstock
 
A

Austin McDonald

Right. I'm out of it at the moment; got wrapped up in following the
idea trails and wound up where Gavin started... my apologies. I think
I'll get some sleep.

Austin
 
F

Florian Gross

Gavin said:
Something I've wanted to do on a few occasions recently is to evaluate
an expression in the context of the calling method.

Use this:
begin
require 'simplecc'
rescue LoadError
def Continuation.create(*args, &block)
cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
result ||= args
return *[cc, *result]
end
end

# This method returns 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 do |binding|
# 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(&block)
old_critical = Thread.critical
Thread.critical = true
count = 0
cc, result, error = Continuation.create(nil, nil)
error.call if error

tracer = lambda do |*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)
cc.call(eval("binding", context), nil)
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."
cc.call(nil, lambda { raise(ArgumentError, error_msg ) })
end
end

unless result
set_trace_func(tracer)
return nil
else
Thread.critical = old_critical
yield result
end
end

Regards,
Florian Gross
 
R

Robert Klemme

Florian Gross said:
Gavin said:
Something I've wanted to do on a few occasions recently is to evaluate
an expression in the context of the calling method.

Use this:
begin
require 'simplecc'
rescue LoadError
def Continuation.create(*args, &block)
cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
result ||= args
return *[cc, *result]
end
end

# This method returns 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 do |binding|
# 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(&block)
old_critical = Thread.critical
Thread.critical = true
count = 0
cc, result, error = Continuation.create(nil, nil)
error.call if error

tracer = lambda do |*args|

You're recreating the function on every invocation. Could be a performance
bottleneck.
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)
cc.call(eval("binding", context), nil)
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."
cc.call(nil, lambda { raise(ArgumentError, error_msg ) })
end
end

unless result
set_trace_func(tracer)
return nil
else
Thread.critical = old_critical
yield result
end
end

Regards,
Florian Gross


This seems much simpler to me...

set_trace_func lambda { |event, file, line, id, binding, classname|
case event
when "call"
( Thread.current[:bindings] ||= [] ).push binding
when "return"
Thread.current[:bindings].pop
end
}

module Kernel
private

def caller_binding
Thread.current[:bindings][-3]
end
end

# demo usage:

def test2
# set x for demonstration purposes
x = 99
p eval( "x", caller_binding )
end

def test
x = 10
test2
x = 20
test2
end

test

Regards

robert
 
F

Florian Gross

Robert said:
This seems much simpler to me...

set_trace_func lambda { |event, file, line, id, binding, classname|
case event
when "call"
( Thread.current[:bindings] ||= [] ).push binding
when "return"
Thread.current[:bindings].pop
end
}

Global trace_funcs are a *huge* performance bottle neck. That's why I'm
doing so much work to make sure that the trace_func will go away as soon
as possible.

Regards,
Florian Gross
 
R

Robert Klemme

Florian Gross said:
Robert said:
This seems much simpler to me...

set_trace_func lambda { |event, file, line, id, binding, classname|
case event
when "call"
( Thread.current[:bindings] ||= [] ).push binding
when "return"
Thread.current[:bindings].pop
end
}

Global trace_funcs are a *huge* performance bottle neck. That's why I'm
doing so much work to make sure that the trace_func will go away as soon
as possible.

Ah, ok. I thought there should be some advantage...

robert
 
J

Jean-Hugues ROBERT

Gavin said:
Something I've wanted to do on a few occasions recently is to evaluate
an expression in the context of the calling method.

Use this:
begin
require 'simplecc'
rescue LoadError
def Continuation.create(*args, &block)
cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
result ||= args
return *[cc, *result]
end
end
# This method returns 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 do |binding|
# 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(&block)
old_critical = Thread.critical
Thread.critical = true
count = 0
cc, result, error = Continuation.create(nil, nil)
error.call if error
tracer = lambda do |*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)
cc.call(eval("binding", context), nil)
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."
cc.call(nil, lambda { raise(ArgumentError, error_msg ) })
end
end
unless result
set_trace_func(tracer)
return nil
else
Thread.critical = old_critical
yield result
end
end

Regards,
Florian Gross

This is a very creative piece of work, congratulations !
I am adding that to my rcr.rb with the proper credit.

I guess that it is not extremely efficient but it will
help me a lot in my exception logging scheme. So far I
used to explicitly pass the binding to be able to dump
the local variables when required. Thanks !

For more speed demanding usage, there would be some need
for native support I guess, but as of today your
solution is probably very close to the most efficient
solution.

BTW: What is "simplecc.rb" ?

Yours,

JeanHuguesRobert
 
F

Florian Gross

Jean-Hugues ROBERT said:
[Binding.of_caller]
This is a very creative piece of work, congratulations !
I am adding that to my rcr.rb with the proper credit.

Thanks. :)
I guess that it is not extremely efficient but it will
help me a lot in my exception logging scheme. So far I
used to explicitly pass the binding to be able to dump
the local variables when required. Thanks !

Nice to hear that it's useful. I actually need it for implementing a
wrapper around eval(). (I need to give unique file descriptors to
evaluated code in proc_source.rb so that I can later map them back to code.)
For more speed demanding usage, there would be some need
for native support I guess, but as of today your
solution is probably very close to the most efficient
solution.

I dunno how fast it is -- we would need to do some benchmarking to find
that out -- but at least it is only slow where it's being used. :)
BTW: What is "simplecc.rb" ?

It's the same code that is included in the rescue part. It's the only
way I can use Continuations without going insane.
Yours,
JeanHuguesRobert

Regards,
Florian Gross
 

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
474,146
Messages
2,570,832
Members
47,374
Latest member
EmeliaBryc

Latest Threads

Top