【これで完璧!】Rubyのブロックの使い方、使い道まとめ (do~end {})

こんにちは!Webコーダー・プログラマーの貝原(@touhicomu)です。

今日は、Rubyのブロックについて書きたいと思います。

「そもそも、ブロックって何?」

そういう疑問が浮かぶほど、ブロックってなかなか理解しずらいでよね。

そういった理由で、「ブロックの使い方がわからない!」という声を、筆者もたびたび耳にします。

確かに、Rubyの文法の中でも、ブロックは、構文も含めて難しいですよね。

そこで!今回は、Rubyのブロックについて、やさしく整理して理解が進むよう解説をしてみました!

この記事では、rubyのブロックについて

・ ブロックとは
・ 繰り返し
・ メソッドの一部変更

という基本的な内容から、

・ ブロックをProcオブジェクトとして受け渡し可能にする
・ ブロックの使用上の注意点(スコープ)

といった応用的な内容についても解説していきます。

目次

ブロック基本編

ブロックとは

ブロックとは、一言で言えば、「引数のかたまり」です。

ブロックの、do~end までの範囲すべてが引数です。

または、{ } で囲まれた範囲もブロックで、この範囲すべてが引数です。

ブロックとはどのようなものなのか、ここで簡単に復習してみましょう。

サンプルコード(do~endのブロックの例):

3.times do | i |
  x = i * 2
  p x
end

サンプルコードの実行結果:

0
2
4

上の例の、

“do | i |
  x = i * 2
  p x
end”

までが、ブロックです。
このブロックが、timesメソッドに引数として、与えられています。

timesメソッドは、このブロックを引数として受け取り、0、1、2と数え上げる時に、数え上げている数を変数iに入れて、引数として受け取ったブロックのコードを実行しています。

そしてブロック内で「p x 」が実行されて、画面に実行結果が表示されています。

サンプルコード( { } のブロックの例):

[ "one", "two", "three" ].each { | n |
  str = "this num is " + n
  p str
}

サンプルコードの実行結果:

"this num is one"
"this num is two"
"this num is three"

eachメソッドも、

“{ | n |
  str = “this num is ” + n
  p str
}”

というブロックを、引数として受け取っています。

eachブロックは、”one”、”two”、”three”と配列をループする時に、ループ中の配列の要素を変数nに入れて、引数として受け取ったブロックのコードを実行しています。

そして、ブロックで、「p str」が実行されて画面に実行結果が表示されています。

このように、ブロックはコードではありますが、あくまで引数なので、単体では存在できません。

繰り返し

繰り返し系のメソッドが、一番ブロックを引数として渡すことの多いケースです。

繰り返しの中でも、eachメソッドが一番多く使われます。

初めのうちは、eachメソッドにブロックを引数として渡すことを練習し、ブロックのコツをつかむ方がいいでしょう。

eachメソッドに慣れてきたら、他にハッシュのeachメソッド、範囲のtimesメソッドなどにもブロックを引数として渡してみてください。

「ブロックを引数として渡す」ということを意識して、実際にコードを書き、エラーが出たら修正していくことで、徐々に「ブロックを引数として渡すことは、こんな感じでOKなんだ」と理解が進み慣れてくると思います。

ここでは、配列に対して、ブロックを引数として渡す練習をしてみましょう。

まずは、基本形であるeachメソッドの例です。

eachメソッドは、配列の要素を数え上げながら、数え上げ中の要素を、ブロック変数numに入れて、引数として渡されたブロックのコードを実行します。

サンプルコード1(each):

array = [1, 2, 3, 4]

array.each do |num|
  p num
end

サンプルコード1(each)の実行結果:

1
2
3
4

引数として渡されたブロックで、「p num」が実行されていることが分かりますね。

次によく使う、mapメソッドの例です。

mapメソッドは、ブロック引数の値を新たな配列にして返します。

ブロック引数の値とは、ブロックの最後に実行された式の値です。

以下の例では、「num+10」がブロック引数の値です。

サンプルコード2(map):

array = [1, 2, 3, 4]

ret = array.map do |num|
  num + 10
end
p ret

サンプルコード2(map)の実行結果:

[11, 12, 13, 14]

実行結果は、「num+10」された値の配列になっていますね。

次に、これもよく使うselectメソッドの例です。

selectメソッドは、配列の要素を数え上げている中で、数え上げている要素を一つずつブロック変数numに入れて、ブロック引数に渡します。

そして、ブロック引数の値がtrueである要素のみを入れた新たな配列を作成し、selectメソッドの戻り値として返します。

繰り返しますが、ブロック引数の値とは、ブロックの最後に実行された式の値となります。

以下の例では、「num if num % 2 == 0」がブロック引数の値です。

この式は、numが偶数の時はnumの値(trueと等価)、奇数の時はnil(falseと等価)を返します。
そのため、selectメソッドが返す配列は、偶数のみの配列になります。

サンプルコード3(select):

array = [1, 2, 3, 4]

ret = array.select do |num|
  num if num % 2 == 0
end
p ret

サンプルコード3の実行結果:

[2, 4]

実行結果では、「num if num % 2 == 0」がtrueとなる要素、つまり偶数の配列が表示されていますね。

ブロック引数のイメージが具体的になってきたでしょうか?

繰り返しますが、「ブロックは引数のかたまり」と思えてきたら、理解が進むと思います。

なお、上記each、map、selectのようなブロック引数を受け取るメソッドのことを「ブロック付きメソッド」と言います。覚えておいてください。

メソッドの一部変更

ここでは、より深く、ブロックについての理解を進めます。

ブロックは、例えば、eachメソッドの処理内容の一部をブロックのコードで変更しているとも言えます。

このブロックによるコード変更のおかげで、柔軟な処理が可能となります。

ここで例をあげましょう。

ここでは、sortメソッドで、ブロックが処理を肩代わりしていることを確認します。

sortメソッドは、文字列の配列などを、アルファベットや50音の昇順で並び替えするメソッドです。

サンプルコード(昇順並び替え):

array = [ "b", "c", "a"]

ret = array.sort
p ret

サンプルコード(昇順並び替え)の実行結果:

["a", "b", "c"]

ここで、sortメソッドにブロック引数を渡して、sortメソッドの処理の一部を変えましょう。

sortメソッドは、並び替えの基準となる式を「 a <=> b 」という式で使用しています。
この式をブロック引数で変えると、並び替えの順番が変わります。

ここでは、sortメソッドの並び替えを降順にするために、ブロック引数の値を、式「 b <=> a 」としましょう。

「b <=> a 」は、「a <=> b 」と逆の計算結果になるので、ソート結果も昇順の逆の降順になります。

サンプルコード(降順並び替え):

array = ["b", "c", "a"]

ret = array.sort do | a, b |
  b <=> a
end
p ret

サンプルコード(降順並び替え)の実行結果:

["c", "b", "a"]

並び替えが降順になりましたね。

先にあげたeach、map、selectも同様にブロック引数で処理の一部を変更している例となります。

ブロック応用編

ブロックをProcオブジェクトとして受け渡し可能にする

ブロックはブロック単体では存在を維持できません。

しかし、Procオブジェクトにブロックを渡してあげればProcオブジェクトとして存在できます。

サンプルコード:

proc = Proc.new {|w| puts w}

def hello(&proc)
  proc.call("hello")
end

hello(&proc)

サンプルコードの実行結果:

hello

Proc.newでインスタンスを作成し、ブロックを渡してあげることで簡単にProcオブジェクトとしてブロックを扱えます。

そして、その後定義したprocをhelloに渡しています。

ProcのインスタンスメソッドであるcallProcが保持しているブロックを使用することができます。

Procオブジェクトの扱い方はこちらの記事にまとめられているので、一読することをおすすめします。

ブロックの使用上の注意点(スコープ)

最後に、ブロックの注意点を挙げます。

実は、ブロック内の変数は、ブロックの外(do~endの外、{ }の外)では、参照できません。

ブロック内の変数は全てブロックのローカル変数です。このブロックのローカル変数をブロックスコープのローカル変数といいます。

例を挙げて説明しましょう。

サンプルコード:

array = [1, 2, 3, 4]
local = 'local'

array.each do |num|
  block = 'block'
end

p local   # => 
p block   # => 

サンプルコードの実行結果

"local"
Main.rb:9:in `
': undefined local variable or method `block' for main:Object (NameError)

上記サンプルコードのように、変数「local」は、メインスクリプト内のローカル変数ですので、「p」にて正常に値が表示されています。

しかし、変数「block」は、ブロック内のみで有効なので、メインスクリプト内では、未定義のままであり、「p」メソッドで参照するとエラーが出てしまいます。

このようなエラーを出さないために、「ブロック内の変数は、ブロック外では参照できない。」ということを念頭においていてください。

まとめ

いかがでしたでしょうか。

ブロックが引数であるとは、なかなか慣れないものです。
引数のわりには、複数行にまたがって書きますので、「これで引数といっていいの?」など思われるかもしれませんね。

しかし、ブロックはただ単に、{ } や、do ~ end で囲まれた引数なのです。

既存のブロック付きメソッドにブロック引数を与えることで、メソッドの処理を柔軟に、また、直感的に実装できますので、覚えておくととても役に立ちます。

特に、Rubyを書くとブロックは頻出しますので、必ず覚えるようにしてください。

ブロックを覚えると、Rubyが大変使いやすいものになります。

ブロックの使い方を忘れたら、またこの記事を読み返してみてください。

この記事を書いた人

こんにちは!貝原(@touhicomu)と申します。
現在は、Web業界のフリーランスとして、主にPHP/WordPress/BuddyPress/VPSサーバー構築などの業務を受注しています。
現住所は、日本の西海岸、長崎県は波佐見町です。田舎ライフです。^^
地元の観光団体「笑楽井石」にボランティアでほたる撮影会やそば塾などのスタッフとして参加させて頂いています。
以下の活動も行っています。
 ・笑楽井石のブログ
 ・エクセル関数を日本語化するソフト
 ・エクセルVBAを日本語で記述するソフト

目次