From: Dumaiu Date: 2006-03-05T07:23:39+09:00 Subject: Re: Review requested: Binding.call_stack(), Binding.of_caller()-style Hiya! Sure, I'll talk some more. It took *me* a while to wrap my head around Florian Gross's stuff. I just didn't want to look like I was trying to talk down to anyone. As we know, Kernel::caller() returns an array of strings, and is pretty frickin' useless by itself. A lot of people, independent of one another, have wanted a way to write library methods that can "magically" access the scope in which they were invoked. Gross wrote a library to do this, binding_of_caller.rb, which has been floating around this newsgroup for a while; and if you haven't seen it yet, you shouldn't have trouble finding it. The only way to generate Binding objects automatically is set_trace_func(), so you'll see it in every attempt of this sort. You stick in a trace func that catches the bindings from the 'return' events. Gross's clever solution was to use a continuation to mark a location, take a detour to collect Bindings, and then resume. Scenario: you write a library function requiring access to its caller's binding. Your function calls his Binding::of_caller(), puts a block into set_trace_func(), creates a continuation, and allows itself to return. You have now moved back a stack frame into your original function. The tracer notes this. Your function then *also* returns immediately (enforced by binding_of_caller.rb), and with the second 'return' event you get the binding you're after. Once the trace func sees this, it uses the cached continuation to dive back into normal execution. The necessity of Binding::of_caller() being a tail call bothered me. Instead of def library_func() bind = Binding::of_caller do_something( bind ) end you must split your function into an outer and an inner component: def library_func() # Header Binding::of_caller { |bind| library_func_impl(bind) } # Continuation is only available as a block parameter. # Nothing allowed here end def library_func_impl(bind) # Body do_something( bind ) end That block is required. Not concealed enough! The only novelty in my code is the use of an escape continuation, i.e., an exception, which eliminates the tail-call thing: set_trace_func() sees nothing but a pile of 'return' events until it reaches the top (bottom? top?) of the stack, and then--*just* before the uncaught exception crashes the program--the stored continuation is called. Roughly equivalent to the previous example is def library_func() bind = Binding::call_stack(1,1).first.binding do_something( bind ) end call_stack() calls callcc(). callcc() calls set_trace_func() and throws an exception. The exception unrolls the stack, triggering the tracer automatically. The tracer returns flow to call_stack() and then unsets itself. The real stuff is all in Binding::call_stack(), the rest just 'limbs and outward flourishes.' I don't know about you, but I had to work very hard to understand Kernel::callcc(). It's spaghetti syntax, to be sure. F.G.'s design is faster and optimal if you only need to regress one frame. Once I realized I could get the whole stack, it seemed silly to return an array of just Bindings only, so I made it more like I wished Kernel::caller() had worked to begin with. The implementation is a byzantine shot at balancing extensibility and efficiency. In particular, the capability of passing start- and endpoints as arguments, the last feature added and the least well tested, complicated things. Take care, -Jonathan