概要
ブロックをいまいち把握してない、do~endでくくっているものはなんだ、と聞かれて答えられない人、{|n| ....}をなんとなく使っている、また、関数型チックなProcやlambdaといったものを敬遠する方は意外と、多いのではないかと思います。
そこを理解すれば、もう少しエレガントにRubyを書ける様になります。
その導入として、lambdaを使ってより簡潔に手続きを実装し、ブロックの理解を深めてみましょう。
スクールなどでRubyはある程度わかったが、知識おぼろげでなんとなく書いているよ、という方は読んでみて欲しいです。
ブロックがわかるとコードが読みやすくなり、ラムダでちょっとカッコよくかけます。(小声)
ブロック構文とは
Rubyには**「ブロック」という構文があります。ブロックはdo … endまたは{ … }**で囲まれた手続きのひと塊りです。
ブロックを使うと、メソッドを呼び出す際に、そのメソッドに処理の塊を渡すことができます。
ざっくりいうとこんな感じ。
def greet
yield "こんにちは!今日もいい天気ですね。"
end
greet do |comment|
puts "鈴木さん、#{comment}"
end
鈴木さん、こんにちは!今日もいい天気ですね。
yieldは、メソッドに渡されたブロックを実行できます。
yieldにdo~endで囲まれた手続きの塊が引き渡され、yieldの引数に渡した文字列、"こんにちは!今日もいい天気ですね。"が、ブロック変数に渡され、上記の様に出力されます。
ブロックはRubyにおいて、特徴的な機構の一つで、まず最初に覚えておきたい概念の一つですね。
do~end 構文の書き方
do~end構文は、繰り返し文(each文) などで使ったことがある方が多いかと思います。
今回は、do~end構文を通して、ブロックを学んでいきましょう。
ブロックは、一連の処理(手続き)をメソッドに引き渡す為に使う。ということを念頭に置いて観てください。
また、ここでは詳しく説明しませんが、do~end構文と{...}の違いは、端的には結合度に違いがあり、イテレータ(集合的データ構造への繰り返し処理)にはdo~end構文を、リソースの管理を行う場合は波括弧を利用するとされています。
[1, 2, 3].each do |n|
puts n
end
# 出力=> 1 ,2 ,3
簡単な例だと上記の具合で、よく使用されているかと思います。
FizzBuzzでみるブロック
FizzBuzz問題を参考に、do~end構文を理解していきましょう。
FizzBuzz問題は、下記の様な要件の有名なプログラミングの問題です。
1から100までの数を出力するプログラムを書いてください。ただし、3の倍数のときは数の代わりに「Fizz」と、5の倍数のときは「Buzz」と表示してください。3と5の倍数の時は「FizzBuzz」と表示してください。
これをeachを使った**do~end構文(ブロック)**で書くと以下の様な形になります。
eachは、配列や範囲オブジェクトなどに使用される繰り返しのメソッドです。
(1..100).each do |n|
if n % 15 == 0
puts "FizzBuzz"
elsif n % 5 == 0
puts "Buzz"
elsif n % 3 == 0
puts "Fizz"
else
puts n
end
end
以下の様な形で出力されたかと思います。
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
上述しましたが、eachメソッドは繰り返しの処理を行うためのメソッドで、**do 〜 endのブロック(今回は、数値に合わせて、出力を調整するという手続きの塊)**がメソッドに渡されて、その処理が配列に入った要素に合わせて、繰り返し実行されます。
配列や範囲オブジェクトに対して、|n| の部分でブロック変数として取り出していき、オブジェクトに含まれる要素が無くなるまでFizzBuzzの条件文に従った出力が、繰り返し行われます。その手続きが、eachメソッドに渡されているということですね。
Procオブジェクト(手続きオブジェクト)
さて、lambdaはRuby内で、ブロックをオブジェクトとして扱うことができる様になる、匿名関数 の内の一つです。
Rubyでは、その様な手続きオブジェクトのことを、Procオブジェクトと言います。
もうお分かりかと思いますが、Rubyには**「ブロック」という構文があります。ブロックはdo … endまたは{ … }で囲まれた手続きのひと塊り**です。(二回目)
そこで、**Procオブジェクト(手続きオブジェクト)**を使うと、そのブロックをオブジェクトとして扱うことができる様になります。
ブロックをProcオブジェクトにするには lambda , proc , Proc.new , -> のいづれかを使います。
オブジェクトとして、Procオブジェクトのインスタンスを返すので、変数に代入できます。
lambda(ラムダ)の使い方
上記の、Procクラスや**lambda(ラムダ)**を使うことで、ブロックとして書いた手続きそのものをオブジェクトにして使用することが可能になります。
lambdaで生成されるProcオブジェクトは、Proc.new()や、procメソッドで生成されるProcオブジェクトよりも、厳格でよりメソッドに近い動きをします。
使い方としては、下記のように引数を渡して、ブロックをオブジェクトとして扱えるようになります。
sayHello = lambda { |s| puts "Hello, #{s}!" }
sayHello.call("world")
lambdaでは、設定した引数の数に対して、厳密な数の引数を対応させて渡さなければエラーになったり、評価される範囲が、callやreturnを使っても、よりメソッドに近い形になるので、ヒューマンエラーを防げるよう設計されています。
プログラムにおいて制約は力になります。
なので実際にも、手続きオブジェクトを使用する際は、lambdaが使用されるケースの方が多いようです。
フィボナッチ数列でlambda体験
では、フィボナッチ数列という、第三項以降の項がすべて直前の二項の和になっている数列を出力するプログラムを書いてみましょう。
要は、3番目以降の数字が1個前と2個前を足したものになる数列です。
普通に書くとこんな感じです。
def fibonacci(n)
if n < 2
return n;
else
return fibonacci(n - 2) + fibonacci(n - 1);
end
end
10.times{|i| puts "fibonacci(#{i}) : #{fibonacci(i)}"}
ではこれを、lambdaで書くとどうなるでしょうか。
f = lambda { |n| n < 2 ? n : f[ n - 1 ] + f[ n - 2 ]}
10.times{|i| puts "fibonacci(#{i}) : #{f[i]}"}
なんと、2行で書けました!
irbを起動し、上記両方のコードを貼り付けてみましょう。
どちらのコードでも、下記のように、フィボナッチ数列が出力されたでしょうか?
fibonacci(0) : 0
fibonacci(1) : 1
fibonacci(2) : 1
fibonacci(3) : 2
fibonacci(4) : 3
fibonacci(5) : 5
fibonacci(6) : 8
fibonacci(7) : 13
fibonacci(8) : 21
fibonacci(9) : 34
lambdaの方は、ブロック変数 |n| に、手続きオブジェクトfに[i] (.timesに渡っているブロック内のブロック変数|i|)で渡した値が入ります。
そして、三項演算の形で、nが 2 以下ならそのまま出力、それ以上ならば lambda に引数として渡された値から 1 を引いたものと 2 を引いたものが出力される訳ですね。
また、**->**を使う場合は下記の様によりすっきりと書けます。
f = -> n { n < 2 ? n : f[ n - 1 ] + f[ n - 2 ]}
10.times{|i| puts "fibonacci(#{i}) : #{f[i]}"}
このように、lambdaを扱えると、Procオブジェクトとして、わざわざ名前付きの関数を定義する必要もなく、より抽象的、簡潔に、なかなかオシャレに手続きが実装できますね。
ガンガンこういった実装をしていけるとかっこいいですよね。