tapメソッドとは?tapの具体的な使い方の紹介

Ruby

tapは『selfを引数としてブロックを評価する』『返す値はブロックの評価値ではなくself』という特徴をもつメソッドです。1

今回はtapの利用例について紹介をします。

メソッドチェインの途中結果を確認する

tap内のブロックの評価値は捨てられるため、メソッドチェインの途中でtapを利用しても結果に影響を与えません。この特徴を利用すると、メソッドチェインの途中結果をtapで確認できます。

def tap_example
  "hoge".tap {|string| puts "original: #{string}" }
        .upcase.tap {|string| puts "upcase: #{string}" }
        .reverse.tap {|string| puts "reverse: #{string}" }
end

実行結果

$ rails c
> tap_example
original: hoge
upcase: HOGE
reverse: EGOH
=> "EGOH"

インスタンスの生成と初期設定をまとめて実行し、評価値として返す

newに対してtapを利用することで、ブロック内でインスタンスのメソッド実行やプロパティのセットができます。そして、tapはブロック内で実行された内容が適用されたインスタンスを返します。

たとえば以下のような『インスタンスの作成、初期値のセット、インスタンスの返却』を行うメソッドがあったとします。

class User < ApplicationRecord
  def self.build_user(name)
    user = User.new
    user.name = name
    user
  end
end

実行結果

$ rails c
> user = User.build_user("Bob")
> user.name
=> "Bob"

上記のコードをtapで書き換えると以下のようになります。

class User < ApplicationRecord
  def self.build_user(name)
    User.new.tap do |user|
      user.name = name
    end
  end
end

実行結果

$ rails c
> user = User.build_user("Bob")
> user.name
=> "Bob"

この特性を利用した使用例でよくみかけるのが『Loggerの初期設定をするメソッド』です。
『Loggerの初期設定をするメソッド』をtapを利用しないで実装した場合、以下のようになります。

class MyLogger
  def self.logger
    @logger = ActiveSupport::Logger.new(STDOUT)
    @logger.formatter = Logger::Formatter.new
    @logger.formatter.datetime_format = '%Y-%m-%d %H:%M:%S'
    @logger
  end
end

実行結果

$ rails c
> require 'my_logger'
> MyLogger.logger.formatter.datetime_format
=> "%Y-%m-%d %H:%M:%S"

上記のコードをtapで書き換えると以下のようになります。||=は自己代入演算子です。

class MyLogger
  def logger
    @logger ||= ActiveSupport::Logger.new(STDOUT).tap do |logger|
      logger.formatter = Logger::Formatter.new
      logger.formatter.datetime_format = '%Y-%m-%d %H:%M:%S'
    end
  end
end

実行結果

$ rails c
> require 'my_logger'
> MyLogger.logger.formatter.datetime_format
=> "%Y-%m-%d %H:%M:%S"

オブジェクトを返しつつ、オブジェクトの参照を外す

Rubyのtapはメソッドチェーンだけのものじゃない!では『オブジェクトがセットされたインスタンス変数の参照をはずしつつ、オブジェクトを返す』というメソッドをtapで実装する方法について紹介しています。

たとえば以下のようなメソッドがあったとします。

# リセット対象のnameを返しつつ、変数@nameを初期化する
def reset_name
   name = @name
   @name = nil
   name
end

実行結果

$ rails c
> @name = 'Bob'
> reset_name
=> "Bob"
> @name
=> nil

tapはブロックの実行をしますが、selftapのレシーバ)を返します。ですので、上記のコードをtapで書き換えると以下のようになります。

# self(ここでいう@name.tapの@name)を返しつつ、変数@nameを初期化する
def reset_name
   @name.tap { @name = nil }
end

実行結果

$ rails c
> @name = 'Bob'
> reset_name
=> "Bob"
> @name
=> nil

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考