Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > python-style decorators

Reply
Thread Tools

python-style decorators

 
 
Keith Rarick
Guest
Posts: n/a
 
      08-03-2007
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-forum.com/attachment/52/deco.rb

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

 
Reply With Quote
 
 
 
 
Trans
Guest
Posts: n/a
 
      08-06-2007


On Aug 3, 4:53 pm, Keith Rarick <(E-Mail Removed)> 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/52/deco.rb


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.


 
Reply With Quote
 
 
 
 
Keith Rarick
Guest
Posts: n/a
 
      08-07-2007
On 8/6/07, Trans <(E-Mail Removed)> 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/PythonDecoratorLibrary.)

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

 
Reply With Quote
 
Phrogz
Guest
Posts: n/a
 
      08-09-2007
On Aug 6, 10:23 am, Trans <(E-Mail Removed)> 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.

 
Reply With Quote
 
Trans
Guest
Posts: n/a
 
      08-28-2007


On Aug 7, 1:45 pm, "Keith Rarick" <(E-Mail Removed)> 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.


 
Reply With Quote
 
Keith Rarick
Guest
Posts: n/a
 
      08-28-2007
On 8/28/07, Trans <(E-Mail Removed)> 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

 
Reply With Quote
 
Marc Heiler
Guest
Posts: n/a
 
      08-28-2007
A simple question, what's it used for?
--
Posted via http://www.ruby-forum.com/.

 
Reply With Quote
 
Trans
Guest
Posts: n/a
 
      08-28-2007


On Aug 28, 3:22 pm, Marc Heiler <(E-Mail Removed)> 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-...7e33b46?hl=en&

T.


 
Reply With Quote
 
Daniel DeLorme
Guest
Posts: n/a
 
      08-29-2007
Trans wrote:
>
> On Aug 7, 1:45 pm, "Keith Rarick" <(E-Mail Removed)> 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

 
Reply With Quote
 
Keith Rarick
Guest
Posts: n/a
 
      08-29-2007
On 8/29/07, Daniel DeLorme <(E-Mail Removed)> 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

 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Decorators v Inheritance: Forcing subclass to call super() ? Michael Strorm Java 12 04-20-2005 08:05 PM
PEP 318 decorators are not Decorators Arien Malec Python 11 08-16-2004 06:38 PM
RE: Using metaclasses to play with decorators. Robert Brewer Python 9 06-26-2004 12:58 AM
Using metaclasses to play with decorators. Michael Sparks Python 4 06-18-2004 06:25 AM
Re: Using metaclasses to play with decorators. Michael Sparks Python 6 06-18-2004 05:04 AM



Advertisments