メソッドの引数

デフォルト引数とかよくわからないって話を聞いたので、ちょっと解説してみます。
数、型の異なる引数群について同名のメソッドを複数宣言できる機能をオーバーロードとか言いますが、Rubyにはその機能はありません。

irb(main):001:0> class A
irb(main):002:1>   def foo(a)
irb(main):003:2>     puts "foo(a) <-- #{a.inspect}"
irb(main):004:2>   end
irb(main):005:1>
irb(main):006:1*   def foo(a, b)
irb(main):007:2>     puts "foo(a, b) <-- #{a.inspect}, #{b.inspect}"
irb(main):008:2>   end
irb(main):009:1> end
=> nil
irb(main):010:0>
irb(main):011:0* a1 = A.new
=> #<A:0x2ed194c>
irb(main):012:0>
irb(main):013:0* a1.foo(1)
ArgumentError: wrong number of arguments (1 for 2)
        from (irb):13:in `foo'
        from (irb):13
irb(main):014:0>
irb(main):015:0* a1.foo(1,2)
foo(a, b) <-- 1, 2
=> nil
irb(main):016:0>

でもこれでは、場合によって違う引数を渡したりすることができませんよね。それを実現する方法の一つがデフォルト引数です。

デフォルト引数

irb(main):016:0> class A
irb(main):017:1>   def foo(a, b = 2)
irb(main):018:2>     puts "foo(a, b) <-- #{a.inspect}, #{b.inspect}"
irb(main):019:2>   end
irb(main):020:1> end
=> nil
irb(main):021:0>
irb(main):022:0* a2 = A.new
=> #<A:0x2ec27a8>
irb(main):023:0>
irb(main):024:0* a2.foo(1)
foo(a, b) <-- 1, 2
=> nil
irb(main):025:0>
irb(main):026:0* a2.foo(1,2)
foo(a, b) <-- 1, 2
=> nil
irb(main):027:0>

引数に=で区切るとデフォルト値が宣言できます。
デフォルト引数を宣言するときには、一つのルールがあります。必ずデフォルト値を指定した引数は、指定されていない引数よりも後に宣言される必要があります。ですので、以下のコードはSyntaxErrorが発生します。

  def foo(a = 1, b)
    puts "foo(a, b) <-- #{a.inspect}, #{b.inspect}"
  end

これがもしOKだったら、a2.foo('x')とa2.foo('x', 'y')で渡された値がそれぞれ、a == 1 and b == 'x' と a == 'x' and b == 'y' となってしまい、直観的じゃないですよね。


また、Rubyの素敵さが炸裂するのは、引数のデフォルト値として指定する部分にメソッドやコードを書くことができちゃうところです。

irb(main):037:0* class A
irb(main):038:1>   def foo(a, b = bar)
irb(main):039:2>     puts "foo(a, b) <-- #{a.inspect}, #{b.inspect}"
irb(main):040:2>   end
irb(main):041:1>
irb(main):042:1*   def bar
irb(main):043:2>     puts "bar"
irb(main):044:2>     return "bar"
irb(main):045:2>   end
irb(main):046:1> end
=> nil
irb(main):047:0>
irb(main):048:0* a3 = A.new
=> #<A:0x2ea4ec4>
irb(main):049:0>
irb(main):050:0* a3.foo(1)
bar
foo(a, b) <-- 1, "bar"
=> nil
irb(main):051:0>
irb(main):052:0* a3.foo(1,2)
foo(a, b) <-- 1, 2
=> nil
irb(main):053:0> class A
irb(main):054:1>   def foo(a, b = (puts "bar"; "bar"))
irb(main):055:2>     puts "foo(a, b) <-- #{a.inspect}, #{b.inspect}"
irb(main):056:2>   end
irb(main):057:1> end
=> nil
irb(main):058:0>
irb(main):059:0* a4 = A.new
=> #<A:0x2e94b8c>
irb(main):060:0>
irb(main):061:0* a4.foo(1)
bar
foo(a, b) <-- 1, "bar"
=> nil
irb(main):062:0>

おおー、()でくくって、;で区切れば複数の式も書けちゃうんですねー。知らんかった。

調子に乗って、こんなん書いてみました。

irb(main):001:0> class A
irb(main):002:1>   def foo(a, b = (s = 'code_in_args'; puts s; s))
irb(main):003:2>     puts "foo(a, b) <-- #{a.inspect}, #{b.inspect}"
irb(main):004:2>   end
irb(main):005:1> end
=> nil
irb(main):006:0> A.new.foo(1)
code_in_args
foo(a, b) <-- 1, "code_in_args"
=> nil
irb(main):007:0>

おおー、引数の宣言の中で変数を宣言できちゃうんだ!
まあ実際こう書くことはまずないと思うけどすげー!

可変長引数

違う数の引数を渡す仕組みはデフォルト引数だけじゃありません。可変長引数も使えます。

irb(main):035:0* class B
irb(main):036:1>   def foo(*args)
irb(main):037:2>     puts "args.class => #{args.class}"
irb(main):038:2>     puts "args.length => #{args.length}"
irb(main):039:2>     puts "foo(*args) <-- #{args.inspect}"
irb(main):040:2>   end
irb(main):041:1> end
=> nil
irb(main):042:0>
irb(main):043:0* b1 = B.new
=> #<B:0x2ea741c>
irb(main):044:0>
irb(main):045:0* b1.foo(1)
args.class => Array
args.length => 1
foo(*args) <-- [1]
=> nil
irb(main):046:0>
irb(main):047:0* b1.foo(1,2)
args.class => Array
args.length => 2
foo(*args) <-- [1, 2]
=> nil
irb(main):048:0>
irb(main):049:0* b1.foo(1,2,3)
args.class => Array
args.length => 3
foo(*args) <-- [1, 2, 3]
=> nil
irb(main):050:0>
irb(main):051:0* b1.foo(1,[2,3])
args.class => Array
args.length => 2
foo(*args) <-- [1, [2, 3]]
=> nil
irb(main):052:0>

引数の前に*をつけると、それは引数群を配列として受け取ることができます。これは複数指定できないっす。それから(たぶんデフォルト引数と同じ理由で)普通の引数の前に書くことはできません。

名前付き引数(っぽいもの)

これはメソッドを定義する方じゃなくて、呼び出す方の約束事なんですけど、最後の引数にHashの中身を書くとHashとして解釈してくれます。要は{}を省略できるってことっす。

irb(main):206:0* class C
irb(main):207:1>   def foo(a)
irb(main):208:2>     puts "foo(a) <-- #{a.inspect}"
irb(main):209:2>   end
irb(main):210:1> end
=> nil
irb(main):211:0>
irb(main):212:0* c1 = C.new
=> #<C:0x2ed5600>
irb(main):213:0>
irb(main):214:0* c1.foo(:x => 100, :y => 200, :z => 300)
foo(a) <-- {:x=>100, :y=>200, :z=>300}
=> nil
irb(main):215:0>
irb(main):216:0* c1.foo({:x => 100, :y => 200, :z => 300})
foo(a) <-- {:x=>100, :y=>200, :z=>300}
=> nil
irb(main):217:0>
irb(main):218:0* c1.foo('x' => 100, 'y' => 200, 'z' => 300)
foo(a) <-- {"x"=>100, "y"=>200, "z"=>300}
=> nil
irb(main):219:0>
irb(main):220:0* c1.foo({'x' => 100, 'y' => 200, 'z' => 300})
foo(a) <-- {"x"=>100, "y"=>200, "z"=>300}
=> nil
irb(main):221:0>
irb(main):222:0* c1.foo(1 => 100, 2 => 200, 3 => 300)
foo(a) <-- {1=>100, 2=>200, 3=>300}
=> nil
irb(main):223:0>
irb(main):224:0* c1.foo({1 => 100, 2 => 200, 3 => 300})
foo(a) <-- {1=>100, 2=>200, 3=>300}
=> nil
irb(main):225:0>

c1.foo(:x => 100, :y => 200, :z => 300) っていう書き方は、一見名前付き引数に見えますが、実はHashとして扱っているだけ、っていうのがRubyの素敵なところですよね。
名前付き引数は、デフォルト引数よりも必然性の低いものを使うのが普通です。
で、こういうのってデフォルト値を指定したくなっちゃうことは多々あるので、やってみましょう。

irb(main):311:0* class C
irb(main):312:1>   def foo(a = {})
irb(main):313:2>     options = {:x => 100, :y => 200, :z => 300}.update(a || {})
irb(main):314:2>     puts "foo(a) <-- #{a.inspect}"
irb(main):315:2>     puts "options : #{options.inspect}"
irb(main):316:2>   end
irb(main):317:1> end
=> nil
irb(main):318:0>
irb(main):319:0* c2 = C.new
=> #<C:0x2e3af10>
irb(main):320:0>
irb(main):321:0* c2.foo
foo(a) <-- {}
options : {:x=>100, :y=>200, :z=>300}
=> nil
irb(main):322:0>
irb(main):323:0* c2.foo(nil)
foo(a) <-- nil
options : {:x=>100, :y=>200, :z=>300}
=> nil
irb(main):324:0>
irb(main):325:0* c2.foo(:x => 300)
foo(a) <-- {:x=>300}
options : {:x=>300, :y=>200, :z=>300}
=> nil
irb(main):326:0>
irb(main):327:0* c2.foo(:y => 400, :x => 500)
foo(a) <-- {:x=>500, :y=>400}
options : {:x=>500, :y=>400, :z=>300}
=> nil
irb(main):328:0>
irb(main):329:0* c2.foo(:x => 300, :y => 400, :z => 500)
foo(a) <-- {:x=>300, :y=>400, :z=>500}
options : {:x=>300, :y=>400, :z=>500}
=> nil
irb(main):330:0>

デフォルト式で{}を指定していますが、nilが指定されるかもしれないなーと思ったので、

    options = {:x => 100, :y => 200, :z => 300}.update(a || {})

updateの引数に a || {} を指定してます。

組み合わせてみよう!

実はこの辺のことはここに詳しく書いてあります。
http://www.ruby-lang.org/ja/man/html/_A5AFA5E9A5B9A1BFA5E1A5BDA5C3A5C9A4CEC4EAB5C1.html#a.a5.e1.a5.bd.a5.c3.a5.c9.c4.ea.b5.c1

で、ここには引数の順番が書いてあります。

    * デフォルト式のない引数(複数指定可)
    * デフォルト式のある引数(複数指定可)
    * * を伴う引数(1つだけ指定可)
    * & を伴う引数(1つだけ指定可)

というわけでよくあるパターンを書いてみましょう〜

irb(main):179:0* class D
irb(main):180:1>   def foo(a, b, options = {})
irb(main):181:2>     puts "foo(a, b, options = {}) <-- #{a.inspect}, #{b.inspect}, #{options.inspect}"
irb(main):182:2>   end
irb(main):183:1>
irb(main):184:1*   def bar(a = 'X', *parameters)
irb(main):185:2>     puts "bar(a = 'X', *parameters) <-- #{a.inspect}, #{parameters.inspect}"
irb(main):186:2>   end
irb(main):187:1> end
=> nil
irb(main):188:0>
irb(main):189:0* d1 = D.new
=> #<D:0x2f00864>
irb(main):190:0>
irb(main):191:0* d1.foo(1, 2)
foo(a, b, options = {}) <-- 1, 2, {}
=> nil
irb(main):192:0>
irb(main):193:0* d1.foo(1, 2, :x => 1, :y => 2)
foo(a, b, options = {}) <-- 1, 2, {:x=>1, :y=>2}
=> nil
irb(main):194:0>
irb(main):195:0* d1.bar
bar(a = 'X', *parameters) <-- "X", []
=> nil
irb(main):196:0>
irb(main):197:0* d1.bar('a')
bar(a = 'X', *parameters) <-- "a", []
=> nil
irb(main):198:0>
irb(main):199:0* d1.bar('a', 2, 3, 4)
bar(a = 'X', *parameters) <-- "a", [2, 3, 4]
=> nil
irb(main):200:0>
irb(main):201:0* d1.bar('a', 2, 3, 4, :x => 1, :y => 2)
bar(a = 'X', *parameters) <-- "a", [2, 3, 4, {:x=>1, :y=>2}]
=> nil
irb(main):202:0>

まあ、こんな感じで色々出来るわけですが、上のbarの引数 *parametersに名前付き引数を渡した場合にそれをoptionsとしてゲットしたい場合はどうするのか、よくあるパターンなので紹介します

irb(main):344:0* class D
irb(main):345:1>   def bar(a = 'X', *parameters)
irb(main):346:2>     options = parameters.last.is_a?(Hash) ? parameters.pop : {}
irb(main):347:2>     options = {:x => 100, :y => 200, :z => 300}.update(options)
irb(main):348:2>     puts "bar(a = 'X', *parameters) <-- #{a.inspect}, #{parameters.inspect}"
irb(main):349:2>     puts "options : #{options.inspect}"
irb(main):350:2>   end
irb(main):351:1> end
=> nil
irb(main):352:0>
irb(main):353:0* d1 = D.new
=> #<D:0x2bb6384>
irb(main):354:0>
irb(main):355:0* d1.bar
bar(a = 'X', *parameters) <-- "X", []
options : {:x=>100, :y=>200, :z=>300}
=> nil
irb(main):356:0>
irb(main):357:0* d1.bar('a')
bar(a = 'X', *parameters) <-- "a", []
options : {:x=>100, :y=>200, :z=>300}
=> nil
irb(main):358:0>
irb(main):359:0* d1.bar('a', 2, 3, 4)
bar(a = 'X', *parameters) <-- "a", [2, 3, 4]
options : {:x=>100, :y=>200, :z=>300}
=> nil
irb(main):360:0>
irb(main):361:0* d1.bar('a', 2, 3, 4, :x => 1, :y => 2)
bar(a = 'X', *parameters) <-- "a", [2, 3, 4]
options : {:x=>1, :y=>2, :z=>300}
=> nil
irb(main):362:0>

複雑なデータも結構すっきり渡せるっていいっすよねー。すばらしー。