2017-03-21 38 views
4

我經常memoize的使用begin ... end塊語法Ruby方法:將Ruby`begin ... end`塊短路的恰當習慣用法是什麼?

$memo = {} 
def calculate(something) 
    $memo[something] ||= begin 
    perform_calculation(something) 
    end 
end 

但是,這裏有一個疑難雜症。如果我通過保護條款從begin ... end塊提前返回,結果不是memoized:

$memo = {} 
def calculate(something) 
    $memo[something] ||= begin 
    return 'foo' if something == 'bar' 
    perform_calculation(something) 
    end 
end 
# does not memoize 'bar'; method will be run each time 

我可以通過避免return聲明避免這種情況:

$memo = {} 
def calculate(something) 
    $memo[something] ||= begin 
    if something == 'bar' 
     'foo' 
    else 
     perform_calculation(something) 
    end 
    end 
end 

這工作,但我不」不喜歡它,因爲:

  1. 很容易忘記在這種情況下我不允許使用return
  2. 在許多情況下,它將代碼與守衛子句相對立起來。

除了避開return之外,還有更好的成語嗎?

+0

上的一些公約一篇好文章:http://www.justinweiss.com/articles/4-simple-memoization-patterns-in-ruby-and-one-gem// – Anthony

+1

我想**強烈建議不要使用全局緩存。儘可能保持變量的上下文非常重要,即儘可能使用''''樣式的實例變量。 – tadman

+0

如果您的程序具有純函數,則可以進一步使用它並使用緩存方法調用它們自己。例如[cache-method](https://github.com/seamusabshere/cache_method)。如果需要,爲緩存提供離散API的好處是更容易切換持久化類型。 –

回答

3

我想補充另一個層:

def calculate(something) 
    $memo[something] ||= _calculate(something) 
end 

def _calculate(something) 
    return if something == 'bar' 
    perform_calculation(something) # or maybe inline this then 
end 

這具有提供你,每當你想,一定要得到一個新鮮的計算結果,你可以調用一個方法的額外好處。我會花更多的時間在方法命名上。

4

據我所知,begin ... end不能被短路。你可以做你試圖雖與特效到底該怎麼做:

$memo = {} 
def calculate(something) 
    $memo[something] ||= -> do 
    return 'foo' if something == 'bar' 
    perform_calculation(something) 
    end.call 
end 

話雖這麼說,我從來沒有見過此之前完成,所以它肯定不是地道。

2

解決此問題的一種方法是使用元編程,您可以在該方法定義之後將其包裝。這保留了其中的任何行爲:

def memoize(method_name) 
    implementation = method(method_name) 

    cache = Hash.new do |h, k| 
    h[k] = implementation.call(*k) 
    end 

    define_method(method_name) do |*args| 
    cache[args] 
    end 
end 

這創建了一個充當緩存的閉包變量。這樣可以避免醜陋的全局,但這也意味着如果需要,你無法真正清除該緩存,因此如果傳遞大量不同的參數,它可能會消耗大量內存。要小心!如果需要,可以通過爲任何給定方法x定義一些輔助方法(如x_forget)來添加該功能。

下面是它如何工作的:

def calculate(n) 
    return n if (n < 1) 

    n + 2 
end 
memoize(:calculate) 

然後,你可以看到:

10.times do |i| 
    p '%d=%d' % [ i % 5, calculate(i % 5) ] 
end 

# => "0=0" 
# => "1=3" 
# => "2=4" 
# => "3=5" 
# => "4=6" 
# => "0=0" 
# => "1=3" 
# => "2=4" 
# => "3=5" 
# => "4=6" 
+0

非常有趣!據推測,它是在一個模塊被包括在內。 –

+0

@CarySwoveland這是對Rails中舊的'memoize'方法的重新評估。安息。 – tadman

0

我怕我不正確認識這個問題,因爲它似乎事情很簡單會做。

$memo = {} 

def calculate(something) 
    $memo[something] ||= something == 'bar' ? 'foo' : perform_calculation(something) 
end 

讓我們試試吧。

def perform_calculation(something) 
    'baz' 
end 

calculate('bar') 
    #=> "foo" 
$memo 
    #=> {"bar"=>"foo"} 
calculate('baz') 
    #=> "baz" 
$memo 
    #=> {"bar"=>"foo", "baz"=>"baz"} 
calculate('bar') 
    #=> "foo" 
$memo 
    #=> {"bar"=>"foo", "baz"=>"baz"} 
0

我不知道用return一個解決方案,但在你的例子保護條款,我會用case

$memo = {} 
def calculate(something) 
    $memo[something] ||= case something 
         when 'foo' then 'bar' 
         else perform_calculation(something) 
         end 

end 
相關問題