🐘

【Ruby】tap メソッドの使い方

2024/03/12に公開

概要

  • tap メソッドはブロックにオブジェクト(レシーバ)自身を渡す
  • 最終評価として、そのオブジェクト(レシーバ)を返す

https://docs.ruby-lang.org/ja/latest/method/Object/i/tap.html

環境

❯ ruby -v
ruby 3.0.6p216 (2023-03-30 revision 23a532679b) [arm64-darwin23]

解説

例えば以下のようなコードのとき、tapはブロックの評価が終わった後に実行されたオブジェクト(レシーバ)自身を返すので、結果は"a"になります

"a".tap do |x|
end

=> "a"

ブロック内でレシーバに対してメソッドを実行しても、返り値はレシーバになります。

"a".tap do |x|
  x.upcase
end
=> "a"

ブロック内で破壊的変更をすると、変更後の値が返ります。

"a".tap do |x|
  x.upcase!
end
=> "A"

注意

変数に格納したレシーバを、tap のブロック内で破壊的に変更すると、変数自体も変更されてしまい、
予期せぬバグを引き起こすおそれがあります。

reciever = "a"
reciever.tap do |x|
  x.upcase!
end

# レシーバが変更されている
reciever
=> "A"

ユースケース

自分の現場では、空の配列やハッシュに対して .tap を実行し、レシーバに要素を追加していくという処理をよくやっています。

[].tap do |x|
  x << 1 if true
  x << 2 if true
  x << 3 if false
end
=> [1, 2]

インスタンスの初期値を変更するということもできます。

class User
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end
end

user = User.new("John", 20)
user.name
=> "John"

user.tap do |x|
  x.name = "Doe"
end
user.name
# name が変更されている
=> "Doe"

コンストラクタにデフォルトで nil を指定してあげると、以下のようにインスタンス生成することもできます。

class User
  attr_accessor :name, :age, :email

  def initialize(name: nil, age: nil)
    @name = name
    @age = age
  end
end

user = User.new.tap do |x|
  x.name = "John"
  x.age = 25
end

user.name
=> "John"

user.age
=> 25

まとめ

返り値がレシーバになるというところが、前回投稿した each_with_object と似ている(each_with_object は各ブロックの返り値が引数で渡したもの)ので、こちらの記事と一緒に見ると、差分で理解しやすいと思います。
https://zenn.dev/ebina_shohei/articles/887905eb25a1ae

Discussion