15
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Ruby】<< をそのままメソッドの戻り値にするより、[*array, element] を使おう

Last updated at Posted at 2024-04-14

はじめに: <<をそのままメソッドの戻り値にする?

コードレビューをしていると、ときどき以下のようなコードを見かけます。

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] と書けば、既存の配列 arrayelement という要素を追加した配列を作成したことになります。

なので結果として 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

の方が「これがメソッドの戻り値ですよ!」という意図がより明確になるので、ベターなんじゃないかなーと思っています。

コードを書いたり、コードレビューをしたりするときにもし同じようなユースケースに遭遇したら、本記事を参考にしてみてください。

15
7
5

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
15
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?