日記帳

日記です。

Ruby で遅延初期化処理付き getter を一発定義

こんな感じでしょうか?

class Module
	def attr_reader_with_initializer(name, initializer_name = nil)
		name = name.to_s
		variable_name = "@#{name}"
		first_accessor = "#{name}_reader_with_initializer".to_sym
		second_accessor = "#{name}_reader".to_sym
		attr_reader name.to_sym
		alias_method(second_accessor, name)

		define_method(first_accessor){
			unless instance_variable_get(variable_name) then
				if block_given? then
					value = yield
				else
					unless initializer_name then
						initializer_name = "create_#{name}"
					end
					value = method(initializer_name.to_sym).call()
				end
				instance_variable_set(variable_name, value)

			end

			instance_eval <<-EOD
				class << self
					alias :#{name} :#{second_accessor} 
				end
			EOD

			instance_variable_get(variable_name)
		}

		alias_method(name, first_accessor)
	end

end

もっとスマートに書けそうな気もするんだけどとりあえず.
使い方は以下の通り.

class Hello
	attr_reader_with_initializer(:message) {
		Time::now.strftime("Hello @ %Y/%m/%d")
	}
end

class Hello2
	def create_message
		Time::now.strftime("Hello @ %Y/%m/%d")
	end

	attr_reader_with_initializer(:message, :create_message)
end

遅延初期化してるのに getter の速度が遅くならないなんて世界中の遅延初期化大好きっ子が大喜びしそうです.そんな人が何人いるかは知りませんが…

でも気に入らない点がいくつか.

  • メソッド名を勝手に使ってる
  • 文字列を引数にした instance_eval を使ってる
  • 高速な getter を定義するために Module#attr_reader() に頼ってる

ってあたりでしょうか?
標準の Module#attr_reader() を仕様変更する方向で実装すれば全部解決しそうな予感もします.

…と思ったけど Module#attr_reader() は複数の引数を渡された場合それら全部の getter を定義する仕様なので合わないですね.