Rubyの面白さを理解するためのメソッド、ブロック、Proc、lambda、クロージャの基本若手エンジニア/初心者のためのRuby 2.1入門(8)(2/3 ページ)

» 2014年09月29日 18時00分 公開
[著:麻田優真、監修:山根剛司株式会社アジャイルウェア]

可変長引数

 ここまで、メソッド定義の基本的な形を解説しました。基本的な形では引数の数は固定でしたが、可変長引数を取ることもできます。可変長引数を用いた例を、method05.rbに示しましょう。

def sum(*args)
  puts args.class
  args.inject(:+)
end
 
p sum(1, 2, 3, 4, 5)
p sum(1)
p sum
method05.rb
$ ruby method05.rb
Array
15
Array
1
Array
nil
method05.rbの実行結果

 ここでは、与えられた任意の数の引数の合計値を求めるメソッドsumを定義して使っています。可変長引数を取る場合、引数の前に「*(アスタリスク)」を付けます。コードの2行目の実行結果が示しているように、メソッドの中では、アスタリスクを付けた引数が配列として扱われます。3行目は少しトリッキーな書き方ですが、配列の内容をたたみ込むinjectメソッドを使って、各要素に対して「+メソッド」を適用し、合計値を求めています。

キーワード引数(Ruby 2.0/2.1の新機能)

 引数の数が多くなってくると、その順番を覚えるのが大変です。そのため、メソッドを作る側としても使う側としても、メンテナンスのための手間が増えてしまいます。そこで、キーワード引数を使うことで、使いやすくメンテナンスしやすいメソッドを用意できます。

 1.9では、ハッシュを使ってキーワード引数のようなことを実現していました。その後、本当のキーワード引数が2.0で登場しましたが、その時点では引数のデフォルト値が必須となっていました。2.1ではさらなる改良が施され、デフォルト値を設定しなくてもキーワード引数を取れるようになりました。

 では、method06.rbに例を示しましょう。

def basic_form(a: "a", b: ["b", "b", "b"], c: :c)
  puts "a(#{a.class}): #{a}"
  puts "b(#{b.class}): #{b}"
  puts "c(#{c.class}): #{c}"
end
 
def complex_form(a, *b, c: "c", d:)
  puts "a(#{a.class}): #{a}"
  puts "b(#{b.class}): #{b}"
  puts "c(#{c.class}): #{c}"
  puts "d(#{d.class}): #{d}"
end
 
basic_form(a: "meow", b: "oink", c: :bowwow)
basic_form(a: "meow")
complex_form("oink", "bowwow", d: "meow", c: "quack")
complex_form
method06.rb
$ ruby method06.rb
a(String): meow
b(String): oink
c(Symbol): bowwow
a(String): meow
b(Array): ["b", "b", "b"]
c(Symbol): c
a(String): oink
b(Array): ["bowwow"]
c(String): quack
d(String): meow
method06.rb:17:in `<main>': missing keyword: d (ArgumentError)
method06.rbの実行結果

 basic_formがキーワード引数の基本形です。キーワードに続けて:(コロン)を書き、その後にデフォルト値を続けます。メソッドの中では、キーワードを変数として使えます。15行目のように一部の引数を省略しても、デフォルト値が使われていることが実行結果から確認できます。

 complex_formは、少し複雑な形です。通常の引数と可変長引数、キーワード引数が混在しています。この場合、通常の引数、可変長引数、キーワード引数、という順番を守る必要があります。全ての引数を埋めて呼び出した16行目の実行は成功していますが、17行目ではArgumentErrorという例外が出て、プログラムが終了しています。

 「d」のようにキーワード引数のデフォルト値を省略した場合、その引数は必須の引数となります。17行目の実行では、必須の引数である「d」を設定していないために、「ArgumentError」つまり「引数のエラー」が例外として出現します。

処理そのものを引数として渡す「ブロック」

 eachメソッドなど、Rubyでは「ブロック」という仕組みを活用して、メソッドに「処理そのものを引数として渡す」というパターンが頻出します。ここでは、何気なく使ってきたブロックについて詳しく解説します。これまで使うばかりであったブロックを、メソッドの引数として利用する方法についても学びます。

 では、メソッドの引数としてブロックを与え、それを利用する基本形を見てみましょう。block01.rbに例を示します。

def repeat(n)
  for i in 1..n
    puts "#{i} =>"
    yield
  end
end
repeat(3) { puts "pyonpyon" }
block01.rb
1 =>
pyonpyon
2 =>
pyonpyon
3 =>
pyonpyon
block01.rbの実行結果

 この例では、引数に指定した回数だけブロックとして与えた処理を繰り返す「repeat」というメソッドを定義しています。

 ここで、4行目に注目してください。「yield」という見慣れない予約語があります。実は、このyieldこそがキモで、メソッドに与えたブロックを実行するという働きがあります。for文によって1から「n」まで1ずつ整数を取り出しながら「i」に代入し、その回数だけyieldが実行されるので、結果的にn回ブロックが実行されることになります。

 では、より高度なブロックの使い方を学ぶために、指定した数の等差数列を生成するジェネレーターを作ってみましょう。等差数列というのは、ある初項から始めて、一定の公差を加えることで生成される数列です。

 例えば、初項2で公差3の場合は、「2、5、8、11、14、17……」という数列になります。サンプルコードをblock02.rbに示します。

def arithmetic_sequence(init: 1, diff: 1, count: 10)
  current = init
  if block_given?
    count.times do
      yield(current)
      current += diff
    end
  else
    Array.new.tap do |a|
      count.times do
        a << current
        current += diff
      end
    end
  end
end
 
arithmetic_sequence(init: 2, diff: 3, count: 5) do |n|
  puts n
end
 
p arithmetic_sequence(init: 2, diff: 3, count: 5)
block02.rb
$ ruby block02.rb
2
5
8
11
14
[2, 5, 8, 11, 14]
block02.rbの実行結果

 まず、3行目に注目してください。「block_given?」メソッドを利用して、メソッドにブロックが与えられているかどうかを判定して、処理を分けています。もしブロックが与えられていれば4〜7行目が実行され、そうでなければ、9〜14行目が実行されます。

 この例では、5行目でyieldに引数を与えています。yieldに与えた引数は、9行目のメソッドに与えたブロックの引数として利用されます。つまり、「arithmetic_sequence」メソッドで生成した等差数列のそれぞれの値がブロック引数nに格納され、メソッドを使う側が自由に利用できるということになります。

 また、ブロックが与えられなかった場合は、配列を用意して、生成した数列を配列に順に格納して呼び出し元に返しています。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。