ブロックとは何か・ブロックの使い方を解説

ブロックは繰り返しのレッスンで少し取り上げましたね。ブロックとは、メソッドの呼び出しを行った際に引数と共に渡すことのできる処理の処理のかたまりのことをいいます。 

吉田先生

ブロックは繰り返し以外でも使用されます。たとえば、File.openメソッドにブロックを与えれば、記述しなくても後処理としてFile#closeを行ってくれるのです。


ブロックとは

具体的にどの部分がブロックにあたるかというと以下例の構文の「do~end」までの範囲がブロックです。

.times do |ブロック変数|
  処理
end

この例だとtimesメソッドとなっていますが、eachメソッドなども「do~end」がブロックとなります。また、「 { } 」を使い書かれている場合には「 { } 」内がブロックとなります。

オブジェクト.each {|ブロック変数|
 処理
}

これらのブロックは、メソッドの引数と考えます。上記例であれば「each」や「times」メソッドの引数となりますね。そしてメソッドが呼び出された際に、指定された回数だけブロックに書いた処理が実行されます。また、このような呼び出し形式をブロックつきメソッド呼び出しもしくは、ブロック付き呼び出しといいます。

2.times do |i|
  puts "#{i}回目のブロック実行です"
end
#0回目のブロック実行です
#1回目のブロック実行です

timesメソッドが呼び出されると、ブロック内で定義した処理である「#{i}回目のブロック実行です」という文字列の出力を指定回数の2回行っていますね。また、「i」というブロック変数に何度目の実行かという数字を代入するように定義しています。これは、0からカウントし始めてしまうので結果は「0回目のブロック実行です」となっています。

ブロック変数はブロックパラメータとも呼ばれており、メソッドによってブロック変数(ブロックパラメータ)に代入できる値の種類や数は変わります。eachメソッドの場合は配列オブジェクト当の要素が1つづつブロック変数に代入されます。このブロック変数は繰り返しのメソッド全てに存在するわけではなく、loopメソッドなどにはブロック変数はありません。

ブロックの使い方

このセクションでは一般的に良く使われている、ブロックの使い方について学んでいきましょう。

繰り返し

ブロックつき呼び出しが使われる代表的なものが繰り返しですね。呼び出された際にブロックを受け取るというメソッドの中で繰り返しを行う「each」や「times」等のことを「イテレータ」と呼びます。

これまでいくつか「times」や「each」等を様々な例で学んできました。このセクションでは、新たに「each_line」というものの使い方についてみていきましょう。

info.txt

参考書1:1600円
rubyについての本です。

参考書2:2200円
railsについての本です。

test.rb

file = File.open("info.txt")
file.each_line do |line|
  if line.include?("円")
  p line
end
file.close
ruby test.rb
#"参考書1:1600円\n"
#"参考書2:2200円\n"

まず「info.txt」という本の題名と金額、本の概要という事が複数冊分記載されているファイルがあるとします。このファイルからそれぞれの本の金額を抽出したいとしましょう。その時にファイルから1行ずつ文字列を取り出すことのできる「each_line」メソッドを使うと簡単に情報を取得する事が出来ます。

最初にファイルを開くための記述をします。そして、開いたファイルを代入した変数名でeach_line~と書きます。このブロック変数「line」に取り出された文字列(1行ずつ)が代入されていきます。

そして、この例の場合は金額が書かれている行を取り出したいので、「もし"円"という文字列が含まれる行があれば取り出す(if~p line)」という定義をします。そうすると、以下のように金額の行のみ抽出することが出来ます。

結果の部分の末尾の「/n」は改行文字というものです。この改行文字なしで出力したい場合は「p line」の部分を「p line.chomp」としてください。

*全ての行を取り出す場合には「if~("円")」の記述は不要です。

処理を隠す

前セクションでは、イテレータにてブロックが使用される際の例を学びました。そしてこれまでのレッスンでもブロックについてのご説明の際にはほとんど繰り返しを例としてきましたが、実はブロックは繰り返し以外でも使う事が出来るのです。では使い方の例をみていきましょう。

test2.rb

File.open("info.txt") do |file|
  file.each_line do |line|
    if line.include?("円")
    p line
  end
end
ruby test2.rb
#"参考書1:1600円\n"
#"参考書2:2200円\n"

この「test2.rb」は先程の「test.rb」を書き換えたものになります。test.rbではFile.openメソッドでファイルを開き変数に代入という形でしたが、これをブロックを使って書くとtest2.rbのようになります。以下部分がFile.openメソッドのブロックとなるという事ですね。

do |file|
  file.each_line do |line|
    if line.include?("円")
    p line
  end
end

eachメソッドやtimesメソッドは複数回ブロックを実行していたのに対し、このFile.openメソッドがブロックを実行するのは1度です。そして、重要なのがtest2.rbはtest.rbと異なり「file.close」が書かれていませんね。

これは、File.openメソッドでブロックを使用すると「file.close」の記述が不要となる為です。ブロックを与えることにより記述しなくてもFile#closeが行われるので以下構文を書いているのと同じ状況になります。

file = File.open("info.txt")
begin
  file.each_line do |line|
    if line.include?("円")
    p line
  end
ensure
  file.close
end

つまり、File.openメソッドでブロックを使用すると、後処理としてFile#closeを自動で行ってくれるという事です。

計算を差し替える

順序の指定

配列オブジェクトの属するArrayクラスなどで、配列の中の要素の並び順を入れ替えるという例を使って学んでいきます。この要素を並べ替えるということは「整列する」または「ソートする」という言い方もされます。要素の並び替えを行う為のメソッドには「sort」や「sort_by」などがあります。では、どのような並べ替えのパターンが考えられるかいくつか例を挙げてみますね。

・値が大きい順

・あいうえお順

・文字数順

このようにいくつかありますが、それぞれに1つずつメソッドが用意されているわけでもありませんし、1つ1つでその為のメソッドを定義するとなるとプログラムが複雑になってしまいます。ですので、「sort」メソッドは並べ替えるという処理のみが備わっていて、どのように並べ替えるかという部分はブロックを使って記述することになっています。

どのように =   ブロック内に記述
並べ替える =    sort

もちろん、ブロックを使わなくても並べ替えは行うことが出来ます。その場合ブロックを使わない=要素同士の比較方法を記述しないということになります。このように記述しない場合はデフォルトとして、「<=>」という演算子で比較されることとなります。

「<=>」比較演算子

この演算子は「左辺 <=> 右辺」という形で使われます。左辺と右辺のどちらが大きいかという比較を行います。結果については以下のように返ってきます。

左辺の方が大きい
左辺の方が小さい -1
左辺と右辺が等しい

比較できるのは数値だけではなく文字列も比較できます。文字列の場合上記の表に当てはめて言うと、アルファベット/あいうえお順で早い=小さいと判断されます。また、アルファベットの場合大文字の方が小さいと判断されます。以下例を参考にしてください。

 "あ" &lt;=> "お"
 #=> -1 

"a" &lt;=> "z"
 #=> -1 

"a" &lt;=> "Z"
 #=> 1 

"a" &lt;=> "a"
 # => 0 

では、並べ替えに戻りましょう。複数の文字列を含むというような配列を作り、要素を並べ替えてみましょう。

array = ["shoes", "accessory", "bag"]

narabi = array.sort
 #=> ["accessory", "bag", "shoes"]

アルファベット順に並べ替えられていますね。では、これをブロックを使い記述してみましょう。

array = ["shoes", "hat", "bag"]

narabi = array.sort{ |a, b| a &lt;=> b }
 #=> ["accessory", "bag", "shoes"]

最初の構文はデフォルトでこのような事が記述せずとも行われるようになっているという事です。この「 { } 」ブロック内では、ブロック変数「a」と「b」を定義しこの変数に文字列が代入され「a <=> b」というように比較を行っています。

そしてその結果通りにソート、つまり並べ替えられるという事です。ちなみに、「b <=> a」というようにaとbの記述位置を入れ替えれば大きい方から順に並べられます。

junban = array.sort{ |a, b| b.size &lt;=> a.size }
 #=> ["accessory", "shoes", "bag"] 

更にこの場合、ブロック変数「a」と「b」で「size」メソッドを使用しています。ですので、デフォルトのアルファベット順ではなく、文字数の多い順に並べ替えられています。

必要情報の取得

前セクションで取り上げた「sort」メソッド。このメソッドを使い並べ替えを行うとき要素が多かったり、並べ替えの条件が複雑であった場合にはプログラム実行の時間が長くなってしまうという欠点があります。

「sort」メソッドで書いたとき、並べ替えるためにメソッドやブロックが要素数以上に何度も呼ばれてしまいます。そういった場合には「sort_by」メソッドを使用します。この「sort_by」は、要素1つにつき1回だけブロックを実行し、メソッドは要素数分の呼び出しとなります。

ですので、同じ並べ替え方をしたとしても呼び出し回数は「sort_by」の方が少なくて済むのです。

test = array.sort_by{ |foo| foo.length }

ブロックつきメソッド

ブロックの実行

ブロックの実行をしたい場合は、「yield」というものを記述します。以下例の場合は、ブロック内「puts "ブロックが実行されました"」という処理をメソッド内で「yield」と書いてブロックを受け取り実行させています。

jikkou.rb

def foo
  num = 1

  while num &lt; 3
    yield
    num = num + 1
  end
end

foo do
  puts "ブロックが実行されました"
end
ruby jikkou.rb
#ブロックが実行されました #ブロックが実行されました

では、ブロック変数を記述するパターンもみてみましょう。

def foo
  yield("ruby")
end

foo do |lang|
  puts "私が今勉強している言語は#{lang}です"
end
#私が今勉強している言語はrubyです

「yield」に引数として「ruby」という文字列を指定しています。これがブロック変数「lang」に代入されています。

ブロック実行の制御

繰り返しの制御を行いたい場合は「break」や「next」を使用すると繰り返しのレッスンで学びましたね。このセクションでは更に詳しく学んでいきましょう。

「break」

breakはブロックの実行を中断する際に使います。では、ブロック内でbreakと記述した場合戻り値は何になるでしょうか。以下例で試してみましょう。

3.times do
  p "3回繰り返します"
  break
end
#"3回繰り返します"
 #=> nil 

#中断されなければ以下の結果となる

"3回繰り返します"
"3回繰り返します"
"3回繰り返します"
 => 3 

戻り値はnilですね。breakと記述されているところで中断し繰り返しから抜けています。ですので、結果を返すという所までいくことが出来ず終了してしまうのです。しかし、「break」に引数を指定すると、指定した引数の値が戻り値となります。

3.times do
  p "3回繰り返します"
  break 1
end
#"3回繰り返します"
 #=> 1

このように「break」を使った場合の戻り値を「nil」以外にしたい場合は「break」の引数で指定しましょう。この例では「break」に「1」という引数を渡しています。ですので戻り値が1となっていますね。ただし、「p "3回繰り返します"」という処理はbreakを記述しているので1度のみの出力です。

「next」

nextはnextの後に書いた処理をスキップするというものでしたね。では例を見てみましょう。

10.times do |num|
  if num.even?
    next
  end
  p num
end
#1
#3
#5
#7
#9

「even?」は偶数である場合に「true(真)」を返すメソッドです。ですので、偶数である場合に処理をスキップしていますね。

オブジェクトとして受け取るには

ブロックを実行するのセクションでは「yield」と書きブロックを受け取っていましたね。この他にもブロックをオブジェクトとして受け取る方法があります。「Proc」オブジェクトというものを使う方法です。

この「Proc」オブジェクトを使えばブロックを受け取ったメソッドではない場所でもブロックを実行できるようになります。ブロックをオブジェクトとして使用できるようになるのです。「Proc」クラスというクラスが存在しますので、「Proc.new」としてProcクラスのインスタンスを作成します。では例を見てみましょう。

product = Proc.new do |item|
  puts "新作商品入荷!>#{item}"
end

product.call("バッグ")
 #新作商品入荷!>バッグ
product.call("アクセサリー")
 #新作商品入荷!>アクセサリー

このように「Proc.new」メソッドをブロックつきメソッドとするのです。呼び出しの際は「call」というメソッドを使います。「item」というブロック変数には、「call」メソッドの引数が代入されていますね。

また、メソッド名の後の引数を「&引数名」という形で書けばブロックをProcオブジェクトととし更に引数として渡すことが出来ます。このような引数を「Proc引数」といいます。

def test(&amp;block)
  block.call
end

test do 
  puts "ブロックが実行されます"
end

#ブロックが実行されます

ブロックをProc引数としてメソッドに渡され、「&引数名.call」でブロックを実行させています。Proc引数を含め複数の引数を指定したい場合は、必ずProc引数を1番最後に記述してください。

ローカル変数 / ブロック変数

ブロック変数はブロック内が有効範囲となります。ブロック外に、ブロック内と同名の変数があっても別の変数として扱われます。ただし、ブロック外で書かれたローカル変数にブロック内で値を代入するとブロック外のローカル変数の値もきちんと変更されます。では以下例を見てみましょう。

hensuu.rb

outside = 100

3.times do |inside|
  outside = 10 + inside
end

puts outside
puts inside
ruby hensuu.rb
#12
#hensuu.rb:8:in `&lt;main>': undefined local variable or method `inside' for main:Object (NameError)

まずローカル変数「outside」に100を代入しています。そして、ブロック内で「outside」に10+ブロック変数「inside」の値を足した数を代入しています。

そしてそれぞれの変数を呼び出してみると「outside」の方は、ブロック変数「inside」の値+10の結果が出力されていますね。この場合のブロック変数の値は最後のブロック実行の時の2です。(0からカウントされ始める為)きちんと出力されているので、ブロック外のローカル変数の値をブロック内で変更出来ることが確認できました。

対してブロック変数「inside」はブロック内では使用できていますが、ブロック外で呼び出そうとしてもエラーとなります。(この場合は、ブロック外に同名の変数がない為)

ブロックローカル変数

ローカル変数は有効範囲がブロック内にも及ぶので、ブロック内でも使用できますが「ブロックローカル変数」というものを使えばブロック外で存在しているローカル変数と別の変数として同じ名前の変数を作ることが出来ます。では例を見てみましょう。

blocal.rb

x = 1000

3.times do |inside, x|
  x = inside + 10
  puts "「ブロック変数inside=#{inside}」"
  puts "「ブロックローカル変数x=#{x}」"
end

puts x
ruby blocal.rb
#「ブロック変数inside0
#「ブロックローカル変数x=10
#「ブロック変数inside1
#「ブロックローカル変数x=11
#「ブロック変数inside2
#「ブロックローカル変数x=12

#1000

まずローカル変数「x」がブロック外で存在していることを確認してください。そして、ブロック内で「inside, x」という形でブロック変数とブロックローカル変数を定義しています。

これは「ブロック変数; ブロックローカル変数」という構文になります。続いて、ブロックローカル変数にブロック変数+10を代入しています。この結果をみてみると、ブロック内ではローカル変数「x」とは別の変数としてブロックローカル変数「x」が使用できていることが分かります。

しかし、ブロック外から「puts x」とするとローカル変数「x」の方が出力されています。これは、ブロックローカル変数はブロック内でのみ有効という事を示しています。

まとめ

ブロックは繰り返し以外でも使用されます。たとえば、File.openメソッドにブロックを与えれば、記述しなくても後処理としてFile#closeを行ってくれるのです。

更に「sort」や「sort_by」メソッドでブロックを使い、どのように要素を並べ替えるか定義する事が出来ます。

各変数の有効範囲は以下です。

・ブロック変数の有効範囲はブロック内のみ

・ローカル変数の有効範囲はブロック内外

・ブロックローカル変数とはブロック外に存在するローカル変数と同名の別の変数(ブロック内でのみ有効)

無料ビデオ講座のお知らせ

Skillhub [スキルハブ]では無料の動画講座を多数公開しています。他校だと数万円するような講座が無料で受講できます。

無料講座一覧を見る

×