[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Wrote "Hookable" mixin, what now?

Jan 'jast' Krueger

4/4/2005 6:41:00 PM

Hi everyone,

I've just written a mixin that allows hooking into methods of a class.

Since an example is worth more than a hundred words, here is one (taken from
the documentation, slightly adapted).

--- begin example ---
require 'Hookable'

class Something
include Hookable

def test_inner *args
puts "In Something#test_inner(#{args.inspect})"
end
hookable :test_inner

def test2 *args
puts "In Something#test2(#{args.inspect})"
test_inner(*args)
end
hookable :test2
end

class MyWrapper < Hookable::Wrapper
def test_inner obj, *args
puts "BEGIN wrapper around test_inner"
super
puts "END wrapper around test_inner"
end

def test2 obj, *args
puts "BEGIN wrapper around test2"
super
puts "END wrapper around test2"
end
end

MyWrapper.add_all_to Something
s = Something.new
s.test2(1,2,3)
MyWrapper.disable_in Something, :test_inner
s.test2(4,5,6)
MyWrapper.enable_all_in Something
s.test2(7,8,9)
--- end example ---

Now what do I do with it? Some people in #ruby-lang on freenode have told me
they like the general idea, but I'd like more suggestions for improvements
(implementation, API, whatever) before I submit it to RAA (is submitting it
a reasonable idea)?

This is similar to a suggestion for Ruby 2.0 I've read about somewhere that
extends the language itself, to allow something like def method:pre/post. I
think my approach, though not quite finished yet, is somewhat more powerful
than that, and also slightly more powerful than AspectR. I haven't done a
full analysis on Hookable's performance yet but I think it should be okay.

An example of something that should be improved: there's a major annoyance
in Hookable::Wrapper that is mentioned in the RDoc documentation (see below).

For reference:
RDoc documentation at <http://heapsort.de/circus/doc/classes/Hookabl...
Current version at <http://heapsort.de/circus/Hooka...

These URLs are likely to become invalid once I've decided what to do with
Hookable.

All comments are appreciated.

--
Best regards,
Jan 'jast' Krueger <usenet@zoidberg.org>, Aachen, Germany
(mails to <jast@...> go through less strict spam checking)
6 Answers

Robert Klemme

4/4/2005 7:18:00 PM

0


"Jan 'jast' Krueger" <usenet@zoidberg.org> schrieb im Newsbeitrag
news:3bdg29F6hjs0oU1@news.dfncis.de...
> Hi everyone,
>
> I've just written a mixin that allows hooking into methods of a class.

You're not the first one. :-) Others have done similar things:
http://www.rubygarden.org/ruby?M...

(Disclaimer: I would do it probably different nowadays.)

> Since an example is worth more than a hundred words, here is one (taken
> from
> the documentation, slightly adapted).
>
> --- begin example ---
<snip/>

> Now what do I do with it? Some people in #ruby-lang on freenode have told
> me
> they like the general idea, but I'd like more suggestions for improvements
> (implementation, API, whatever) before I submit it to RAA (is submitting
> it
> a reasonable idea)?
>
> This is similar to a suggestion for Ruby 2.0 I've read about somewhere
> that
> extends the language itself, to allow something like def method:pre/post.

Yep, that is in sync with my current knowledge.

> I
> think my approach, though not quite finished yet, is somewhat more
> powerful
> than that, and also slightly more powerful than AspectR. I haven't done a
> full analysis on Hookable's performance yet but I think it should be okay.

Could you explain in which ways it is more powerful? I see from your
example that one has to inherit from a certain class and that you use super
to invoke original methods. That might not work in all cases, i.e., when
you cannot determine the class of instances created somewhere else.
Wrapping methods with an approach like the one I have demonstrated works
always because you can open a class any time.

Kind regards

robert

Jan 'jast' Krueger

4/4/2005 7:47:00 PM

0

>> I think my approach, though not quite finished yet, is somewhat more
>> powerful than that, and also slightly more powerful than AspectR. I
>> haven't done a full analysis on Hookable's performance yet but I think
>> it should be okay.
>
> Could you explain in which ways it is more powerful? I see from your
> example that one has to inherit from a certain class and that you use
> super to invoke original methods. That might not work in all cases,
> i.e., when you cannot determine the class of instances created somewhere
> else. Wrapping methods with an approach like the one I have demonstrated
> works always because you can open a class any time.

The syntax shown in the example is one of two possible general approaches.
You can either write a class that inherits Hookable::Wrapper with a number
of methods and hook all (or a specific) methods into another class, or hook
a single Proc into a hookable method; see the [1]RDoc documentation for an
example.

As for why it's more powerful: single hooks (or all methods in
Hookable::Wrapper descendents) can be disabled temporarily, which is
probably useful when a hook should only run on sundays (contrived example
;)). Also, when the Proc interface is used, a Proc can be exchanged with
another without having to remove the old Proc from the chain and add the new
one to the top. In either case, hooks can also be removed. The def
method:pre/post approach doesn't allow for this AFAIK.

The super in my example doesn't do what you might think it does. In fact,
Hookable::Wrapper "catches" the super and calls the next hook in the chain
(or the actual hooked method; I said chain because hooks can be nested). I
can't currently think of an example where this doesn't work except for the
cases documented in [2] at the bottom.

I added this "super" construct to allow for the easiest possible syntax
within the Hookable::Wrapper hooks; it's not used with the other interface
(again, see [1]).

[1] <http://heapsort.de/circus/doc/classes/Hookabl...
[2] <http://heapsort.de/circus/doc/classes/Hookable/Wrappe...

--
Best regards,
Jan 'jast' Krueger <usenet@zoidberg.org>, Aachen, Germany
(mails to <jast@...> go through less strict spam checking)

Jan 'jast' Krueger

4/4/2005 7:50:00 PM

0

> An example of something that should be improved: there's a major annoyance
> in Hookable::Wrapper that is mentioned in the RDoc documentation (see below).

I forgot another deficiency that's not included in the documentation
yet--Hookable is not thread-safe. I see no way to fix this without
completely changing the interface, though.

--
Best regards,
Jan 'jast' Krueger <usenet@zoidberg.org>, Aachen, Germany
(mails to <jast@...> go through less strict spam checking)

Evan Webb

4/4/2005 7:59:00 PM

0

To chime in, perhaps we should look to merge Hookable and the class
that I've been working on, called Maskable. It has a similar aim,
allows you to attach new methods to run with an original method.
Maskable was designed as a replacement for 'alias :old_thing :thing'
as it's not extensible when there is a method that is commonly
modified (like Kernel.require). With a few tweaks from the Hookable
code, it'd be possible to add the hook style stuff too.

Maskable is thread safe as well.

Maskable is at http://hoshi.fallingsnow.net/svn/maskable/m...

It should be noted that maskable hasnt undergone extensive testing.
There are examples of Maskable at the bottom of the maskable.rb.

Thoughts?

Evan

On Apr 4, 2005 12:54 PM, Jan 'jast' Krueger <usenet@zoidberg.org> wrote:
> > An example of something that should be improved: there's a major annoyance
> > in Hookable::Wrapper that is mentioned in the RDoc documentation (see below).
>
> I forgot another deficiency that's not included in the documentation
> yet--Hookable is not thread-safe. I see no way to fix this without
> completely changing the interface, though.
>
> --
> Best regards,
> Jan 'jast' Krueger <usenet@zoidberg.org>, Aachen, Germany
> (mails to <jast@...> go through less strict spam checking)
>
>


Jan 'jast' Krueger

4/4/2005 8:30:00 PM

0

> To chime in, perhaps we should look to merge Hookable and the class
> that I've been working on, called Maskable. It has a similar aim,
> allows you to attach new methods to run with an original method.

Looks nice, though I haven't digged through all of the code yet.

> modified (like Kernel.require). With a few tweaks from the Hookable
> code, it'd be possible to add the hook style stuff too.
>
> Maskable is thread safe as well.

The problem with Hookable is that hookable methods aren't reentrant. When a
hookable method is run, it's pushed on a stack within the internal state of
the owner class. This happens so you can use "super" in hooks (or the
equivalent for the Proc interface) without having to know what method you
were hooking. The stack makes it possible for one hookable method to call
another, like this:

def SomeClass
include Hookable
def test1
test2
end
def test2
# nop
end
hookable :test1, :test2
end

The stack is only necessary because you might want to do this:

a = Proc.new do |obj, *args|
# nop
obj.hooked_method *args
# nop
end

SomeClass.add_hook(:test1, a)
SomeClass.add_hook(:test2, a)

This could be changed so the Proc gets another argument containing the
method name, I suppose, but it makes the Proc interface just a little bit
more awkward.

Now for your main question. I think Maskable and Hookable are both useful,
but there's no reason for mixing them--they provide quite different
functionality.

Even if I misjudged that, your Maskable calls masked methods sequentially
while Hookable calls hooks on the same method nested into each other. It's
probably just not worth the effort rewriting either module.

At first glance, I think Maskable and Hookable can co-exist within one
class, as long as you don't mask a hookable method (which wouldn't make a
lot of sense anyway).

> Thoughts?

Even if I don't (yet) like the idea of combining Maskable and Hookable,
you've given me a few things to think about. Thank you!

--
Best regards,
Jan 'jast' Krueger <usenet@zoidberg.org>, Aachen, Germany
(mails to <jast@...> go through less strict spam checking)

Evan Webb

4/4/2005 8:46:00 PM

0

See below.

On Apr 4, 2005 1:34 PM, Jan 'jast' Krueger <usenet@zoidberg.org> wrote:
> > To chime in, perhaps we should look to merge Hookable and the class
> > that I've been working on, called Maskable. It has a similar aim,
> > allows you to attach new methods to run with an original method.
>
> Looks nice, though I haven't digged through all of the code yet.
>
> > modified (like Kernel.require). With a few tweaks from the Hookable
> > code, it'd be possible to add the hook style stuff too.
> >
> > Maskable is thread safe as well.
>
> The problem with Hookable is that hookable methods aren't reentrant. When a
> hookable method is run, it's pushed on a stack within the internal state of
> the owner class. This happens so you can use "super" in hooks (or the
> equivalent for the Proc interface) without having to know what method you
> were hooking. The stack makes it possible for one hookable method to call
> another, like this:
>
> def SomeClass
> include Hookable
> def test1
> test2
> end
> def test2
> # nop
> end
> hookable :test1, :test2
> end
>
> The stack is only necessary because you might want to do this:
>
> a = Proc.new do |obj, *args|
> # nop
> obj.hooked_method *args
> # nop
> end
>
> SomeClass.add_hook(:test1, a)
> SomeClass.add_hook(:test2, a)
>
> This could be changed so the Proc gets another argument containing the
> method name, I suppose, but it makes the Proc interface just a little bit
> more awkward.
>
> Now for your main question. I think Maskable and Hookable are both useful,
> but there's no reason for mixing them--they provide quite different
> functionality.
>
> Even if I misjudged that, your Maskable calls masked methods sequentially
> while Hookable calls hooks on the same method nested into each other. It's
> probably just not worth the effort rewriting either module.

Actually, not true. Thats just the default behavior. A method can
change how the subsequent methods are called by calling Method.rest
which will run all the methods under it and returns the result. The
reason for this is that methods that are added via a mask know they
are masking something, so they can call Method.rest and get the result
of calling the methods under it so it can act on that result. Whereas
when a method is masked, it probably doesnt know it's been masked, so
when it returns without calling Method.rest, the next method is run.
Below is a quick example of the 2 behaviors.

class A
def run
puts "in A run"
end
end

class B
def run
puts "in B run"
end
end


module Mask1
def run
puts "start mask1"
puts "end mask1"
end
end

module Mask2
def run
puts "start mask2"
Method.rest
puts "end mask2"
end
end

A.add_mask Mask1
B.add_mask Mask2

A.new.run
# output is:
# start mask1
# end mask1
# in A run

B.new.run
# output is:
# start mask2
# in B run
# end mask2

>
> At first glance, I think Maskable and Hookable can co-exist within one
> class, as long as you don't mask a hookable method (which wouldn't make a
> lot of sense anyway).
>
> > Thoughts?
>
> Even if I don't (yet) like the idea of combining Maskable and Hookable,
> you've given me a few things to think about. Thank you!

Your welcome! I'm going to take a look at Hookable this evening as well.

- Evan

>
> --
> Best regards,
> Jan 'jast' Krueger <usenet@zoidberg.org>, Aachen, Germany
> (mails to <jast@...> go through less strict spam checking)
>
>