前記事「【Ruby入門】ブロック」では、ブロックの基本を解説しました。
ブロックとは、処理そのものを部分的にまとめ、投げることができるものでしたよね?
ブロックが理解できたなら、さらに先に進みましょう!
本記事ではProcクラスの使い方や、ラムダ(lambda)まで解説します。
本記事を読み終えると、ブロックやラムダ式、クラスを効率的に使いこなすことができますよ!
ブロックとProcクラス
ブロックとは一連の手続きをまとめて投げるもの、と解説しました。
投げるだけでなく、さらに応用が利くようにできるのです。
そのときに使うのがProcクラスです。
Procクラスの使い方
まずコードから。
sayhello = Proc.new do |name| puts "Hello! #{name}" end
呼び出すときはこうします。
sayhello.call("potepan")
[実行結果]
Hello! potepan
何が起こったのかというと、sayhelloという名前で、受け取った引数をputsで表示する「手続き」を持つオブジェクトができたのです。
sayhelloという名前の、Procオブジェクトにある手続きを呼ぶのがcallです。
callはProcクラスがもとから持っているメソッドなので、私たちはそれについて考える必要はありません。
Procオブジェクトを作るときに、do〜endで囲われたブロックを渡しています。
作ったオブジェクトの中の手続きを呼び出すときの引数がブロック引数となります。
総合すると、先ほどのプログラムはこうなります。
- sayhelloという名前で、Procオブジェクトを作成
- そのオブジェクトにブロックを設定
- 手続きが呼ばれた(つまりcallされた)ときに引数を受け取る。
- 受け取った引数はブロック引数としてブロックに渡る。
- ブロックの中身は引数をputsする処理
そして最後に「sayhello.call(“potepan”)」として結果を受け取ったというわけです。
オブジェクトというと、どうしても変数やメソッドを格納するというイメージがあります。
ここでちょっとパラダイムシフトして、手続きを持つオブジェクト、という理解をしてください。
難しいけど、そのうちハッと気づくときがきっと来ます。
また、呼び出すときはcallメソッドの他に、[]で書く方法もあります。
sayhello["potepan"]
引数はハッキリ決まってなくとも良い
引数の数が仕様上決まらないときは、ブロック変数を「|*配列|」の形式にして配列で受け取ることができます。
upsample1 = Proc.new do |*args| args.each do |str| p str.upcase end end upsample1.call("a", "b", "c")
[実行結果]
"A" "B" "C"
今のところは3つですが、増減しても問題なく動作します。
ラムダ(lambda)
ラムダ(lambda)も手続きを持つオブジェクトを作成できますが、Proc.newと比較して何点か異なる性質があります。
順に解説していきます。
ラムダとは何か
Ruby以外の言語でも、ラムダは普通にあります。ただし、ラムダ式と「式」が付きます。
いずれにしても、手続きを持って運ぶという意味は変わりません。
Procオブジェクトを作る手段をProc.newとラムダのいずれを使うか、という感じです。
ラムダの使い方
ラムダとProc.newのちがいを見ていきましょう。
ちがいに注目するので、内容は超簡単なものにしました。
upsample3 = lambda do |para1, para2, para3| p para1.upcase p para2.upcase p para3.upcase end upsample3.call("a", "b", "c")
[実行結果]
"A" "B" "C"
Proc.newをラムダに置き換えただけですね。
復習のために、Proc.newバージョンも見ておきましょう。
upsample2 = Proc.new do |para1, para2, para3| p para1.upcase p para2.upcase p para3.upcase end upsample2.call("a", "b", "c")
実行結果は同じです。
ラムダとProc.newの比較
前章で、ちがいは書き方くらいかなと思われたでしょうか?
ではなぜ2つ存在しているのか、2つのちがいは何か、というのを解説します。
引数の数が厳密になる
引数の数が厳密になります。Proc.newは数が異なっていてもエラーになりません。
sample1 = Proc.new do |para1, para2, para3| p para1 p para2 p para3 end sample1.call("a", "b")
[実行結果]
"a" "b" nil
数が合わないなりに、なんとか動きます。
一方ラムダではそうはいきません。
sample2 = lambda do |para1, para2, para3| p para1 p para2 p para3 end sample2.call("a", "b")
[実行結果]
wrong number of arguments (given 2, expected 3) (ArgumentError)
引数の数が、本来は3なのに2個しか来てない!と怒られてしまいました。
ただし配列にすれば問題ありません。
sample3 = lambda do |*para| p para[0] p para[1] p para[2] end sample3.call("a", "b")
[実行結果]
"a" "b" nil
returnが使える
ブロックから値を返すときに、returnを使ったときの挙動が異なります。
まずはラムダからです。
def sample4(greeting) lambda do |x| return greeting + x + "さん" end end morning = sample4("おはよう!") p morning.call("honda")
[実行結果]
"おはよう!hondaさん"
次に、Proc.newです。
def sample5(greeting) Proc.new do |x| return greeting + x + "さん" end end morning = sample5("おはよう!") p morning.call("honda")
[実行結果]
unexpected return (LocalJumpError)
若干強引な解説をします。
ラムダの場合、returnしたところでブロックはまだ生き残ったままです。
よって、「morning = sample4(“おはよう!”)」を終えてもまだ生きているために、次の「p morning.call(“honda”)」に来ても実行可能です。
ところがProc.newの場合はそうではありません。
returnした段階でオブジェクトが消えてしまい、次の「p morning.call(“honda”)」ではすでにブロックは消滅した後です。
よって、すでに消滅してしまったmoriningに対してcallを実行しようとしてエラーになったというわけです。
まとめ
本記事ではブロック、とりわけlambdaとProcについて解説しました。
両者をうまく使い分けて、効率的なプログラミングができるようになってくださいね!