Chapter 4: Blocks - Part II

Scope Gates

Scope gate 可以想像為柵欄,因為柵欄讓作用域分隔兩邊的變數既不看見彼此,也無法互通。在 Ruby 裡有三個主要的 scope gates :

  1. Class definitions
  2. Module definitions
  3. Methods

當程式進入或退出類別、模組或是方法時,作用域也會跟著改變。Scope 關鍵字像是: class / module / def 都可想像為作用域的邊界關口,而 end 就類似出關的概念。

在書中 class / module / def 都稱為是 scope gate。

回到上一篇的最後ㄧ個範例:

#  可使用 local_variables 取得當下區域變數的值
v1 = 1
self                       # main

class MyClass              # SCOPE GATE: entering class
  v2 = 2
  local_variables          # => [:v2]

  def my_method           # SCOPE GATE: entering def
    v3 = 3
    local_variables
  end                     # SCOPE GATE: leaving def

  local_variables         # => [:v2]

end                       # SCOPE GATE: leaving def

obj = MyClass.new
obj.my_method             # => [:v3]
obj.my_method             # => [:v3]
local_variables           # => [:v1, :obj]˙

把 SCOPE GATE 標示之後,就能非常清楚地了解在哪ㄧ行程式碼的時候會進入或是退出 scope,什麼變數和方法是可以使用的。要注意的是:class 和 module 都是立刻就執行的,但方法只有在被呼叫時,才會被執行。

Flattening the Scope (nested lexical scopes)

雖然 scope gates 能夠做好把關,不讓其他作用域的變數進入。但是 Ruby 偷偷開了後門,換個方式也是能夠偷渡變數進入的。其實這跟 private 的情況有點像,基本上 private 方法是不被允許使用的,但如果用 send() 方法的話就可以了。

class Cat
  private

  def eat
    puts "a_private_method"
  end
end

cat = Cat.new
cat.eat           # => private method `eat' called…
cat.send(:eat)    # => "a_private_method"

回歸到正題,如果能夠以某種方式避開 class 以及 def 的關鍵字,或許就可以偷渡外面 top-level variables 進入到類別裡。

my_var = "Success"

class MyClass
  # 希望可以使用 my_var 在 MyClass 的 scope
  puts "#{my_var} in the class definition"

  def my_method
    # 希望可以使用 my_var 在 my_method 的 scope
    puts "#{my_var} in the method definition"
  end
end

偷渡方式

Class / Module: 可以用 Class.new 取代原有的 class ,並且讓 block 傳進到 Class.new 來定義實例方法。

Def: 至於 def 的方法就利用之前在介紹動態方法過的 define_method。

my_var = "Success"

MyClass = Class.new do
  puts "#{my_var} in the class definition"

  define_method :my_method do
    puts "#{my_var} in the method definition"
  end
end

MyClass.new.my_method

如果兩個作用域(scope)擠壓合併下,能夠分享變數。就稱此黑魔法為 “ Flat Scope “。

Sharing the Scope

另一個運用 Dynamic Dispatch 存取 Kernel#define_method 創造出 (shared scope) 共享作用域的技巧。

def define_methods
  shared = 0
  
  Kernel.send :define_method, :counter do 
    shared
  end
  
  Kernel.send :define_method, :inc do |x| 
    shared += x
  end
end

define_methods

counter # => 0 
inc(4)
counter # => 4

總結

  • 每一個 Ruby 的作用域都包含ㄧ堆 bindings,而作用域都是被三個 Scope Gates 所隔開:(1) class 、(2) module、(3) def。
  • 如果你需要偷渡 bindings 到不同的 scope,則需要避開 scope gates 的關鍵字,
    1. Class.new 替換 class、
    2. Module.new 替換 module、
    3. Module#define_method 替換 def。
  • 如果你需要共享 local bindngs 在許多方法中,則可以考慮 shared scoped 的技巧。