2013年4月25日 星期四

[RB] Ruby中的Proc及Block

Block:

在寫ROR時,大家或多或少會碰到這樣的語法:
posts.each do |post|
  puts post.tiltle
end

posts.each {|post| puts post.tiltle}
上面兩段程式碼中用do-end和大括號{}包起來的就是block,但用{}包裹程式碼的方法只適用於程式碼是一行的情況。
學ROR以來有好長一段時間我都不太清楚他的妙用,只覺得這麼普通的東西為什麼要寫的那麼拐彎抹角,但是在看了code school的教學影片後發現他跟javascript的callback機制有異曲同工之妙,這樣的設計讓我們在做一些enumeration操作的時候能將block中的code傳入,之前之所以對這個概念一直搞不清楚就是因為很難具體的想像在each中視怎麼call這段code,現在我們就用這個demo來剖開這個黑盒子:
class Child
  def initialize
    @toys=['gun','hammer','puzzle']
  end
  def each_toy
    for toy in @toys 
      yield toy
    end
  end
end

mark = Child.new
mark.each_toy do |toy|
  puts "I'm playing #{toy}~"
end

在這個範例中block像參數一樣被傳入each_toy這個method,並在裡面的loop內被多次呼叫。這樣的設計除了使用起來很靈活、有彈性以外,也能減少重複的程式碼。如果你對javascript比較了解的話可以看這一段程式碼來幫助了解:
function Child() {
    var toys = ['gun','hammer','puzzle'];
    this.eachToy = function (cb) {
        for (var _i in toys) {
            cb(toys[_i]);
        }
    }
}

mark = new Child();
mark.eachToy( function (toy) {
  console.log("I'm playing " + toy + "~");
});

但是上面的程式碼中,block看起來一點都不像是物件,也不能被重複使用。如果要像是javascript一樣能將函數像物件一樣被宣告、保存起來,並傳入其他函數作為callback function,我們還少學了一些東西,那就是接下來要介紹的Proc物件。

Proc:

proc物件可以將ruby的程式碼保存起來,在需要的時候再執行他,或當做block傳入其他函數。proc有兩種實作方法:

By Proc.new

routine = Proc.new {|variable| Do something... }

routine = Proc.new do |variable| 
  Do something...
end

By lambda

routine = lamda {|variable| Do something... }

routine = -> {|variable| Do something... }

直接執行

routine.call value


把proc轉成block

要把proc物件轉為block時,我們要在這個物件的前面加上"&",例如:
posts.each &routine

在method中判斷是否有block傳入

要知道有沒有傳入block,我們可以使用block_given?來判斷:
if block_given?
  yield
else
  puts "No block past in"
end


小結

現在我們就來改寫一下一開始的範例:
class Child
  def initialize
    @toys=['gun','hammer','puzzle']
  end
  def each_toy
    if block_given?
      for toy in @toys 
        yield toy
      end
    else
      puts "No block past in"
    end
  end
end

mark = Child.new
routine = lambda { |toy| puts "I'm playing #{toy}~" }
mark.each_toy &routine