Just do IT

思うは招く

RubyのProcクラスとは何か?

Procクラスとは、処理のまとまりをオブジェクトで表現したものです。Procクラスはコードを見ながらじゃないと理解できないと思うので、まずは以下の例を見てください。

def hello
  puts 'hello'
end

なんの変哲もないメソッドです。helloメソッドを実行してみるとどうなるでしょうか?

def hello
  puts 'hello'
end

hello
#=> hello

はい、helloが出力されるだけです。

Rubyでは、メソッドの実行時にブロックを引数として渡すことができます(「ブロック引数」と呼ばれます)。helloメソッドの実行時にブロック引数を渡してみるとどうなるでしょうか。

hello do
  puts 'byebye'
end

#=> hello

byebyeと出力されそうでしたが、なにも変わりませんね。puts byebyeという処理の塊を実行したいとき、次のように行います。

def hello
  puts 'hello'
  yield # 追加
end

hello do
  puts 'byebye'
end

# hello
# byebye

もとのhelloメソッドの中に、yieldメソッドを追加しました。すると無事にbyebyeも出力されています。これはつまり、yieldを書いたところで今回渡したブロック引数が実行されています。

では、yieldの位置を変えてみましょう。

def hello
  yield #上に移動させた
  puts 'hello'
end

hello do
  puts 'byebye'
end

# byebyeが先に出力される
# byebye
# hello

yieldの位置をputs 'hello'よりも上に移動させました。すると、byebyeのほうが先に出力されています。

&を使ってブロック引数を明示的に渡す

こちらの例をもう一度見てみましょう。

def hello
  yield
  puts 'hello'
end

hello do
  puts 'byebye'
end

helloメソッドを見てみると、引数としてブロックが渡ってくることが明確ではありません。これは使いづらいメソッドです。通常だと、メソッドに引数を渡す場合はメソッド側に引数が渡ることを明示的に定義しますよね。こんな感じで。

def say_love(name)
  puts "I love #{name}!!"
end

say_love('Gakky')
# I love Gakky!!

ブロック引数も明示的に渡すように定義してみましょう。

def hello(&block)
  yield
  puts 'hello'
end

hello do
  puts 'byebye'
end

# byebye
# hello

引数として&blockを渡しました。&をつけることでブロック引数が渡されることを定義できます。&は慣れない書き方ですが、要するにblockというブロック引数が渡ってきたと理解すればよいでしょう。blockという名前はなんでもOKです。

が、ここでちょっと問題があります。せっかく&blockという名前で渡しているのに、メソッド内ではどこにも使われている様子がありません。yieldを呼んでいるので実行自体はできていますが、これではまたわかりにくいメソッドになってしまいます。

そこで、yieldの部分をblock.callに変えてみましょう。

def hello(&block)
  block.call
  puts 'hello'
end

hello do
  puts 'byebye'
end

# byebye
# hello

callメソッドを実行することで、ブロック引数を実行できました。引数で&blockと渡ってきたからといって、&block.callをしてもエラーになってしまうので気をつけてください。

def hello(&block)
  &block.call # &をつけて実行したらエラーになる
  puts 'hello'
end

hello do
  puts 'byebye'
end

proc.rb:2: syntax error, unexpected &
  &block.call

では、引数として渡ってきたblock のクラス名をputs block.classで表示させてみましょう。

def hello(&block)
  puts block.class
  block.call
  puts 'hello'
end

hello do
  puts 'byebye'
end

# Proc ←
# byebye
# hello

blockのクラス名はProcクラスだということがわかりました。処理がまとまったもの、これがProcクラスの正体です。Rubyでは、Procオブジェクトを使って処理の塊を切り分けることができます。では今回の処理をProcオブジェクトとして切り分けて使ってみましょう。

Procオブジェクトを作る

def hello(&block)
  block.call
  puts 'hello'
end

# Procオブジェクトを作っている
bye_proc = Proc.new { puts 'byebye' }

hello(&bye_proc)

# byebye
# hello

通常のオブジェクト生成方法と同じように、Proc.new { puts 'byebye' }をしてbye_procオブジェクトを作っています。これは処理の塊であり、ブロック引数(do~end)として先程まで渡していた処理とまったく同じ内容です。do~endがオブジェクトになったような感じですね。

引数として渡すときは、やはりhello(&bye_proc)といったように&をつけ、Procオブジェクトを渡していることを明示的にする必要があります。