2011-01-13 8 views
8

¿Existe un consenso sobre cómo evitar que las memorias causen errores debido al estado mutable?¿Cómo debo evitar las causas de errores en Ruby?

En este ejemplo, un resultado almacenado en memoria caché ha mutado su estado, y por lo tanto dio el resultado incorrecto la segunda vez que se llamó.

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 

Enfoques puedo ver para evitar esto son:

  1. greeting podría devolver un dup o clone de @greeting_cache[formality]
  2. greeting podría freeze el resultado de @greeting_cache[formality]. Eso provocaría una excepción cuando memoization_mutator le agregue cadenas.
  3. Revise todo el código que usa el resultado de greeting para asegurarse de que ninguna parte mute de la cadena.

¿Existe un consenso sobre el mejor enfoque? ¿La única desventaja de hacer (1) o (2) disminuir el rendimiento? (También sospecho que congelar un objeto puede no funcionar completamente si tiene referencias a otros objetos)

Nota al margen: este problema no afecta la aplicación principal de la memorización: como Fixnum s son inmutables, el cálculo de las secuencias de Fibonacci no tiene problemas con el estado mutable. :)

+0

Un pequeño comentario sobre el estilo: puede simplificar el método de saludo con el operador || =. De esta manera: def saludo (formalidad); @greeting_cache [formalidad] || = expensive_greeting_calculation (formalidad); end – zaius

+0

@zaius: Eso funciona en la mayoría de los escenarios, pero no funcionaría si 'nil' o' false' fueran un valor válido. –

+0

Ah, cierto. Mi error. – zaius

Respuesta

4

Me inclinaría por devolver un objeto clonado. El impacto en el rendimiento de crear una nueva cadena es casi nulo. Y la congelación expone detalles de implementación.

0

Todavía soy "novato de rubí", y no sé si conocía la diferencia entre los métodos "< <" y "+" a una cadena.

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

Gracias por la sugerencia, pero estoy más después de una solución más general. –

Cuestiones relacionadas