What have you found for these years?

2007-07-06

[Ruby] scope with eval and mixin

在 Ruby 中的 scope 全然是使用 lexical scope,
只是有一些例外!就在 eval 和 mixin 中。
先看 eval, Ruby 有三種 eval:

1. eval
2. module_eval (alias with class_eval)
3. instance_eval

第一個 eval 就像 PHP, Action Script 中的 eval,
也就是他吃一個字串,然後會以呼叫者的 scope 為準,而非
呼叫處的 scope. 也就是說:
class C; Fixnum.send :eval, 'puts self'; end
這樣會印出 Fixnum 而非 C

第二和第三個吃的參數比較多,除了 String 外,也吃 block
其 scope 有微小處差異,在於 class 與 instance 間的差異。
我們知道 class 其實也是 instance, 所以當我們寫:
Fixnum.module_eval{ puts self }

Fixnum.instance_eval{ puts self }
的結果都會是 Fixnum
那差異到底在哪?其實我越看越不覺得 Ruby 是 class-based,
而是 prototype-based, 因為其 class 與 instance 間的差異
微乎其微。class 只不過是能產生 instance 的東西,他算是
instance 的 super-set, instance 是閹割過後的 class...
他們之間決定性的差異在於 new 與 define_method 上。
前者沒有太多好說的,跟廢話差不多,後者就挺令人玩味了。

class C; def f; puts self; end; end
這樣是替 C 定義 instance method
C.new.f # -> <C:0x12345>
也就是說,如果在 class scope 中呼叫 define_method
(是的,def ooo; xxx; end 其實是 define_method 的 syntactic sugar)
的話,其實他是在定義 instance method. 如果要定義 class method,
就需要 prefix self. 或是 Name., 變成:
class C; def self.f; puts self; end; end
或是
class C; def C.f; puts self; end; end
這樣才會是 class method, 如此呼叫:
C.f # -> C

回到 module_eval 與 instance_eval, 這邊就是這種差異。
C.module_eval{ def f; puts self; end }
C.new.f # -> <C:0x12345>
C.instance_eval{ def f; puts self; end }
C.f # -> C

同樣的狀況發生在 mixin 上,include 產生 instance method,
extend 產生 class method (which is instance method to the class)
也就是有沒有前綴 self. 的差異。也就是,instance 不會有 module_eval,
也不會有 include 的意思了。

除此之外,其他所有地方的 scope 都是 lexical(的)。

additionally, mixin 的結果就像 meta-programming 一樣,
所有的 scope 都是看 mixee 而非 mixer, 所以:
module M; def f; puts self; end; end
class C; include M; extend M; end
C.new.f # <- <C:12345>
C.f # <- C
而且由於 mixin 是用 reference 去做,而非真正的 meta-programm,
所以:
module M; def f; puts 'XD'; end; end
C.old.f # <- XD
C.f # <- XD
我知道沒有 old 這種東西,我只是想表達如果 mixer 被修改,mixee 會反應。

mixin 還有更多東西﹍。
module M; def self.f; puts self; end; end
如此一來 M.f 就像是 namespace 層級的 function,
不受 mixin 本身影響。永遠只能透過 M.f 來呼叫,結果當然也是 M

最後一個!module_function, 這是個滿詭異的東西。
module M; def f; puts self; end; module_function :f; end
呼叫 module_function 後,被指定的 method 如果被 include 或
extend 的話,會變成 private 的。另一個效果是,自動把 def f 再變成
def self.f ...
也就是說,呼叫 module_function 會自動幫你再定義一次
module level function, 還有將 mixee 的 method 變成 private.

舉 Math 為例,其 sqrt 就是被 module_function 改寫過的。
class C; extend Math; end
C.sqrt # -> private method called.
Math.sqrt 9 # -> 3

不過呢,這個 module level function 只會被 module_function 影響一次。
所以當你再寫:
module Math; def sqrt; puts self; end; end
的話,並不會影響到 Math.sqrt 的正確性,不過可是會影響到 mixer 喔。
C.send :sqrt # -> C

可以自己試著用 eval 為基礎,寫出 module_eval, instance_eval,
還有 module_function, 就能清楚了解其中關係了。

其實還滿複雜的 :o
沒仔細想我自己也會搞不清楚,都需要實驗一次。
不過只要實驗一次就好了,我想這是沒什麼問題的 :p
查資料,做實驗,本來就是 routine 啊。

0 retries:

Post a Comment

All texts are licensed under CC Attribution 3.0