2011-01-13 15 views
8

Esiste un consenso su come evitare la memoizzazione causando bug dovuti allo stato mutabile?Come si dovrebbe evitare la memoizzazione causando bug in Ruby?

In questo esempio, un risultato memorizzato nella cache ha avuto lo stato modificato e pertanto ha dato il risultato errato la seconda volta che è stato chiamato.

class Greeter 

    def initialize 
    @greeting_cache = {} 
    end 

    def expensive_greeting_calculation(formality) 
    case formality 
     when :casual then "Hi" 
     when :formal then "Hello" 
    end 
    end 

    def greeting(formality) 
    unless @greeting_cache.has_key?(formality) 
     @greeting_cache[formality] = expensive_greeting_calculation(formality) 
    end 
    @greeting_cache[formality] 
    end 

end 

def memoization_mutator 
    greeter = Greeter.new 
    first_person = "Bob" 
    # Mildly contrived in this case, 
    # but you could encounter this in more complex scenarios 
    puts(greeter.greeting(:casual) << " " << first_person) # => Hi Bob 
    second_person = "Sue" 
    puts(greeter.greeting(:casual) << " " << second_person) # => Hi Bob Sue 
end 

memoization_mutator 

Approcci che posso vedere per evitare questo sono:

  1. greeting potrebbe restituire un dup o clone di @greeting_cache[formality]
  2. greeting potrebbe freeze il risultato di @greeting_cache[formality]. Ciò farebbe sorgere un'eccezione quando memoization_mutator aggiunge delle stringhe ad esso.
  3. Controllare tutto il codice che utilizza il risultato di greeting per assicurarsi che non si verifichi alcun mutamento della stringa.

Esiste un consenso sull'approccio migliore? L'unico svantaggio di fare (1) o (2) diminuisce le prestazioni? (Sospetto anche il congelamento di un oggetto potrebbe non funzionare completamente se ha riferimenti ad altri oggetti)

Nota a margine: questo problema non influisce sull'applicazione principale della memoizzazione: come Fixnum s sono immutabili, il calcolo delle sequenze di Fibonacci non lo fa avere problemi con lo stato mutabile. :)

+0

Un piccolo commento sullo stile: è possibile semplificare il metodo di saluto con l'operatore || =. Così: def saluto (formalità); @greeting_cache [formality] || = cost_greeting_calculation (formality); fine – zaius

+0

@zaius: Questo funziona nella maggior parte degli scenari, ma non funzionerebbe se 'nil' o' false' fossero un valore valido. –

+0

Ah, vero. Colpa mia. – zaius

risposta

4

Vorrei sporgermi verso la restituzione di un oggetto clonato. Il colpo di prestazioni di creare una nuova stringa è quasi nulla. E il congelamento espone i dettagli di implementazione.

0

Sono ancora 'newy ruby' e non so se eri a conoscenza della differenza tra i metodi ' ' e '+' in una stringa.

first_person = "Bob" 
puts(greeter.greeting(:casual) + " " + first_person) # => Hi Bob 
second_person = "Sue" 
puts(greeter.greeting(:casual) + " " + second_person) # => Hi Sue 

# str << obj → str 
# str + other_str → new_str 
+0

Grazie per il suggerimento, ma sono di più dopo una soluzione più generale. –

Problemi correlati