本記事では「ブロック」を解説します。
処理のほんの一部分を別の処理に置き換えたり、その他さまざまなことができます。
では共通関数やメソッド、クラスのオーバーライドと何がちがう?となりますよね。
ブロックの基本とその応用について、解説していきます。
本記事を読み終える頃には、処理を大きく変更することなく部分的に差し替えるなど、効率的にプログラミングできるようになっていますよ!
ブロックとは?
ブロックといえば、プログラミングの世界では一般的に{ }で囲われた部分を意味します。
Rubyにおいても、確かに同じように{ }で囲うこともあります。しかしブロックには「処理のかたまり」という以上の意味があります。
たいそうに聞こえますが、今までさんざん出てきたといえば驚かれるでしょうか?
繰り返し処理で普通に使ってきたブロック
繰り返し行う処理、eachで1から10までを表示させる場合です。
(1..10).each do |i| puts i end
do 〜 endで囲まれた部分がブロックです。
このような書き方もあります。
(1..10).each { |i| puts i }
1行で書くなら・・・
(1..10).each { |i| puts i }
いずれにせよ、受け取るための変数と、処理する内容を記述するかたまり、という感覚が分かってきたでしょうか?
ここで|i|に注目しましょう。
この受け取るための変数をブロック変数といいます。
処理を隠す
ブロックは他に、定型処理を隠すという使われ方があります。
前記事「【Ruby入門】入力と出力」の「6.ブロックでオープン、クローズを省略可」にて解説しましたが、ここで再度詳細を解説しましょう。
以下が本来のファイルのオープン処理です。
io = File.open("sample1.txt", "r") while line = io.gets puts io.lineno.to_s end io.close
ブロックを指定するとこうなります。
File.open("sample1.txt", "r") do |io| while line = io.gets puts line end end
2番目の方は、openとcloseがないのがお分かりでしょうか?
Rubyのマニュアルを見てみましょう。
ブロックを指定して呼び出した場合は、Fileオブジェクトを引数としてブロックを実行します。ブロックの実行が終了すると、ファイルは自動的にクローズされます。ブロックの実行結果を返します。
とあります。ということは、File.openはブロックでない呼び出し方をされたときと、ブロックで呼び出されたときで動きがちがうということです。
しかもそれは、使う側が単に仕様に応じて選択するだけということです。
処理を一時的に変更する
ブロックは、処理を一時的に変更するときにも使います。
配列のソート(整列)を例にとります。
配列を何らかの規則にもとづきソートさせたいということはよくあります。
その規則性の数だけメソッドを作っていては、名前と種類、引数の数などを覚えるのが大変です。
よって、ソートする枠組みだけ用意して、規則はその都度決めようという考えに至ります。
それがメソッドにブロックを渡すことにより一時的に処理を変更する、という意味です。
上図は何も渡さなければ文字コード順にソートされますが、ブロックで要素間の関係を渡すと、その順にソートできるということを表現しています。
何らかの規則性を受け取るとそちらで上書き、といったイメージでしょうか。
実際にやってみましょう。
まずは、シンプルにソートさせます。
array = ["zoo", "zone", "zabix", "along", "bee", "function"] p array.sort
[実行結果]
["along", "bee", "function", "zabix", "zone", "zoo"]
実はコレ、以下と同じ動きになります。
array = ["zoo", "zone", "zabix", "along", "bee", "function"] p array.sort{|a, b| a <=> b}
実行結果は、先ほどと同じです。
ここで解説です。
「a <=> b」とは、以下の結果を返します。
- a < b ならば -1
- a == b ならば 0
- a > b ならば 1
ちょっと分かりにくいので、こう説明します。
要素1 <=> 要素2
- 結果が-1なら要素1、要素2
- 結果が0ならそのまま
- 結果が1なら要素2、要素1
この判定を使って、配列の要素を昇順にソートできます。
では次に、単語数の少ない順に並べましょう。
array = ["zoo", "zone", "zabix", "along", "bee", "function"] p array.sort{|a, b| a.length <=> b.length}
[実行結果]
["zoo", "bee", "zone", "zabix", "along", "function"]
sortに対して、要素間の関係だけをブロックで渡してソートさせる、ということが理解できたでしょうか?
ブロックの部分を「{|a, b| b <=> a}」とするだけで逆順にソート、といった柔軟な対応ができます。
2項目間の関係をブロックで定義しましたが、必ずしも項目間の関係だけでなく、個々の要素に対しての操作も指定可能です。
array = ["zoo", "zone", "zabix", "along", "bee", "function"] array.each{|a| p a.upcase}
[実行結果]
"ZOO" "ZONE" "ZABIX" "ALONG" "BEE" "FUNCTION"
ブロックつきメソッドを独自に作る
今度は自分でブロックつきメソッドを作ってみましょう。
仕様は以下のとおりです。
- ブロックを受け取らないなら 数量:amount × 単価:price
- ブロックを受け取ればその計算に準ずる
まずは実行側です。
def calc_price(amount, price) result = 0 if block_given? result = yield(amount * price) else result = amount * price end end
そして呼ぶ側は以下です。まずは普通に数量×単価です。
p calc_price(5, 100)
[実行結果]
500
次に、消費税込みの金額を出しましょう。
p calc_price(5, 100){|num| num * 1.08}
[実行結果]
540.0
普通はこんな方法をとりませんが、ブロックを渡す・受け取って使う方法を確認するという観点から、あえてこのような例にしました。
解説
block_given?で、ブロックが与えられた場合はtrue、そうでないならfalseを得られます。ブロックがない場合は普通に数量と単価をかけています。
ブロックを得られれば「yield(amount * price)」にて受け取った数量と単価をかけて、その結果を受け取った計算方法に渡しています。
この場合、数量と単価をかけて、1.08をかけています。結果540円を得たということになります。
ブロックを渡す、つまりこの場合計算方法もろとも渡してしまう、というのが理解できたでしょうか?
計算方法は重要ではなく、計算方法そのものを「まるっと」渡している!ということに注目してください。
まとめ
本記事では、ブロックについて解説しました。
方法まるごと渡してしまう、この便利さに少しでも気づいていただけたなら幸いです。
先の例を使って、さまざまな計算方法を渡してみたりして遊んでくださいね!