La pregunta es sencilla. ¿Se puede considerar inmutable un tipo que puede cambiar su estado interno sin que sea observable desde el exterior?
Ejemplo simplificado:
public struct Matrix { bool determinantEvaluated; double determinant; public double Determinant { get //asume thread-safe correctness in implementation of the getter { if (!determinantEvaluated) { determinant = getDeterminant(this); determinantEvaluated = true; } return determinant; } } }ACTUALIZACIÓN : se aclaró el problema de seguridad de subprocesos, ya que estaba causando distracción.
Depende.
Si está documentando para autores de código de cliente o razonando como autor de código de cliente, entonces le preocupa la interfaz del componente (es decir, su estado y comportamiento observables externamente) y no sus detalles de implementación (como la representación interna). ).
En este sentido, un tipo es inmutable incluso si almacena en caché el estado, incluso si se inicializa con pereza, etc., siempre que estas mutaciones no sean observables externamente. En otras palabras, un tipo es inmutable si se comporta como inmutable cuando se usa a través de su interfaz pública (o sus otros casos de uso previstos, si corresponde).
Por supuesto, esto puede ser complicado de hacer bien (con un estado interno mutable, es posible que deba preocuparse por la seguridad de los subprocesos, el comportamiento de serialización/serialización , etc.). Pero suponiendo que lo haga bien (al menos en la medida en que lo necesite), no hay razón para no considerar este tipo inmutable.
Obviamente, desde el punto de vista de un compilador o un optimizador, dicho tipo normalmente no se considera inmutable (a menos que el compilador sea lo suficientemente inteligente o tenga alguna "ayuda" como sugerencias o conocimiento previo de algunos tipos) y las optimizaciones que se pretendían para tipos inmutables puede no ser aplicable, si este es el caso.
Sí, inmutable puede cambiar su estado, siempre que los cambios no se vean para otros componentes del software (generalmente cachés). Muy parecido a la física cuántica: un evento debe tener un observador para ser un evento.
En su caso, una posible implementación es algo así:
public class Matrix { ... private Lazy<Double> m_Determinant = new Lazy<Double>(() => { return ... //TODO: Put actual implementation here }); public Double Determinant { get { return m_Determinant.Value; } } } Tenga en cuenta que Lazy<Double> m_Determinant tiene un estado cambiante
m_Determinant.IsValueCreatedque es, sin embargo, inobservable .
Voy a citar al autor de Clojure Rich Hickey aquí :
Si un árbol cae en el bosque, ¿hace ruido?
Si una función pura muta algunos datos locales para producir un valor de retorno inmutable, ¿está bien?
Es perfectamente razonable mutar objetos que exponen API que son inmutables al exterior por motivos de rendimiento. Lo importante del objeto inmutable es su inmutabilidad hacia el exterior. Todo lo que está encapsulado dentro de ellos es un juego limpio.
De alguna manera, en los lenguajes de recolección de basura como C #, todos los objetos tienen algún estado debido al GC. Como consumidor, eso no debería preocuparte normalmente.