はじめに: <<
をそのままメソッドの戻り値にする?
コードレビューをしていると、ときどき以下のようなコードを見かけます。
def do_something
foo = []
# 処理があれこれ続く
# ...
# ...
foo << 123
end
bar = do_something
puts bar
注目してほしいのは foo << 123
の行です。
ここは配列 foo
に123を追加し、なおかつそのときの戻り値をこのメソッド(do_something
メソッド)の戻り値にしています。
def do_something
foo = []
# 以下の1行は2つの意味を持つ
# - fooに123を追加する
# - 123を追加したfooをこのメソッドの戻り値とする
foo << 123
end
たしかに配列に要素を追加する <<
は戻り値としてその配列自身を返しますが、ふだん <<
を使うときはあくまで要素を追加するだけで、<<
の戻り値を利用することは少ないです(少なくとも僕の周りでは)。
arr = []
# << は要素を追加するだけで、その戻り値は利用しない(ことが多い)
arr << 1
arr << 2
arr.each do |el|
# ...
そのため、メソッドの最後に登場するfoo << 123
は見方によっては「要素を追加してるだけ?」と見えてしまいます。
def do_something
foo = []
# ...
# ...
# ...
foo << 123 # 要素を追加しておしまい?
end
また、1行で「要素の追加」と「メソッドの戻り値を返す」という2つの仕事をしている点もややこしいです。
おすすめ: <<
の代わりに [*array, element]
を使う
こういった場合は [*array, element]
に置き換えると意図が明確になります。
def do_something
foo = []
# ...
# ...
# ...
[*foo, 123]
end
<<
を使ったときとは異なり、こちらの方が [*foo, 123]
という配列をメソッドの戻り値にしていることが伝わりやすくなるはずです。
なお、[*foo, 123]
が何をやっているのかはこの下で説明します。
*
って何のために付けてるの?
*
は配列を展開する演算子です。
*
を付けなかったときと、付けたときの挙動の違いは以下のコードをご覧ください。
a = [1, 2, 3]
# *なしだと配列がネストしてしまう
[a, 4]
#=> [[1, 2, 3], 4]
# *を付けると配列aが展開されてフラットな配列になる
[*a, 4]
#=> [1, 2, 3, 4]
つまり [*array, element]
と書けば、既存の配列 array
に element
という要素を追加した配列を作成したことになります。
なので結果として array << element
と(ほぼ)同じことをやっていることになります。
では <<
の戻り値はどういうときに使うのか
<<
の戻り値は以下のように連続して<<
を呼び出したいときに使います。
(つまり、メソッドチェーンの一種。ですが、ふだんこういうコードを書く機会もあまりないですね……)
# https://docs.ruby-lang.org/ja/latest/method/Array/i/=3c=3c.html
ary = [1]
ary << 2 << 3 << 4
p ary #=> [1, 2, 3, 4]
でも、configなどで設定値を複数件追加したいときとかはちょっと便利かな?
Rails.application.configure do
# foo.example.com と bar.example.com を許可hostに追加する
config.hosts << "foo.example.com" << "bar.example.com"
end
注意: <<
と [*array, element]
はまったく同じではない
厳密にいうと、 <<
と [*array, element]
はまったく同じ、ではありません。
<<
は元の配列に要素を追加するのに対し、 [*array, element]
は元の配列を利用して新しい配列のインスタンスを作成します。
以下のコードを見て object_id
の値に注目してください。
a = [1]
a.object_id #=> 20800
(a << 2).object_id #=> 20800
a = [1]
a.object_id #=> 33620
[*a, 2].object_id #=> 39980
<<
を使った場合は object_id
がどちらも20800なので、同一のインスタンスであることを示しています。
一方、 [*array, element]
は33620と39980で値が変わっています。
これは a
と [*a, 2]
でインスタンスが異なることを示しています。
とはいえ、a
がローカル変数で、なおかつ [*a, 2]
(または a << 2
)の結果をメソッドの戻り値とする場合はこの違いが問題になることはほぼないでしょう。
(a
がメソッドの仮引数だったり、インスタンス変数だったり、 [*a, 2]
や a << 2
のあとにも処理が続いたりする場合は少し話が変わってきますが、話題が広がりすぎるのでここでは割愛します)
まとめ
というわけで、この記事では配列に要素を追加して、なおかつその配列をメソッドの戻り値として返したいときの記法について説明してみました。
def do_something
foo = []
# ...
# ...
# ...
foo << 123
end
みたいな書き方が絶対にダメというわけではないですし、人によっては「全然アリ」と考えるかもしれませんが、個人的には foo << 123
よりも、
def do_something
foo = []
# ...
# ...
# ...
[*foo, 123]
end
の方が「これがメソッドの戻り値ですよ!」という意図がより明確になるので、ベターなんじゃないかなーと思っています。
コードを書いたり、コードレビューをしたりするときにもし同じようなユースケースに遭遇したら、本記事を参考にしてみてください。