From: Robert Klemme Date: 2003-05-22T18:01:20+09:00 Subject: Re: How I'd like method-wrapping to work... This is a multi-part message in MIME format. ------=_NextPart_000_001F_01C32050.04CF41B0 Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: 7bit "Hal E. Fulton" schrieb im Newsbeitrag news:0d1f01c31fed$2b5978e0$0300a8c0@austin.rr.com... > ----- Original Message ----- > From: "Robert" > Newsgroups: comp.lang.ruby > To: "ruby-talk ML" > Sent: Wednesday, May 21, 2003 5:39 PM > Subject: Re: How I'd like method-wrapping to work... > > > > That's no fantasy. Dreams come true... > > The plane, the plane! (Sorry, showing my age...) > (For non-Americans and/or people under 35: A reference > to the TV show _Fantasy Island_.) I just know a hit by, I think it was "Tight Fit" with the same name. The 80's... > Thanks for this fascinating reply... You're welcome! I'm always amazed what can be done with Ruby, it's really a jewel. Sometimes it's even scaring... > > > 3. I've probably overlooked a lot of issues. Please > > > educate me (Matz, Guy, Dave, David, others...) > > > Although not listed this bugged me. See what I came up with (attached). > > Not listed? :) > > others.include?(Robert) # true Oooops, my implementation of include? seems to be broken. Thank's for the fix. :-) > > Cons: > > - 'this' had to be introduced since I found no way to > > rebind the proc (even if there were a way we had > > to deal with multiple concurrent invocations). The > > deeper reason for this problem is, that the block > > comes into existence in class context but should be > > evaluated in instance context later. > > I was having a hard time with that, too, and I wasn't > even worrying about threads. That's the single biggest problem for the hook approach with blocks. If that could be solved, maybe by a method Proc#rebind( binding ) => Proc returning a new proc with the given binding we had the perfect solution. I think there were discussions about rebinding somethread else. > > - Argument lists have to be doubled for the block > > Hmm, not sure what you mean here. But I am going to > play with this a little. If you want to access method arguments in the block you have to include them like I did in the second test script (attached). > > Pros: > > - It nearly looks like what you asked for. > > - Return values work reasonably. > > By "reasonably" do you mean that the return value of > prior will be used even if that call is not the last > one inside the hook block? Ideally we shouldn't have > to do the "val" stuff that I do. I'll test. No, I meant, that you can decide whether you want to propagate them or override them. But it can be fixed to always return the original. Lemme see... Done. If I find some time I might even include a test for duplicate prior calls from the same block. Oops, that was easy. I'll have to stop that... Please don't come up with further feature requests. :-)) > > - It even works for nested invocations, i.e. recursion > > - It works with several instances in the call chain > > This is essential if I understand you rightly. It's also > one reason we need this -- methods are sometimes aliased > more than once, e.g., in a library and in your own code. Yep, that was what I suspected was the typical case. You don't want to worry whether someone else hooked a method already. > > - You can exactly do what you put in > > So I can! I see that the code in hook_test is line for line > the same as mine. Si! > However, on 1.7.3 on Windows I got a syntax error on > line 40 of hooks.rb (had to remove the "proc &"). Not > sure what the issue is here -- assume it worked for you > that way? Strange, I got no error with 1.7.3 on Win. Need to investigate this. The version I have here at work complains, too. > Thanks for a quick reply... Oh, I took some detours in between... (I dropped a version with a String instead a block.) > this is another bit of code > that I'll certainly save. That's very kind of you. > I do hope the discussion continues, though... Of course! There's always room for improvement (see attached :-)) Kind regards robert ------=_NextPart_000_001F_01C32050.04CF41B0 Content-Type: application/octet-stream; name="hooks.rb" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="hooks.rb" class Object private def prior thr =3D Thread.current raise "Just one invocation of prior allowed!" if thr[ = :__hook_prior ] thr[ :__hook_prior ] =3D true ths =3D thr[ :__hook_self ] sym =3D thr[ :__hook_next ] raise "No more methods in chain" unless sym begin puts '>>> nest' if $VERBOSE result =3D ths.send( sym, *(thr[ :__hook_args ])) puts '<<< unnest' if $VERBOSE thr[ :__hook_result ] =3D result return result ensure thr[ :__hook_next ]=3D sym thr[ :__hook_self ]=3D ths end end def this Thread.current[ :__hook_self ] end end class Module private def set_hook_proc( key, pr ) (@hook_procs ||=3D {})[ key ]=3D pr end public def get_hook_proc( key ) (@hook_procs ||=3D {})[ key ] end def define_hook( symbol, &block ) al =3D generate_hook_alias symbol set_hook_proc( al, proc ) x =3D <<-"EO_HOOK" alias #{al} #{symbol} def #{symbol}(*args) thr =3D Thread.current thr[ :__hook_self ]=3D self thr[ :__hook_args ]=3D args thr[ :__hook_next ]=3D :#{al} thr[ :__hook_prior ]=3D false result =3D #{self}.get_hook_proc( "#{al}" ).call( *args ) thr.key?( :__hook_result ) ? thr[ :__hook_result ] : result end EO_HOOK # puts x self.class_eval x # p @hook_procs end private def generate_hook_alias( symbol ) im =3D instance_methods count =3D 0 begin s =3D "__hook__#{symbol}_#{count}" count +=3D 1 end while im.include? s s end end ------=_NextPart_000_001F_01C32050.04CF41B0 Content-Type: application/octet-stream; name="another_test.rb" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="another_test.rb" require 'hooks.rb' class Foo attr :len, true def initialize; @len = 0; end def bar puts "bar: #{self.len}" "bar!" end define_hook :bar do puts "hook 1: #{this.len}" this.len += 1 prior end define_hook :bar do puts "hook 2: #{this.len}" this.len += 1 puts "prior 1" prior puts "prior 2" prior "gotcha!" end def foo(x) p this puts "foo #{x}" Foo.new.foo nil if x "foo!" end define_hook :foo do |x| puts "hook foo 1: #{this.len}" puts "hook foo 1: x=#{x}" this.len += 1 prior end end f = Foo.new ["__hook__bar_0", "__hook__bar_1", "bar"].each do |m| puts "calling #{m}" p f.send( m ) puts "" end p f.foo("xxx") ------=_NextPart_000_001F_01C32050.04CF41B0 Content-Type: application/octet-stream; name="hook_test.rb" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="hook_test.rb" require 'hooks.rb' class String define_hook(:length) do puts "I'm calculating the length" prior end define_hook(:upcase) do prior puts "I just did an upcase operation" end define_hook(:reverse) do puts "I'm about to do a reverse operation" prior puts "I just did a reverse" end end str = "abcde" x = str.length puts x y = str.upcase puts y z = str.reverse puts z ------=_NextPart_000_001F_01C32050.04CF41B0--