Magic in ruby

Post on 15-Jan-2015

443 views 1 download

Tags:

description

Method Swizzling Method Decorator Refinement

Transcript of Magic in ruby

Magic in RubyDavid Lin

davll.xc@gmail.com

<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>