On 8/6/07, Trans [email protected] 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.
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