❓
Immutable な Value Object とメモ化の相性が良くない件
例がいまいちだけど次のようにURLを受け取ってURLの引数を簡単に取得できるクラスがあったとする
require "rack"
class FooUrl
def initialize(url)
@url = url
end
def xxx
params["xxx"]
end
private
def params
@params ||= Rack::Utils.parse_query(URI(@url).query)
end
end
foo_url = FooUrl.new("https://example.com/?xxx=1")
foo_url.xxx # => "1"
これはインスタンスに対して get しかしてないので Value Object と考えられる
Value Object だったら Immutable にした方がいいだろう
というわけでコンストラクタの最後に freeze を入れてみる
require "rack"
class FooUrl
def initialize(url)
@url = url
freeze
end
def xxx
params["xxx"]
end
private
def params
@params ||= Rack::Utils.parse_query(URI(@url).query)
end
end
foo_url = FooUrl.new("https://example.com/?xxx=1")
foo_url.xxx # =>
するとこれ
# ~> -:16:in `params': can't modify frozen FooUrl (FrozenError)
# ~> from -:10:in `xxx'
# ~> from -:21:in `<main>'
freeze する前に params を実行しておくとかダサすぎるしいったいどうしたらいいんだともう何年も悩んでいる
というのを投稿したらコメントで良いアイデアを教えていただいたのでこうしてみた
require "rack"
class FooUrl
def initialize(url)
@url = url
@cache = {}
freeze
end
def xxx
params["xxx"]
end
private
def params
@cache[:params] ||= Rack::Utils.parse_query(URI(@url).query)
end
end
foo_url = FooUrl.new("https://example.com/?xxx=1")
foo_url.xxx # => "1"
これでええやん
解決!
Data クラスを使うときも同様の方法で行ける
Vector = Data.define(:x, :y) do
def initialize(...)
@cache = {}
super
end
def length
@cache[:length] ||= Math.sqrt(x**2 + y**2)
end
end
vector = Vector.new(1, 2)
vector.length # => 2.23606797749979
親クラスの initialize で freeze されているっぽいので super を呼ぶ前に @cache を定義しておく
Discussion
initialize で
@params = []
しておいて、params の中では@params[0] ||= ...
のようにすると、とりえあず動くようになりますね。コメントありがとうございます
self を freeze しても既存のインスタンス変数は変更できるんですね