【これで完璧!】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が大変使いやすいものになります。

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

この記事を書いた人

【プロフィール】
DX認定取得事業者に選定されている株式会社SAMURAIのマーケティング・コミュニケーション部が運営。「質の高いIT教育を、すべての人に」をミッションに、IT・プログラミングを学び始めた初学者の方に向け記事を執筆。
累計指導者数4万5,000名以上のプログラミングスクール「侍エンジニア」、累計登録者数1万8,000人以上のオンライン学習サービス「侍テラコヤ」で扱う教材開発のノウハウ、2013年の創業から運営で得た知見に基づき、記事の執筆だけでなく編集・監修も担当しています。
【専門分野】
IT/Web開発/AI・ロボット開発/インフラ開発/ゲーム開発/AI/Webデザイン

目次