2016-10-14 9 views
2

В Python можно легко украсить методы, чтобы они запоминают их результат:Как кэшировать результаты метода в Рубине

def store(self): 
    a = line1() 
    b = line2(a) 
    return line3(b) 

=>

from lazy import lazy 

@lazy 
def store(self): 
    a = line1() 
    b = line2(a) 
    return line3(b) 

Есть ли некоторые аналогичные идиомы в Ruby, для метода расчета результат только один раз?

+0

Является ли модуль для memoization в python действительно называемым 'lazy'? Это было бы просто неправильно ... – ndn

+0

@ndn, не совсем. «Ленькая оценка - это стратегия оценки, которая задерживает оценку выражения до тех пор, пока его значение не понадобится, а также избегает повторных оценок» https://en.wikipedia.org/wiki/Lazy_evaluation –

+0

После стольких лет это первый раз Я слышу * «избегает повторных оценок» * как часть определения. Человек каждый день узнает что-то новое. Тем не менее, я думаю, что это немного вводит в заблуждение. Почему бы не использовать однозначную * memoize *? – ndn

ответ

3

В Ruby этого обычно называют memoization и он принимает наивную форму:

def store 
    @store ||= begin 
    a = line1 
    b = line2(a) 
    line3(b) 
    end 
end 

Есть важные проблемы, если этот код используется в многопоточной среде, хотя, поэтому есть драгоценные камни которые управляют этим и гарантируют, что ваши ленивые инициализаторы запускаются только один раз, если это вызывает беспокойство.

+0

Даже игнорируя потоки, наивная форма не работает для значений «nil» или «false». Спасибо за советы по терминологии. Я нашел https://github.com/dkubb/memoizable –

+2

Это хороший момент, но в 99% случаев наивная форма выполнит свою работу. То, что 1% времени может быть трудно уместно, например, если «false» является допустимым возвращаемым значением, поэтому вам нужно знать, как это происходит. Правильный модуль memoization охватывает вас во всех случаях. – tadman

2

Другой вариант, который работает с false и nil тоже:

def store 
    unless defined?(@store) 
    a = line1 
    b = line2(a) 
    @store = line3(b) 
    end 

    @store 
end 
1

Не совсем. Существует оператор (||=), который присваивает значение только в том случае, если левый операнд является ложным (nil или false). Обычно использование этого с переменной экземпляра делает трюк.

Memoist и Memoizable - драгоценные камни, которые часто используются для этой цели.

Но я предполагаю, что ваш вопрос более общий - как вы реализуете декораторы методов в рубине?

class Module 
    def decorate(method_name, &decoration) 
    undecorated_method = instance_method(method_name) 

    define_method(method_name) do 
     decoration.call(&undecorated_method.bind(self)) 
    end 
    end 

    def memoize(method_name) 
    @values ||= {} 

    decorate(method_name) do |&evaluator| 
     unless @values.key?(method_name) 
     @values[method_name] = evaluator.call 
     end 

     @values[method_name] 
    end 
    end 
end 

class SuperComputer 
    memoize def answer 
    puts "Pending... 7½ million years remaining." 
    42 
    end 
end 

deep_mind = SuperComputer.new 
deep_mind.answer # => 42 (and prints) 
deep_mind.answer # => 42 (doesn't print) 

В настоящее время существует много лучших от этой реализации (арг, kwargs, блоки, каждый экземпляр запоминания, и т.д.), но это должно дать вам общее представление о том, как декораторы могут быть реализованы без необходимость внесения изменений в язык.

+0

изменить '@values ​​[имя_процесса] || = оценщик.call' на' @ values.has_key? (Имя_пользователя)? @values ​​[имя_процесса]: @values ​​[method_name] = valuator.call', и это будет работать для всех falseys i.e 'nil' и' false' – bjhaid

+0

@bjhaid, изначально я хотел сохранить его как можно более простым. Готово. – ndn

+0

Этот ответ очень интересный – bjhaid