Proc/begin式/tapによるインスタンス生成方法の比較

Ruby

独自のLoggerクラスを実装する時をはじめ、インスタンスの生成とインスタンスメソッドの実行をまとめて行いたいケースがあります。具体的には以下のようなコードです。

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

上記のコードはProcオブジェクト、begin式、tapを利用することで簡潔に書き換えられます。
今回はそれぞれの方法を利用して上記のコードを書き換える例について紹介します。

Procオブジェクトを利用する場合

Procクラスはブロックをオブジェクト化するクラスです。
つまりProcオブジェクトとは、Procクラスを利用してオブジェクト化されたブロックのインスタンスのことを指します。

Procオブジェクトを利用すると以下のように書き換えられます。

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

proc {...}で作成されたProcオブジェクトを.callで実行することで@loggerにLoggerインスタンスをセットしています。

begin式を利用する場合

Rubyにおけるbegin式の一般的な利用方法はrescue節を組み合わせた例外処理です。
しかし、begin式には『式全体の評価値は本体/rescue節/else節のうち最後に評価された文の値』という特徴があります。 1

begin式では引数を利用できません。今回の例のように引数を利用しない場合であればbegin式で以下のように書き換えられます。

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

begin式はProcオブジェクトを利用する場合よりも処理速度が早いです。2

tapを利用する場合

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

tapを利用すると以下のように書き換えられます。

class MyLogger
  def self.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

tapはbegin式と違いスコープを作成します。ですので、一時的に利用する変数loggertapのブロック内でのみ利用可能です。
スコープによって変数が周りに影響を与えなくなるため、tapのほうがbegin式よりも安全といえます。

さいごに

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

参考