magic in ruby
DESCRIPTION
Method Swizzling Method Decorator RefinementTRANSCRIPT
![Page 1: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/1.jpg)
Magic in RubyDavid Lin
<beer> Oct. 18, 2013</beer>
![Page 2: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/2.jpg)
Suppose You Know
1. Ruby Programming Language - class, module, method - block - mixin (include, extend)2. Metaprogramming - class_eval - define_method3. Monkey Patching
![Page 3: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/3.jpg)
Outline
1. Smalltalk-style method calling2. Method swizzling3. Decorator4. Refinement
![Page 4: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/4.jpg)
Rubyis
Smalltalk-style!Objective-C, too.
![Page 5: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/5.jpg)
丟訊息給物件看看物件有什麼反應
![Page 6: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/6.jpg)
obj.hello(“world”)
![Page 7: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/7.jpg)
obj.hello(“world”)
這是 message
![Page 8: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/8.jpg)
obj.hello(“world”)
這是 receiver
![Page 9: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/9.jpg)
obj.hello(“world”)
![Page 10: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/10.jpg)
obj.send(:hello, “world”)
Yes, it sends a message!
![Page 11: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/11.jpg)
obj 接收了message內容為 hello(“world”)
![Page 12: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/12.jpg)
現在 obj 就要
![Page 13: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/13.jpg)
暴走同步率400%
![Page 14: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/14.jpg)
obj 找一個對應的method body
根據 message name :hello
![Page 15: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/15.jpg)
然後...引爆 invoke
呼叫這個 method body
![Page 16: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/16.jpg)
DEFINEA METHOD
事實上是定義一個 method body然後連到對應的 message name
![Page 17: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/17.jpg)
def hello(s)define a method named ‘hello’
define a method for the name ‘hello’
a method body :hello
![Page 18: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/18.jpg)
method body&
message name沒有綁死在一起而是可以改變的
![Page 19: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/19.jpg)
HACK IT!Let’s change behaviour
![Page 20: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/20.jpg)
MethodSwizzling把 method body “抽換”掉
![Page 21: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/21.jpg)
Example.rb
class MyObj def hello(s) “Hello #{s}” endend
method body A
:hello
![Page 22: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/22.jpg)
Hack1.rb (part 1)
class MyObj alias_method :hello_original, :helloend
method body A
:hello
:hello_original
![Page 23: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/23.jpg)
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
![Page 24: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/24.jpg)
Hack1.rb (part 3)
class MyObj alias_method :hello, :hello_with_bracketend
method body B
:hello_with_bracket
:hellomethod body A
![Page 25: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/25.jpg)
Hack1.rb (final)
method body B
:hello_with_bracket
:hello_original
send message
:hello
method body A
![Page 26: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/26.jpg)
可不可以更簡潔些?
太多沒必要的 message names
![Page 27: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/27.jpg)
Hack2.rb (to expect)
method body B
:hello_with_bracket
:hello_original
send message
:hello
method body A
call directly
![Page 28: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/28.jpg)
Hack2.rb (to expect)
method body B
:hello
method body A
call directly
![Page 29: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/29.jpg)
Hack2.rb (yes, that’s all)
class MyObj m = instance_method(:hello)
define_method(:hello) do |s|
“{” + m.bind(self).call(s) + “}”
end
end
![Page 30: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/30.jpg)
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)
![Page 31: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/31.jpg)
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
![Page 32: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/32.jpg)
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
![Page 33: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/33.jpg)
DecoratorMethod Wrapping
(it’s more complicant than Python, though)
![Page 34: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/34.jpg)
Example (Unsafe!)
def send_money(from, to, amount)
from.balance -= amount
to.balance += amount
from.save!
to.save!
end
![Page 35: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/35.jpg)
Example (Safer)
def send_money(from, to, amount)
ActiveRecord::Base.transcation do from.balance -= amount
to.balance += amount
from.save!
to.save!
end
end
![Page 36: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/36.jpg)
Example in Ruby
def send_money(from, to, amount)
ActiveRecord::Base.transcation do # do operations
end
end
![Page 37: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/37.jpg)
Example in Python
def send_money(from, to, amount):
try:
db.start_transcation()
# do operations
db.commit_transcation()
except:
db.rollback_transcation()
raise
![Page 38: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/38.jpg)
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:
![Page 39: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/39.jpg)
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
![Page 40: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/40.jpg)
Decorating in Ruby
class Bank
extend Transcational # include decorator
def send_money(from, to, amount)
# do operations end
transcational :send_money # decorate!
end
![Page 41: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/41.jpg)
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
![Page 42: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/42.jpg)
這只是Method
Swizzlingsince Ruby has methods but functions
![Page 43: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/43.jpg)
RefinementFor Ruby 2.0+
![Page 44: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/44.jpg)
當你要...Monkey Patching
Modify existing classes of other libs
![Page 45: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/45.jpg)
Instead of monkey patching
class KlassOrModule # define instance methods...end
module MyLibrary # using modified KlassOrModule...end
![Page 46: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/46.jpg)
住手!You’re gonna RUIN EVERYTHING!
![Page 47: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/47.jpg)
Use refinement
module MyLibrary refine KlassOrModule do # define instance methods... end # using modified KlassOrModule...end
![Page 48: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/48.jpg)
Example
module MyLibrary refine String do # modify String def hello “Hello #{self}” end endend
![Page 49: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/49.jpg)
Example (cont.)
# Outside MyLibrary
puts “Kitty”.hello# => NoMethodError: hello
![Page 50: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/50.jpg)
Example (cont.)
# Outside MyLibrary
using MyLibrary
puts “Kitty”.hello# => Hello Kitty
![Page 51: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/51.jpg)
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
![Page 52: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/52.jpg)
Inside Refinement
module MyLib refine String do
end
end
module<refinement:String@MyLibrary>
![Page 53: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/53.jpg)
Automatic Mixining自動化MIXIN
It is ...
![Page 54: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/54.jpg)
When using MyLibrary ...
every new instance of String is extended!
using MyLibrary
puts “x”.hello # “x” is extendedputs String.new(“x”).hello
![Page 55: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/55.jpg)
using is lexical in scopejust like Java’s import
![Page 56: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/56.jpg)
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
![Page 57: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/57.jpg)
using is lexical
Please DO NOT…
class Foo using MyLibend
module Moo using MyLibend
def bar using MyLibend
![Page 58: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/58.jpg)
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
![Page 59: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/59.jpg)
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
![Page 60: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/60.jpg)
Better Refinementthan
Monkey Patchingplease, get rid of monkey patching.
![Page 61: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/61.jpg)
魔法のRubyまほうのRuby
![Page 62: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/62.jpg)
本簡報用高橋流製作
在此向たかはしさん致敬
![Page 63: Magic in ruby](https://reader034.vdocuments.site/reader034/viewer/2022052321/54b795464a7959db528b4b67/html5/thumbnails/63.jpg)
<FIN>