Magic in ruby

63
Magic in Ruby David Lin [email protected] <beer> Oct. 18, 2013 </beer>

description

Method Swizzling Method Decorator Refinement

Transcript of Magic in ruby

Page 1: Magic in ruby

Magic in RubyDavid Lin

[email protected]

<beer> Oct. 18, 2013</beer>

Page 2: Magic in ruby

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

Outline

1. Smalltalk-style method calling2. Method swizzling3. Decorator4. Refinement

Page 4: Magic in ruby

Rubyis

Smalltalk-style!Objective-C, too.

Page 5: Magic in ruby

丟訊息給物件看看物件有什麼反應

Page 6: Magic in ruby

obj.hello(“world”)

Page 7: Magic in ruby

obj.hello(“world”)

這是 message

Page 8: Magic in ruby

obj.hello(“world”)

這是 receiver

Page 9: Magic in ruby

obj.hello(“world”)

Page 10: Magic in ruby

obj.send(:hello, “world”)

Yes, it sends a message!

Page 11: Magic in ruby

obj 接收了message內容為 hello(“world”)

Page 12: Magic in ruby

現在 obj 就要

Page 13: Magic in ruby

暴走同步率400%

Page 14: Magic in ruby

obj 找一個對應的method body

根據 message name :hello

Page 15: Magic in ruby

然後...引爆 invoke

呼叫這個 method body

Page 16: Magic in ruby

DEFINEA METHOD

事實上是定義一個 method body然後連到對應的 message name

Page 17: Magic in ruby

def hello(s)define a method named ‘hello’

define a method for the name ‘hello’

a method body :hello

Page 18: Magic in ruby

method body&

message name沒有綁死在一起而是可以改變的

Page 19: Magic in ruby

HACK IT!Let’s change behaviour

Page 20: Magic in ruby

MethodSwizzling把 method body “抽換”掉

Page 21: Magic in ruby

Example.rb

class MyObj def hello(s) “Hello #{s}” endend

method body A

:hello

Page 22: Magic in ruby

Hack1.rb (part 1)

class MyObj alias_method :hello_original, :helloend

method body A

:hello

:hello_original

Page 23: Magic in ruby

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

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

Hack1.rb (final)

method body B

:hello_with_bracket

:hello_original

send message

:hello

method body A

Page 26: Magic in ruby

可不可以更簡潔些?

太多沒必要的 message names

Page 27: Magic in ruby

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

Hack2.rb (to expect)

method body B

:hello

method body A

call directly

Page 29: Magic in ruby

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

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

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

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

DecoratorMethod Wrapping

(it’s more complicant than Python, though)

Page 34: Magic in ruby

Example (Unsafe!)

def send_money(from, to, amount)

from.balance -= amount

to.balance += amount

from.save!

to.save!

end

Page 35: Magic in ruby

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

Example in Ruby

def send_money(from, to, amount)

ActiveRecord::Base.transcation do # do operations

end

end

Page 37: Magic in ruby

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

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

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

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

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

這只是Method

Swizzlingsince Ruby has methods but functions

Page 43: Magic in ruby

RefinementFor Ruby 2.0+

Page 44: Magic in ruby

當你要...Monkey Patching

Modify existing classes of other libs

Page 45: Magic in ruby

Instead of monkey patching

class KlassOrModule # define instance methods...end

module MyLibrary # using modified KlassOrModule...end

Page 46: Magic in ruby

住手!You’re gonna RUIN EVERYTHING!

Page 47: Magic in ruby

Use refinement

module MyLibrary refine KlassOrModule do # define instance methods... end # using modified KlassOrModule...end

Page 48: Magic in ruby

Example

module MyLibrary refine String do # modify String def hello “Hello #{self}” end endend

Page 49: Magic in ruby

Example (cont.)

# Outside MyLibrary

puts “Kitty”.hello# => NoMethodError: hello

Page 50: Magic in ruby

Example (cont.)

# Outside MyLibrary

using MyLibrary

puts “Kitty”.hello# => Hello Kitty

Page 51: Magic in ruby

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

Inside Refinement

module MyLib refine String do

end

end

module<refinement:String@MyLibrary>

Page 53: Magic in ruby

Automatic Mixining自動化MIXIN

It is ...

Page 54: Magic in ruby

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

using is lexical in scopejust like Java’s import

Page 56: Magic in ruby

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

using is lexical

Please DO NOT…

class Foo using MyLibend

module Moo using MyLibend

def bar using MyLibend

Page 58: Magic in ruby

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

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

Better Refinementthan

Monkey Patchingplease, get rid of monkey patching.

Page 61: Magic in ruby

魔法のRubyまほうのRuby

Page 62: Magic in ruby

本簡報用高橋流製作

在此向たかはしさん致敬

Page 63: Magic in ruby

<FIN>