[lnkForumImage]
TotalShareware - Download Free Software

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


 

Forums >

comp.lang.ruby

python-style decorators

Keith Rarick

8/3/2007 11:53:00 PM

I've been using ruby for about 8 months now and I've come to appreciate
its powerful features. But I miss python's decorators, so I made
something similar.

The code is attached; here are some examples. It's really easy to use a
decorator:

class C
memoized
def tak(x, y, z)
return z if x <= y
tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
end
end

And really easy to write one:

class Module
decorator
def memoized(name, f)
lambda do |*args|
((@__memo_cache ||= {})[[name, args]] ||=
[f.bind(self).call(*args)])[0]
end
end
end

You can use this to do all of the usual decorator things, like
memoization, method call tracing, synchronization, currying, type
checking, basic profiling, print warnings for deprecated methods, or
anything else you can think of.

One important limitation of this implementation is that you cannot
"stack" decorators like you can in python. That's fixable, and hopefully
I'll have time to make another version of this library with stackable
decorators. But until then, maybe someone will find this useful.

I'd love to hear feedback about this library! If you have any comments
or questions, please let me know.

kr

Attachments:
http://www.ruby-...attachment/...

--
Posted via http://www.ruby-....

33 Answers

Trans

8/6/2007 2:23:00 PM

0



On Aug 3, 4:53 pm, Keith Rarick <k...@essembly.com> wrote:
> I've been using ruby for about 8 months now and I've come to appreciate
> its powerful features. But I misspython'sdecorators, so I made
> something similar.
>
> The code is attached; here are some examples. It's really easy to use a
> decorator:
>
> class C
> memoized
> def tak(x, y, z)
> return z if x <= y
> tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
> end
> end
>
> And really easy to write one:
>
> class Module
> decorator
> def memoized(name, f)
> lambda do |*args|
> ((@__memo_cache ||= {})[[name, args]] ||=
> [f.bind(self).call(*args)])[0]
> end
> end
> end
>
> You can use this to do all of the usual decorator things, like
> memoization, method call tracing, synchronization, currying, type
> checking, basic profiling, print warnings for deprecated methods, or
> anything else you can think of.
>
> One important limitation of this implementation is that you cannot
> "stack" decorators like you can inpython. That's fixable, and hopefully
> I'll have time to make another version of this library with stackable
> decorators. But until then, maybe someone will find this useful.
>
> I'd love to hear feedback about this library! If you have any comments
> or questions, please let me know.
>
> kr
>
> Attachments:http://www.ruby-forum.com/attachment/...

This is quite interesting.

I'm not sure how I feel about the use of declarative style. I'm not a
big fan of public, private, protected to begin with b/c of this. It
also complicates the code dealing with method_added and wrapping
methods... I wonder how robust it is. (This is another good example of
where some built in AOP functionality could improve things.)

Though it's a bit less convenient, it might be better to just name the
method:

class C
def tak(x, y, z)
return z if x <= y
tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
end
memoized :tak
end

Unfortunately, not as nice, but the underlying code would certainly
get simplified.

Dreaming a little. I wonder, if there were a callback for when a class/
module closes, then maybe you do do it lazily? Also, I wonder if this
corresponds to Matz' idea of ":"-notation he used for pre and post.
So,

class C
def tak:memoized(x, y, z)
return z if x <= y
tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
end
end

Of course we could always do:

class C
def_memoized :tak do |x, y, z|
return z if x <= y
tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
end
end

Though the lack of block support and closed scope make that not quite
the same.

Oh, one last thing. Could you give some other examples? Memoization
perhaps isn't the best, since most times it is as easy as:

class C
def initialize
@_tak = {}
end

def tak(x,y,z)
@_tak[x,y,z]] ||= (
return z if x <= y
tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
)
end
end

T.


Keith Rarick

8/7/2007 8:45:00 PM

0

On 8/6/07, Trans <transfire@gmail.com> wrote:
> This is quite interesting.

Thanks. I'd like it to be more than interesting; I want it to be
useful! Hopefully I can make some improvements.

> I'm not sure how I feel about the use of declarative style.
> I'm not a big fan of public, private, protected to begin
> with b/c of this.

I can understand if you have reservations about the style. Personally,
I'm used to it and I find it easy to read.

One notable difference between the public, private, protected notation
and these decorators is that these must appear immediately before each
method they should apply to. A decorator's effects don't stick around
beyond the very next method, so there's no danger of having it go
unnoticed further down the file.

> It also complicates the code dealing
> with method_added and wrapping methods... I wonder how
> robust it is. (This is another good example of where some
> built in AOP functionality could improve things.)

I worry about the interaction with existing method_added() or
singleton_method_added() hooks. I tested some straightforward examples
of those and found no problems. Also, this code is working with no
trouble in a rather large rails app in the company I work for.

The code would be simpler and safer if ruby treated metaclasses and
classes consistently by calling metaclass.method_added() instead of
(or in addition to) singleton_method_added(). (See my earlier mail
with subject "method_added hook and class methods" for more.)

Perhaps the following notation would be better:

class C
decorate :memoized
def tak(x, y, z)
return z if x <= y
tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
end
end

It would obviate the need to redefine the decorator method itself and
thus simplify the implementation. Also, this notation is more explicit
about the mechanism.

> Though it's a bit less convenient, it might be better to
> just name the method:
>
> class C
> def tak(x, y, z)
> return z if x <= y
> tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
> end
> memoized :tak
> end
>
> Unfortunately, not as nice, but the underlying code would
> certainly get simplified.

Yes, the implementation would be pretty easy. (There's even a similar
example, called "once", in the pickaxe book.) However, putting the
decorator at the bottom makes it easy to miss, especially if the
method body is long.

> Dreaming a little. I wonder, if there were a callback for when
> a class/module closes, then maybe you do do it lazily?

Yeah, when I started thinking about how to do this I looked for such a
callback but didn't find one.

> Also, I
> wonder if this corresponds to Matz' idea of ":"-notation he
> used for pre and post. So,
>
> class C
> def tak:memoized(x, y, z)
> return z if x <= y
> tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
> end
> end

That's very interesting! I didn't know about that notation before. I
just read about pre, post, and wrap methods, which seem similar but
less useful. They are invoked at the method call, rather than the
method definition, so they have less chance to affect the method's
interface.

Now, if you could define arbitrary methods to be used with the
":"-notation, like def tak:memoized(x, y, z) in your example above,
that would be really useful.

> Oh, one last thing. Could you give some other examples?

Sure. The one I find most useful is tracing:

class Module
TRACE_LEVEL = [0]

decorator
def traced(name, meth)
lambda do |*args|
s = '. ' * TRACE_LEVEL[0]
puts s + "calling #{name}(#{args.map{|a|a.inspect}.join(',')})"
TRACE_LEVEL[0] += 1
r = begin
begin
meth.bind(self).call(*args)
ensure
TRACE_LEVEL[0] -= 1
end
rescue => ex
puts(s + "! #{ex.class}: " + ex)
raise ex
end
puts(s + '=> ' + r.inspect)
return r
end
end
end

Then you can turn tracing on (or off) for any function easily:

class Calc
class << self
traced
def fact(n)
return 1 if n < 2
return n * fact(n - 1)
end

traced
def bomb(n)
raise 'boo' if n < 2
return n * bomb(n - 1) if n < 5
begin
return n * bomb(n - 1)
rescue
return n * fact(n - 1)
end
end
end
end

Calc.fact(5)
Calc.bomb(5)

I've also used decorators to do database object lookups automatically.
For example, assume that a (hypothetical) web framework will call
Profile.show("37") when the user makes a request.

class Module
decorator
def lookup(name, f)
lambda do |id|
f.bind(self).call(self.class.find(id.to_i))
end
end
end

class Profile
lookup
def show(profile)
return profile.name + ' is a nice person.'
end

lookup
def edit(profile)
end
end

Type checking (if you like that sort of thing):

(This one requires a small change that I will post shortly.)

class Module
decorator
def checked(name, f, types)
lambda do |*args|
[args, types].transpose.each do |a, t|
raise TypeError if !a.is_a?(t)
end
f.bind(self).call(*args)
end
end
end

class C
checked Integer, String
def warn(level, message)
STDERR.puts '!'*level + message
end
end

You can also do general pre- and postconditions.

Deprecation warnings:

(Adapted from http://wiki.python.org/moin/PythonDecora....)

class Module
decorator
def deprecated(name, f)
lambda do |*args|
STDERR.puts "Warning: function #{name} is deprecated."
f.bind(self).call(*args)
end
end
end

kr

Phrogz

8/9/2007 3:36:00 AM

0

On Aug 6, 10:23 am, Trans <transf...@gmail.com> wrote:
> I'm not sure how I feel about the use of declarative style. I'm not a
> big fan of public, private, protected to begin with b/c of
....
> Though it's a bit less convenient, it might be better to just name the
> method:
>
> class C
> def tak(x, y, z)
> return z if x <= y
> tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
> end
> memoized :tak
> end

Very much for reasons like this do I keepcauggesting that def be a
statement that returns the defined method instance. Then you could do:

class C
memoized def tak( x, y, z )
#...
end
end

Further (not that I know python) I imagine this would enable the
"stacking" that the OP wanted:

class C
memoized awesomificated def tak( ... )
#...
end
end

....as long as the decorating methods in play kept on returning the
method being affected.

Trans

8/28/2007 3:11:00 PM

0



On Aug 7, 1:45 pm, "Keith Rarick" <k...@essembly.com> wrote:
> Perhaps the following notation would be better:
>
> class C
> decorate :memoized
> def tak(x, y, z)
> return z if x <= y
> tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
> end
> end
>
> It would obviate the need to redefine thedecoratormethod itself and
> thus simplify the implementation. Also, this notation is more explicit
> about the mechanism.

That's not a bad idea really. It would make it clear when a decorator
is being used too.

> > Though it's a bit less convenient, it might be better to
> > just name the method:
>
> > class C
> > def tak(x, y, z)
> > return z if x <= y
> > tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
> > end
> > memoized :tak
> > end
>
> > Unfortunately, not as nice, but the underlying code would
> > certainly get simplified.
>
> Yes, the implementation would be pretty easy. (There's even a similar
> example, called "once", in the pickaxe book.) However, putting thedecoratorat the bottom makes it easy to miss, especially if the
> method body is long.
>
> > Dreaming a little. I wonder, if there were a callback for when
> > a class/module closes, then maybe you do do it lazily?
>
> Yeah, when I started thinking about how to do this I looked for such a
> callback but didn't find one.
>
> > Also, I
> > wonder if this corresponds to Matz' idea of ":"-notation he
> > used for pre and post. So,
>
> > class C
> > def tak:memoized(x, y, z)
> > return z if x <= y
> > tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
> > end
> > end
>
> That's very interesting! I didn't know about that notation before. I
> just read about pre, post, and wrap methods, which seem similar but
> less useful. They are invoked at the method call, rather than the
> method definition, so they have less chance to affect the method's
> interface.
>
> Now, if you could define arbitrary methods to be used with the
> ":"-notation, like def tak:memoized(x, y, z) in your example above,
> that would be really useful.

I think it could be pretty useful too. Haven't heard Matz talk about
this notation in along time though.

> > Oh, one last thing. Could you give some other examples?

[snip]

Excellent examples, thank you.

If your okay with it, I'd like to give this consideration for
inclusion in Facets.

T.


Keith Rarick

8/28/2007 9:35:00 PM

0

On 8/28/07, Trans <transfire@gmail.com> wrote:
> If your okay with it, I'd like to give this consideration for
> inclusion in Facets.

Absolutely. I'd be delighted if you decide to include this. I'll post
an updated version shortly that uses the "decorate :memoized"
notation. Feel free to include that version if you prefer.

kr

Marc Heiler

8/28/2007 10:22:00 PM

0

A simple question, what's it used for?
--
Posted via http://www.ruby-....

Trans

8/28/2007 11:02:00 PM

0



On Aug 28, 3:22 pm, Marc Heiler <sheve...@linuxmail.org> wrote:
> A simple question, what's it used for?

I think the examples in kr's post give a good idea of it's uses:

http://groups.google.com/group/ruby-talk-google/msg/85bd05e0c7e33b46?...

T.


Daniel DeLorme

8/29/2007 10:51:00 PM

0

Trans wrote:
>
> On Aug 7, 1:45 pm, "Keith Rarick" <k...@essembly.com> wrote:
>> Perhaps the following notation would be better:
>>
>> class C
>> decorate :memoized
>> def tak(x, y, z)
>> return z if x <= y
>> tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
>> end
>> end
>>
>> It would obviate the need to redefine thedecoratormethod itself and
>> thus simplify the implementation. Also, this notation is more explicit
>> about the mechanism.
>
> That's not a bad idea really. It would make it clear when a decorator
> is being used too.

what about:
class C
decorate :memoized do
def ...
end
end

That makes the implementation much more simple and solid than relying on
method_added which may be incorrectly overridden somewhere.

Daniel

Keith Rarick

8/29/2007 11:41:00 PM

0

On 8/29/07, Daniel DeLorme <dan-ml@dan42.com> wrote:
> what about:
> class C
> decorate :memoized do
> def ...
> end
> end
>
> That makes the implementation much more simple and solid than relying on
> method_added which may be incorrectly overridden somewhere.

I like that. I think the gain in robustness more than offsets the
slight degradation (IMO) in readability. What would it look like for
class methods? Something like this? I don't know how I would implement
example 2 below.

class C

# Example 1 (instance method)
decorate :memoized do
def tak(x, y, z)
return z if x <= y
tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
end
end

# Example 2 (class method)
decorate :memoized do
def self.tbk(x, y, z)
return z if x <= y
tbk(tbk(x - 1, y, z), tbk(y - 1, z, x), tbk(z - 1, x, y))
end
end

# Example 3 (class method)
class << self
decorate :memoized do
def tck(x, y, z)
return z if x <= y
tck(tck(x - 1, y, z), tck(y - 1, z, x), tck(z - 1, x, y))
end
end
end

end

kr

Trans

8/30/2007 12:02:00 AM

0



On Aug 29, 4:40 pm, "Keith Rarick" <k...@essembly.com> wrote:
> On 8/29/07, Daniel DeLorme <dan...@dan42.com> wrote:
>
> > what about:
> > class C
> > decorate :memoized do
> > def ...
> > end
> > end
>
> > That makes the implementation much more simple and solid than relying on
> > method_added which may be incorrectly overridden somewhere.
>
> I like that. I think the gain in robustness more than offsets the
> slight degradation (IMO) in readability. What would it look like for
> class methods? Something like this? I don't know how I would implement
> example 2 below.
>
> class C
>
> # Example 1 (instance method)
> decorate :memoized do
> def tak(x, y, z)
> return z if x <= y
> tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
> end
> end
>
> # Example 2 (class method)
> decorate :memoized do
> def self.tbk(x, y, z)
> return z if x <= y
> tbk(tbk(x - 1, y, z), tbk(y - 1, z, x), tbk(z - 1, x, y))
> end
> end
>
> # Example 3 (class method)
> class << self
> decorate :memoized do
> def tck(x, y, z)
> return z if x <= y
> tck(tck(x - 1, y, z), tck(y - 1, z, x), tck(z - 1, x, y))
> end
> end
> end
>
> end

Just a thought...

There's this trick I always want to use to define class-level module
methods that would be "inherited" through the class hierarchy.
Unfortunately it's doesn't work because the method still winds up in a
class rather than a module. However, it can be used for other things.
And this might be a good case. Using your examples:

# Example 1 (instance method)

def memoized.tak(x, y, z)
return z if x <= y
tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y))
end

# Example 3 (class method)

class << self
def memoized.tck(x, y, z)
return z if x <= y
tck(tck(x - 1, y, z), tck(y - 1, z, x), tck(z - 1, x, y))
end
end

Interestingly, the only place I've used this trick so far is for
#meta, which allows me to define class methods by-passing access
privacy. For instance I can use it to do:

class X
meta.attr_writer :foo
end

instead of

class X
class << self
attr_writer :foo
end
end

It should even be possible to combine this with example #3 to do:

# Example 3 (class method)

def meta.memoized.tck(x, y, z)
return z if x <= y
tck(tck(x - 1, y, z), tck(y - 1, z, x), tck(z - 1, x, y))
end

T.