7
3

More than 1 year has passed since last update.

splat演算子と [*array] と {**hash}

Posted at

以下の書き方って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}
7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3