2016-03-29

Kernel#procのはなし

ちょっと前の話なんですが、最近ブログ書けていないのでAsakusa.rbで話題になったちょっと意外なKernel#procのはなしを書きます。

Kernel#procというと、こんな風に書くとProcオブジェクトが生成されてProc.newとか書かなくてよいよねくらいの便利メソッドです。

proc do
  hogehoge
end

ところでみなさん、ブロック付きで呼び出されたメソッドでブロックをProcとして使いたいときはどうしているでしょうか?

def method(&block)
  hogehoge(&block) # 例えば次のメソッドにそのまま引き渡す
end

こんな風にブロック引数を定義しておいて使うのではないでしょうか。
実際にこの話題が出たときもとあるソースコードを読んでいるときにこのようなコードがありました。
私がブロック引数を使わなくても書けるということを言いだしたのがことの発端です。

Rubyのブロック引数は必ず最後に書かなければならず、yieldするだけなら省略することができます。 block_given?なんてメソッドもありますし、ブロック引数なんて書かない場合がほとんどなのではないでしょうか。
ところがこのケースにはブロック引数を書かなければなりません。
どうしてもブロック引数を書きたくない場合にはこんな風に書くこともできます。

def method
  hogehoge &proc { yield } # yieldの呼び出しをProcでくるみます
end

おや、これはProc.newするので遅くなりそうですね。実際にベンチマークを取ると倍くらい遅いようです。
そこで登場するのが、Kernel#procのブロックなしの呼び出しです。
こんな風に書けます。

def method
  hogehoge &proc
end

めっちゃ簡潔です。ベンチマークもブロック引数の場合と変りないです。
なんとこの用法、その場にいた誰もが知らないというのです。しかしリファレンスマニュアルのKernel#procの項をみると次のように書かれています。

与えられたブロックから手続きオブジェクト (Proc のインスタンス) を生成して返します。Proc.new に近い働きをします。 ブロックが指定されなければ、呼び出し元のメソッドで指定されたブロック を手続きオブジェクトとして返します。呼び出し元のメソッドがブロックなし で呼ばれると ArgumentError 例外が発生します。

そして、こんな風にも…

ただし、ブロックを指定しない呼び出しは推奨されていません。呼び出し元のメソッドで指定されたブロック を得たい場合は明示的に & 引数でうけるべきです。