2022/08/18 今回の気になった bugs.ruby のチケット
今週はメモ化を行う API を Ruby のコア機能に入れる議論がありました。
[eature #18934] Proposal: Introduce method results memoization API in the core
class Test def foo puts "call!" 5 end # foo メソッドをメモ化する memoized :foo end o = Test.new # 1回目の呼び出しはメソッド本体が呼び出される o.foo # prints "call!", returns 5 # 2回目以降はメソッドの本体は呼び出されず1回目の結果を返す o.foo # returns 5 immediately
- 具体的な例として以下のようなコードが上げられている
SomeTokenizer#call
が非効率なメソッドの場合にボトルネックになる可能性がある
class Sentence attr_reader :text def initialize(text) = @text = text def tokens() = SomeTokenizer.new(text).call def size() = tokens.size def empty?() = tokens.all?(&:whitespace?) def words() = tokens.select(&:word?) def service?() = words.empty? end many_sentences .reject(&:empty?) .select { _1.words.include?('Ruby') } .map { _1.words.count / _1.tokens.count }
- メモ化の手段として
||=
を使うイディオムもある
def words()= @words ||= tokens.select(&:word?)
- しかし、イディオムにはいくつか欠点があり例えば
service?
のようにnil / false
を返すようなメソッドの場合にはその都度処理が呼ばれてしまう - なのでそれを回避する場合は以下のように定義する必要がある
def empty? # @empty が定義されている場合のみ tokens.all?(&:whitespace?) を呼び出す return @empty if defined?(@empty) @empty = tokens.all?(&:whitespace?) end
- またインスタンス変数を使うことで
#inspect
の結果などに保持している値が含まれてしまう
class Sentence def initialize(text) @text = text end def empty? # @empty が定義されている場合のみ tokens.all?(&:whitespace?) を呼び出す return @empty if defined?(@empty) @empty = @text.empty? end end require "yaml" s = Sentence.new('Ruby is cool') puts s.to_yaml # => --- !ruby/object:Sentence # text: Ruby is cool p s.empty? # => false # s の中身が汚染されてしまう puts s.to_yaml # --- !ruby/object:Sentence # text: Ruby is cool # empty: false
- メモ化するライブラリとして
memoist
やmemo_wise
が紹介されているmemo_wise
の方が新しくて効率がいいらしい- これらのライブラリでは
nil / false
の戻り値にも対応している
# 使用例 class Sentence prepend MemoWise # ... memo_wise def empty?() = tokens.all?(:whitespace?) end
- ただし、上記のライブラリを使った場合でもインスタンス変数を使っているのでオブジェクトが汚染されてしまう
p s.empty? # false p s # #<Sentence:0x00007f0f474eb418 @_memo_wise={:empty?=>false}, @text="Ruby is cool"> puts s.to_yaml # --- !ruby/object:Sentence # _memo_wise: # :empty?: false # text: Ruby is cool