magic in ruby
DESCRIPTION
Method Swizzling Method Decorator RefinementTRANSCRIPT
Magic in RubyDavid Lin
<beer> Oct. 18, 2013</beer>
Suppose You Know
1. Ruby Programming Language - class, module, method - block - mixin (include, extend)2. Metaprogramming - class_eval - define_method3. Monkey Patching
Outline
1. Smalltalk-style method calling2. Method swizzling3. Decorator4. Refinement
Rubyis
Smalltalk-style!Objective-C, too.
丟訊息給物件看看物件有什麼反應
obj.hello(“world”)
obj.hello(“world”)
這是 message
obj.hello(“world”)
這是 receiver
obj.hello(“world”)
obj.send(:hello, “world”)
Yes, it sends a message!
obj 接收了message內容為 hello(“world”)
現在 obj 就要
暴走同步率400%
obj 找一個對應的method body
根據 message name :hello
然後...引爆 invoke
呼叫這個 method body
DEFINEA METHOD
事實上是定義一個 method body然後連到對應的 message name
def hello(s)define a method named ‘hello’
define a method for the name ‘hello’
a method body :hello
method body&
message name沒有綁死在一起而是可以改變的
HACK IT!Let’s change behaviour
MethodSwizzling把 method body “抽換”掉
Example.rb
class MyObj def hello(s) “Hello #{s}” endend
method body A
:hello
Hack1.rb (part 1)
class MyObj alias_method :hello_original, :helloend
method body A
:hello
:hello_original
Hack1.rb (part 2)
class MyObj def hello_with_bracket(s) “{” + hello_original(s) + ”}” endend
method body B
:hello_with_bracket
:hello_original
send message
Hack1.rb (part 3)
class MyObj alias_method :hello, :hello_with_bracketend
method body B
:hello_with_bracket
:hellomethod body A
Hack1.rb (final)
method body B
:hello_with_bracket
:hello_original
send message
:hello
method body A
可不可以更簡潔些?
太多沒必要的 message names
Hack2.rb (to expect)
method body B
:hello_with_bracket
:hello_original
send message
:hello
method body A
call directly
Hack2.rb (to expect)
method body B
:hello
method body A
call directly
Hack2.rb (yes, that’s all)
class MyObj m = instance_method(:hello)
define_method(:hello) do |s|
“{” + m.bind(self).call(s) + “}”
end
end
Hack2.rb
class MyObj m = instance_method(:hello)
define_method(:hello) do |s|
“{” + m.bind(self).call(s) + “}”
end
end
method body A
:hello
m (a local var)
Hack2.rb
class MyObj m = instance_method(:hello)
define_method(:hello) do |s|
“{” + m.bind(self).call(s) + “}”
end
end
method body A
:hello
method body B
Hack2.rb (Ah, ha!)
class MyObj m = instance_method(:hello)
define_method(:hello) do |s|
“{” + m.bind(self).call(s) + “}”
end
end
method body A
:hellomethod body B
call directly
DecoratorMethod Wrapping
(it’s more complicant than Python, though)
Example (Unsafe!)
def send_money(from, to, amount)
from.balance -= amount
to.balance += amount
from.save!
to.save!
end
Example (Safer)
def send_money(from, to, amount)
ActiveRecord::Base.transcation do from.balance -= amount
to.balance += amount
from.save!
to.save!
end
end
Example in Ruby
def send_money(from, to, amount)
ActiveRecord::Base.transcation do # do operations
end
end
Example in Python
def send_money(from, to, amount):
try:
db.start_transcation()
# do operations
db.commit_transcation()
except:
db.rollback_transcation()
raise
Decorating in Python
@transcational
def send_money(from, to, amount):
# do operations
def send_money(from, to, amount):
# do operations
send_money = transcational(send_money)
Form 2:
Form 1:
Decorator in Python
def transcational(func):
def func2(*args):
try:
db.start_transcation()
func(*args) # call decoratee
db.commit_transcation()
except:
db.rollback_transcation()
raise
return func2
Decorating in Ruby
class Bank
extend Transcational # include decorator
def send_money(from, to, amount)
# do operations end
transcational :send_money # decorate!
end
Decorator in Ruby
module Transcational
def transcational(mthd_name)
mthd = instance_method(mthd_name)
define_method(mthd_name) do |*args, &blk|
ActiveRecord::Base.transcation { # call decoratee
mthd.bind(self).call(*args, &blk)
}
end
end
end
這只是Method
Swizzlingsince Ruby has methods but functions
RefinementFor Ruby 2.0+
當你要...Monkey Patching
Modify existing classes of other libs
Instead of monkey patching
class KlassOrModule # define instance methods...end
module MyLibrary # using modified KlassOrModule...end
住手!You’re gonna RUIN EVERYTHING!
Use refinement
module MyLibrary refine KlassOrModule do # define instance methods... end # using modified KlassOrModule...end
Example
module MyLibrary refine String do # modify String def hello “Hello #{self}” end endend
Example (cont.)
# Outside MyLibrary
puts “Kitty”.hello# => NoMethodError: hello
Example (cont.)
# Outside MyLibrary
using MyLibrary
puts “Kitty”.hello# => Hello Kitty
Inside Refinement
Actually, refinement creates an annoymous module to mixin a class.
module MyLib refine String do puts self.is_a?(Module) # => true puts self
# => #<refinement:String@MyLibrary>
end
end
Inside Refinement
module MyLib refine String do
end
end
module<refinement:String@MyLibrary>
Automatic Mixining自動化MIXIN
It is ...
When using MyLibrary ...
every new instance of String is extended!
using MyLibrary
puts “x”.hello # “x” is extendedputs String.new(“x”).hello
using is lexical in scopejust like Java’s import
using is lexical
1. Refinements are activated only at top-level - Not inside class, module, and method scope
2. Scope of the effect is the whole source code
using is lexical
Please DO NOT…
class Foo using MyLibend
module Moo using MyLibend
def bar using MyLibend
using is lexical
# refinement of MyLib is deactived
# “World”.hello # => NoMethodError
using MyLib # activate!
# refinement of MyLib is activated
def hello_world
“World”.hello
end
# END OF FILE
Scope of MyLibRefinement
Refinement in Ruby
1. Actually, it is based on Mixining2. Avoid global scope corruption3. Prefer lexical scope to runtime scope => easy to use, just like import in Java
Better Refinementthan
Monkey Patchingplease, get rid of monkey patching.
魔法のRubyまほうのRuby
本簡報用高橋流製作
在此向たかはしさん致敬
<FIN>