今天看到陈老师的好文《Python 修饰器的函数式编程》http://coolshell.cn/articles/11265.html
Ruby 怎么写?
支持 meta programming 的语言都很容易做到这个事情,python 是做了个语法糖方便调用,而 ruby 没有内置的语法糖,拿对应的例子来说,先来看给函数打日志,对应的 ruby 版本:
module Decorator
def logger(fn)
m = Module.new do
define_method(fn) do |*args|
result = super(*args)
puts "function = #{fn}"
puts " arguments = #{args.join(',')}"
puts " result = #{result}"
result
end
end
prepend m
end
end
class Calculator
extend Decorator
logger(:multipy)
def multipy(x, y)
x * y
end
logger(:sum_num)
def sum_num(n)
1.upto(n).inject(:+)
end
end
Calculator.new.multipy(2,10)
Calculator.new.sum_num(100)
创建一个 module,在里面重新定义这个函数,然后 prepend 这个 module,用这个 module 也很简单,只要 extend 一下,和 python 的语法糖相比,就是要多打入函数名。
然后看对应的斐波拉契 memo 例子:
module Decorator
def memo(fn)
m = Module.new do
cache = {}
define_method(fn) do |*args|
cache[args] ||= super(*args)
end
end
prepend m
end
end
class Fibonacci
extend Decorator
memo(:fib)
def fib(n)
n < 2 ? n : fib(n - 1) + fib(n - 2)
end
end
Fibonacci.new.fib(100)
可以看到,和第一个例子的流程是一样的:
不同的 decorator 只是在第 3 步要做不同的事情,只要封装一下上面的逻辑,就可以做一个类似 python 的 wraps 类出来。
ruby 的 method 不是 object,所以不能像 python 那样,传入一个 function 再传出一个 function。类型系统的一大败笔
#3 楼 @reus Ruby method 可以是对象 http://www.ruby-doc.org/core-2.1.1/Method.html 顺便山寨一下 makeHtmlTag 那个例子
module Decrorator
def wrap method_name
WarppedObj.new method_name, self
end
class WarppedObj
def initialize method_name, context
@method_name = method_name
@context = context
context.class_eval do
@@_warped_counter ||= {}
@@_warped_counter[method_name] ||= 0
end
end
def with method_name, *args
warpped_method = @method_name
@context.class_eval do
@@_warped_counter[warpped_method] += 1
alias_method :"#{@@_warped_counter[warpped_method]}_#{warpped_method}", warpped_method
i = @@_warped_counter[warpped_method]
define_method warpped_method do |*_args, &_blk|
ret = __send__ :"#{i}_#{warpped_method}", *_args, &_blk
__send__(method_name, *args) {ret}
end
end
end
end
end
class HTMLHelper
extend Decrorator
def makeHtmlTag tag, opts
"<#{tag} #{opts.map {|key, value| %Q{#{key}="#{value}"}}.join(' ')}>" + yield + "</#{tag}>"
end
def hello
'hello world'
end
wrap(:hello).with :makeHtmlTag, 'i', class: 'italic_css'
wrap(:hello).with :makeHtmlTag, 'b', class: 'bold_css'
end
puts HTMLHelper.new.hello # <b class="bold_css"><i class="italic_css">hello world</i></b>
一点个人看法 这个方法是不错,不过貌似 Ruby 一般都不这么写,比较复杂的话,就写个语法把方法名字注入,比如 before_filter
或者简单的就类似于 lambda 或者 proc 的东西写进去,比如 scope
#4 楼 @piecehealth 那是包装了 method 的 Method,不是 method 本身。如果 method 是一个可以被引用的对象,那 decorator 就只是一个高阶函数,接受一个 method 作为参数,输出另一个 method。你可以传递 Method 对象,但 Method 对象的调用方式和普通 method 不同,不能把他们看作是同样的东西。在 method 是 object 的语言里就不需要区分。
def hello method
puts "hello, #{method.name}"
method.call
puts "goodby, #{method.name}"
end
def foo
puts "i am foo"
end
hello method(:foo)
#9 楼 @piecehealth 传递的是 Method object,不是 method。假设 ruby 的 method 是对象,且可以用~引用,则会写成
def hello m
...
m
...
end
def foo end
hello ~foo
而不需要包装成 Method object,也不需要 call。memo decorator 会写成
def memo fn
cache = {}
def wrapper *args
return cache[args] if cache.has_key? args
ret = fn *args
cache[args] = ret
ret
end
~wrapper
end
def fib n
...
end
fib = memo ~fib
fib 10 # 不需要fib.call 10
和 python 的类似。method/function 是一个 object/value 的语言就可以这样写,ruby 不是所以不能。
还有一个区别,如果 method 是 object,那在 def ... end 之后,这个 object 就已经存在了,不需要另外创建或者包装
Ruby is NOT a Callable Oriented Language (It’s Object Oriented)
http://yehudakatz.com/2010/02/21/ruby-is-not-a-callable-oriented-language/
As with Python, in Ruby,… Everything is an object
https://www.ruby-lang.org/en/documentation/ruby-from-other-languages/to-ruby-from-python/
#12 楼 @reus
因为 Python 中的 foo 跟 foo() 不是一个东西,所以用 Python 写起来更自然。
对方法的引用 Python 是 foo,而 Ruby 是 self.method(:foo),其间并没有另外创建或者包装
,因为这个 method object 实际就存在,只是通过一种很不 python 的方式取 (既 self.method(:foo))。
同样,调用一个作为参数的方法 Python 也更简单,加个括号foo()
就行了,Ruby 就只能傻傻的 call 了,但是我认为本质都是一样的,只是 python 的马甲 (语法糖) 更好看一些就是了。
不过话说回来,个人认为修饰器
不是很 Ruby way,如#6 楼所说,当你用 Ruby 怀念修饰器
的时候,肯定有别的更合适的方案,就算非要用修饰器
,自己写一个满足自己需求的也不是很难,一楼已经证明。
#14 楼 @piecehealth 每次调用 method 方法都会产生新的 Method 对象。Method 只是对 method 的包装,并不是 method 本身
def foo
end
puts method(:foo).object_id
puts method(:foo).object_id
puts method(:foo).object_id
def hello(method2)
puts "hello, #{method2.name}"
method2.call
puts "goodby, #{method2.name}"
end
def foo
puts "i am foo"
end
hello method(:foo)
我简单修改了下,这不就是简单的装饰么?有什么问题?
def log(m)
p m.name
end
class Calculator
def self.multipy(x, y)
x * y
end
def self.sum_num(n)
1.upto(n).inject(:+)
end
log method(:sum_num)
log method(:multipy)
end
Calculator.multipy(2,10)
#Calculator.new.sum_num(100)
这样基本可以,就是那个参数没法传递,可有办法么?
不用生搬硬套吧,利用 def ... end
返回的 symbol 做 meta programming 就可以了,连这样都可以:
public void static def main argc, *argv
...
end
#4 楼 @piecehealth 你好。刚开始看有些头晕,今天睡醒了再看一次,感觉基本看懂了。 我想把这个用在我写的免费教程的高级篇,想征求你同意,谢谢了。