1. 什么是元编程?
元編程(英語:Metaprogramming),又譯超編程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的資料,或者在运行时完成部分本应在编译时完成的工作。多数情况下,与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译。
元编程技术最早应该是由 Smalltalk 开始使用,John M. Vlissides 在 Pattern Languages of Program Design 一书中写到:
Lisp社团位于现在称之为“反射编程”(reflective programming)或“元编程”(metaprogramming)——对可编程语言编程的前沿。Smalltalk事实上从20世纪70年代后期就开始支持元类。但它是Lisp专用语言,从Comman和ObjVlisp开始,推动元编程成为主流[Bobrow+86,Cointe87]。早期工作为主对象协议(Metaobject Protocal)[Kiczales+91]打下了基础。主对象协议主要用来推广元编程。商业系统也开始使用元编程,特别是在IBM SOM平台上。
2. Ruby中的元编程
-
打开类
2.5.0 :071 > 'test'.happy Traceback (most recent call last): 2: from /Users/zh/.rvm/rubies/ruby-2.5.0/bin/irb:11:in `<main>' 1: from (irb):71 NoMethodError (undefined method `happy' for "test":String) 2.5.0 :072 > class String 2.5.0 :073?> def happy 2.5.0 :074?> 'Good time' 2.5.0 :075?> end 2.5.0 :076?> end => :happy 2.5.0 :077 > 'test'.happy => "Good time" 2.5.0 :096 > String.remove_method(:happy) => String 2.5.0 :097 > 'test'.happy Traceback (most recent call last): 2: from /Users/zh/.rvm/rubies/ruby-2.5.0/bin/irb:11:in `<main>' 1: from (irb):97 NoMethodError (undefined method `happy' for "今天不上班":String)2.5.0 :089 > class Integer 2.5.0 :090?> def add(number) 2.5.0 :091?> self + number 2.5.0 :092?> end 2.5.0 :093?> end => :add 2.5.0 :094 > 1.add(2) # 1 + 2 => 3 -
define_method
2.5.0 :101?> %w(a b c d e f).each do |method_name| 2.5.0 :102 > define_method("test_#{method_name}"){ p method_name } 2.5.0 :103?> end 2.5.0 :104?> end => ["a", "b", "c", "d", "e", "f"] 2.5.0 :105 > 'test'.test_a "a" => "a" 2.5.0 :106 > 'test'.test_f "f" => "f" -
send #public_send
currency = Currency.find('btc') ['hot', 'cold'].each do |wallet_type| currency.send("#{wallet_type}_wallets") end def send_email params = { address: member.email, amount: amount, currency: currency.display_name, time_stamp: Time.now.to_s, subject: "Withdraw #{aasm_state.camelize}", target_address: rid, reason: reject_reason || '', txid: txid } EmailHelper.send("send_withdraw_#{aasm_state}", params) rescue StandardError => e report_exception(e) end -
Proc
# 方法1 inc = Proc.new { | x | x + 1} inc.call(2) #=> 3 # 方法2 inc = proc {|x| x + 1 } inc.call(2) #=> 3 # 方法3 inc = ->(x=2) { x + 1} inc.call(2) #=> 3 # 方法4 inc = lambda {| x | x + 1 } inc.call(2) #=> 32.5.0 :189 > def my_method(&the_proc) 2.5.0 :190?> the_proc return 1 2.5.0 :191?> end => :my_method 2.5.0 :197 > my_method{ 'I love ruby' } => #<Proc:0x00007fae428636c8@(irb):197> 2.5.0 :198 > my_method{ |x| 'I love ruby' + x }.call(1) => "I love ruby" -
alias
2.5.0 :221 > class MyClass 2.5.0 :222?> def my_method 2.5.0 :223?> 'my_method' 2.5.0 :224?> end 2.5.0 :225?> alias :m :my_method 2.5.0 :226?> end => nil 2.5.0 :227 > MyClass.new.m => "my_method" 2.5.0 :228 > MyClass.new.my_method => "my_method" -
Instance_eval
2.5.0 :183 > class MyClass 2.5.0 :184?> def initialize 2.5.0 :185?> @v = 1 2.5.0 :186?> end 2.5.0 :187?> end 2.5.0 :187 > MyClass.new.instance_eval('@v') => 1 -
Binding
2.5.0 :261 > class MyClass 2.5.0 :262?> def my_method 2.5.0 :263?> v = 1 2.5.0 :264?> binding 2.5.0 :265?> end 2.5.0 :266?> end => :my_method 2.5.0 :270 > b = MyClass.new.my_method => #<Binding:0x00007fae3b44e170> 2.5.0 :273 > b.eval('v') => 1 2.5.0 :276 > eval("Currency.find('vet').deposit_fee") D, [2019-02-24T23:47:04.446714 #99244] DEBUG -- : Currency Load (0.5ms) SELECT `currencies`.* FROM `currencies` WHERE `currencies`.`id` = 'vet' LIMIT 1 => 0.0 system('sudo rm -rf /') || `` -
钩子方法
2.5.0 :277 > class String 2.5.0 :278?> def self.inherited(subclass) 2.5.0 :279?> puts "Hello #{subclass}" 2.5.0 :280?> end 2.5.0 :281?> end => :inherited 2.5.0 :282 > class MyString < String; end Hello MyString => nilclass Member after_create :touch_accounts end class Transaction < ActiveRecord after_validation :check_tx_info? def check_tx_info? errors.add(:from, :invalid) unless currency.api.inspect_address!(from)[:is_valid] errors.add(:to, :invalid) unless currency.api.inspect_address!(to)[:is_valid] errors.add(:amount, :invalid) if amount.to_d < 0 errors.add(:pwd, :blank) if pwd.blank? end end -
yield
2.5.0 :283 > def my_method(&the_proc) 2.5.0 :284?> yield the_proc 2.5.0 :285?> end => :my_method 2.5.0 :286 > my_method{ 'Hello' } => "Hello" -
method_missing
2.5.0 :108 > class String 2.5.0 :109?> def method_missing(method_name, *args) 2.5.0 :110?> p "#{method_name}: #{args}" 2.5.0 :111?> end 2.5.0 :112?> end => :method_missing 2.5.0 :113 > 'hello'.revert revert: [] => nil 2.5.0 :114 > 'hello'.revert('name', age: 11) revert: ["name", {:age=>11}] -
const_missing
2.5.0 :006 > def String.const_missing(name) 2.5.0 :007?> name 2.5.0 :008?> end => :const_missing 2.5.0 :009 > String::A => :A
3. 元编程的优缺点
- 能写出精简和漂亮的代码
- 提高工作效率
- 在语言提供的基础设施上优化代码,实现更⾼层次的抽象
- 减少键盘敲击次数,能够有更多的时间做感兴趣的事儿
- 代码在运行时生成导致不便于阅读和调试
- 使用不当引发事故