以下の書き方ってOKだっけ? そして結果は何? と思ったので調査。
array = [1, 2, 3]
hash = { x: 4, y: 5, z: 6 }
p( [ *array ] )
p( [ *hash ] )
p( [ **array ] )
p( [ **hash ] )
p( { *array } )
p( { *hash } )
p( { **array } )
p( { **hash } )
前置き:splat演算子
Ruby ではメソッドの仮引数に、可変長引数 *rest
や、キーワード引数の余り **kwrest
を指定できる。これらは実引数の値をそれぞれ配列やハッシュの形に固めて受け取る。
https://docs.ruby-lang.org/ja/3.1/doc/spec=2fdef.html#method
# すべて持つ(極端な例なのでおすすめしない) def f(a, b, c, m = 1, n = 1, *rest, x, y, z, k: 1, **kwrest, &blk) ... f("a", "b", "c", 2, 3, "foo", "bar", "baz", "x", "y", "z", k: 42, u: "unknown") { }
逆に、実引数側(メソッド呼び出し元)で配列の類やハッシュを展開したいこともある。このときは同じ記号を使って *array
や **hash
と書ける。 *
はsplat演算子、 **
はdouble splat演算子と呼ぶらしい。
https://docs.ruby-lang.org/ja/3.1/doc/spec=2fcall.html
引数の直前に
*
がついている場合、その引数の値が展開されて渡されます。展開はメソッドto_a
を経由して行なわれます。つまり:foo(1,*[2,3,4]) foo(1,*[]) foo(1,*[2,3,4],5) foo(1,*[2,3,4],5,*[6])
は、それぞれ
foo(1,2,3,4) foo(1) foo(1,2,3,4,5) foo(1,2,3,4,5,6)
と同じです。
https://docs.ruby-lang.org/ja/3.1/doc/news=2f2_7_0.html
キーワード引数として扱いたい場合は、明示的にdouble splat演算子(
**
)を足すことで警告を回避できます。このように書けばRuby 3でも同じ意味で動きます。def foo(key: 42); end; foo({key: 42}) # warned def foo(**kw); end; foo({key: 42}) # warned def foo(key: 42); end; foo(**{key: 42}) # OK def foo(**kw); end; foo(**{key: 42}) # OK
配列とハッシュのリテラルでは
ところで、配列のリテラル(配列式)はメソッド呼び出しと似ていて、実際に *
を使って配列の類を展開することがある。
[1, *4..6, 9]
#=> [1, 4, 5, 6, 9]
ハッシュのリテラル(ハッシュ式)の形も、メソッド呼び出しのキーワード引数と似ている。ということは **
による展開が有効なのだろうか?
試してみると使えた。展開できるオブジェクトはハッシュに限らず、暗黙の変換 #to_hash
が定義されていればいいらしい。
h = { x: 4, y: 5, z: 6 }
{ a: 1, **h, b: 9 }
#=> {:a=>1, :x=>4, :y=>5, :z=>6, :b=>9}
htoa = h.to_a
{ a: 1, **htoa, b: 9 }
# no implicit conversion of Array into Hash (TypeError)
htoa.singleton_class.alias_method(:to_hash, :to_h)
{ a: 1, **htoa, b: 9 }
#=> {:a=>1, :x=>4, :y=>5, :z=>6, :b=>9}
また、ハッシュの中で *
を使うことはできない(現在の文法上あり得ない)が、配列の中で **
を使うことはできる。おそらくハッシュが展開されることで、波括弧を省略した記法と同じ扱いになる。配列式はキーワード引数をとらないはずなので、Ruby2以前からあるほうの規則が適用される。
h = { x: 4, y: 5, z: 6 }
[1, 2, **h]
#=> [1, 2, {:x=>4, :y=>5, :z=>6}]
[1, **h, 2]
# syntax error, unexpected ']', expecting => (SyntaxError)
https://docs.ruby-lang.org/ja/3.1/doc/spec=2fliteral.html#hash
メソッドの引数、もしくは配列式の末尾に要素が1つ以上のハッシュを渡す場合は、
{
,}
を省略することができます。method(1,2,3=>4) # method(1,2,{3=>4}) obj[1=>2,3=>4] # obj[{1=>2,3=>4}] [1=>2,3=>4] # [{1=>2, 3=>4}]
答え合わせ
array = [1, 2, 3]
hash = { x: 4, y: 5, z: 6 }
p( [ *array ] ) #=> [1, 2, 3]
p( [ *hash ] ) #=> [[:x, 4], [:y, 5], [:z, 6]] #to_a による変換
p( [ **array ] ) # TypeError ( #to_hash が無い場合)
p( [ **hash ] ) #=> [{:x=>4, :y=>5, :z=>6}] 波括弧を省略したハッシュの扱い
p( { *array } ) # SyntaxError
p( { *hash } ) # SyntaxError
p( { **array } ) # TypeError ( #to_hash が無い場合)
p( { **hash } ) #=> {:x=>4, :y=>5, :z=>6}