Pregunta

He leído un par de artículos sobre la inmutabilidad, pero aún no entiendo muy bien el concepto.

Hice un hilo aquí recientemente que mencionó la inmutabilidad, pero como este es un tema en sí mismo, estoy creando un hilo dedicado ahora.

En el hilo anterior mencioné que pensé que la inmutabilidad es el proceso de hacer que un objeto sea de solo lectura y le dé poca visibilidad. Otro miembro dijo que realmente no tenía nada que ver con eso. Esta página (parte de un series ) usa un ejemplo de una clase / estructura inmutable y usa readonly y otros conceptos para bloquearlo .

¿Cuál es exactamente la definición de estado en el caso de este ejemplo? El estado es un concepto que realmente no he entendido.

Desde la perspectiva de una guía de diseño, una clase inmutable debe ser una que no acepte la entrada del usuario y realmente, ¿simplemente devolvería valores?

Entiendo que cualquier objeto que simplemente devuelva información debería ser inmutable y " bloqueado " ;, ¿verdad? Entonces, si quiero devolver la hora actual en una clase dedicada con ese método, debería usar un tipo de referencia, ya que funcionará como una referencia del tipo y, por lo tanto, me beneficia la inmutabilidad.

¿Fue útil?

Solución

¿Qué es la inmutabilidad?

  • La inmutabilidad se aplica principalmente a objetos (cadenas, arrays, una clase Animal personalizada)
  • Normalmente, si hay una versión inmutable de una clase, también está disponible una versión mutable. Por ejemplo, Objective-C y Cocoa definen una clase NSString (inmutable) y una clase NSMutableString.
  • Si un objeto es inmutable, no se puede cambiar después de crearlo (básicamente de solo lectura). Podría considerarlo como " solo el constructor puede cambiar el objeto " ;.

Esto no tiene nada que ver directamente con la entrada del usuario; Ni siquiera su código puede cambiar el valor de un objeto inmutable. Sin embargo, siempre puedes crear un nuevo objeto inmutable para reemplazarlo. Aquí hay un ejemplo de pseudocódigo; tenga en cuenta que en muchos idiomas puede simplemente hacer myString = "hello " ;; en lugar de usar un constructor como lo hice a continuación, pero lo incluí para mayor claridad:

String myString = new ImmutableString("hello");
myString.appendString(" world"); // Can't do this
myString.setValue("hello world"); // Can't do this
myString = new ImmutableString("hello world"); // OK

Usted menciona " un objeto que simplemente devuelve información " ;; esto no lo convierte automáticamente en un buen candidato para la inmutabilidad. Los objetos inmutables tienden a devolver siempre el mismo valor con el que fueron construidos, por lo que me inclino a decir que la hora actual no sería ideal, ya que eso cambia a menudo. Sin embargo, puede tener una clase MomentOfTime que se crea con una marca de tiempo específica y siempre devuelve esa marca de hora en el futuro.

Beneficios de la Inmutabilidad

  • Si pasa un objeto a otra función / método, no debería preocuparse por si ese objeto tendrá el mismo valor después de que la función regrese. Por ejemplo:

    String myString = "HeLLo WoRLd";
    String lowercasedString = lowercase(myString);
    print myString + " was converted to " + lowercasedString;
    

    ¿Qué sucede si la implementación de lowcase () cambió myString porque estaba creando una versión en minúsculas? La tercera línea no te daría el resultado que querías. Por supuesto, una buena función en minúsculas () no haría esto, pero tiene garantizado este hecho si myString es inmutable. Como tales, los objetos inmutables pueden ayudar a imponer buenas prácticas de programación orientadas a objetos.

  • Es más fácil hacer que un objeto inmutable sea seguro para subprocesos

  • Potencialmente simplifica la implementación de la clase (bueno si eres el que está escribiendo la clase)

Estado

Si tuviera que tomar todas las variables de instancia de un objeto y anotar sus valores en papel, ese sería el estado de ese objeto en ese momento dado. El estado del programa es el estado de todos sus objetos en un momento dado. El estado cambia rápidamente con el tiempo; un programa necesita cambiar de estado para continuar ejecutándose.

Los objetos inmutables, sin embargo, tienen un estado fijo en el tiempo. Una vez creado, el estado de un objeto inmutable no cambia, aunque el estado del programa en su totalidad podría cambiar. Esto hace que sea más fácil hacer un seguimiento de lo que está pasando (y ver otros beneficios arriba).

Otros consejos

Inmutabilidad

En pocas palabras, la memoria es inmutable cuando no se modifica después de inicializarse.

Los programas escritos en lenguajes imperativos como C, Java y C # pueden manipular los datos en memoria a voluntad. Un área de memoria física, una vez reservada, puede ser modificada en su totalidad o en parte por un hilo de ejecución en cualquier momento durante la ejecución del programa. De hecho, los lenguajes imperativos fomentan esta forma de programación.

Escribir programas de esta manera ha sido increíblemente exitoso para aplicaciones de un solo hilo. Sin embargo, a medida que el desarrollo de aplicaciones modernas avanza hacia múltiples hilos de operación concurrentes dentro de un solo proceso, se introduce un mundo de problemas potenciales y complejidad.

Cuando solo hay un subproceso de ejecución, puedes imaginar que este único subproceso 'posee' todos los datos en la memoria y, por lo tanto, puede manipularlo a voluntad. Sin embargo, no hay un concepto implícito de propiedad cuando se involucran múltiples subprocesos en ejecución.

En cambio, esta carga recae sobre el programador, que debe esforzarse al máximo para garantizar que las estructuras en memoria estén en un estado coherente para todos los lectores. Las construcciones de bloqueo se deben utilizar con cuidado para evitar que un hilo vea datos mientras otro hilo lo actualiza. Sin esta coordinación, un subproceso inevitablemente consumiría datos que estaban a la mitad de la actualización. El resultado de tal situación es impredecible y, a menudo, catastrófico. Además, hacer que el bloqueo funcione correctamente en el código es muy difícil y, si se hace mal, puede dañar el rendimiento o, en el peor de los casos, bloquear los interbloqueos que impiden la ejecución de forma irreversible.

El uso de estructuras de datos inmutables alivia la necesidad de introducir bloqueos complejos en el código. Cuando se garantiza que una sección de la memoria no cambia durante la vida útil de un programa, varios lectores pueden acceder a la memoria simultáneamente. No es posible que observen esos datos en particular en un estado inconsistente.

Muchos lenguajes de programación funcionales, como Lisp, Haskell, Erlang, F # y Clojure, fomentan las estructuras de datos inmutables por su propia naturaleza. Es por esta razón que están disfrutando de un resurgimiento de interés a medida que avanzamos hacia el desarrollo de aplicaciones de subprocesos múltiples cada vez más complejas y las arquitecturas de computadora con muchas computadoras.

Estado

El estado de una aplicación se puede considerar simplemente como el contenido de todos los registros de memoria y CPU en un momento dado.

Lógicamente, el estado de un programa se puede dividir en dos:

  1. El estado del montón
  2. El estado de la pila de cada subproceso en ejecución

En entornos administrados como C # y Java, un hilo no puede acceder a la memoria de otro. Por lo tanto, cada hilo "posee" el estado de su pila. Se puede pensar que la pila contiene variables locales y parámetros de tipo de valor ( struct ), y las referencias a los objetos. Estos valores están aislados de hilos externos.

Sin embargo, los datos en el montón se pueden compartir entre todos los subprocesos, por lo tanto, se debe tener cuidado para controlar el acceso concurrente. Todas las instancias de objetos de tipo de referencia ( class ) se almacenan en el montón.

En POO, el estado de una instancia de una clase está determinado por sus campos. Estos campos se almacenan en el montón y, por lo tanto, son accesibles desde todos los subprocesos. Si una clase define métodos que permiten que los campos se modifiquen después de que el constructor se complete, entonces la clase es mutable (no inmutable). Si los campos no se pueden cambiar de ninguna manera, entonces el tipo es inmutable. Es importante tener en cuenta que una clase con todos los campos C # readonly / Java final no es necesariamente inmutable. Estas construcciones aseguran que la referencia no pueda cambiar, pero no el objeto al que se hace referencia. Por ejemplo, un campo puede tener una referencia incambiable a una lista de objetos, pero el contenido real de la lista puede modificarse en cualquier momento.

Al definir un tipo como realmente inmutable, su estado se puede considerar congelado y, por lo tanto, el tipo es seguro para el acceso de varios subprocesos.

En la práctica, puede ser un inconveniente definir todos sus tipos como inmutables. Para modificar un valor en un tipo inmutable puede implicar un poco de copia de memoria. Algunos idiomas hacen este proceso más fácil que otros, pero de cualquier manera la CPU terminará haciendo un trabajo extra. Muchos factores contribuyen a determinar si el tiempo dedicado a copiar la memoria supera el impacto de las contenciones de bloqueo.

Se han realizado muchas investigaciones sobre el desarrollo de estructuras de datos inmutables, como listas y árboles. Cuando use dichas estructuras, diga una lista, la operación 'agregar' devolverá una referencia a una nueva lista con el nuevo elemento agregado. Las referencias a la lista anterior no ven ningún cambio y aún tienen una vista consistente de los datos.

Para simplificarlo: una vez que creas un objeto inmutable, no hay forma de cambiar el contenido de ese objeto. Ejemplos de objetos inmutables .Net son String y Uri.

Cuando modifica una cadena, simplemente obtiene una nueva cadena. La cadena original no cambiará. Un Uri solo tiene propiedades de solo lectura y no hay métodos disponibles para cambiar el contenido del Uri.

Los casos en que los objetos inmutables son importantes son diversos y, en la mayoría de los casos, tienen que ver con la seguridad. El Uri es un buen ejemplo aquí. (Por ejemplo, no desea que un código no confiable cambie un Uri). Esto significa que puede pasar una referencia a un objeto inmutable sin tener que preocuparse de que el contenido cambie.

Espero que esto ayude.

Las cosas que son inmutables nunca cambian. Las cosas mutables pueden cambiar. Las cosas mutables mutan. Las cosas inmutables parecen cambiar pero en realidad crean una nueva cosa mutable.

Por ejemplo, aquí hay un mapa en Clojure

(def imap {1 "1" 2 "2"})
(conj imap [3 "3"])
(println imap)

La primera línea crea un nuevo mapa de Clojure inmutable. La segunda línea une 3 y " 3 " al mapa. Esto puede aparecer como si estuviera modificando el mapa antiguo, pero en realidad está devolviendo un mapa nuevo con 3 " 3 " adicional. Este es un excelente ejemplo de inmutabilidad. Si este hubiera sido un mapa mutable, simplemente habría agregado 3 " 3 " directamente a el mismo mapa antiguo. La tercera línea imprime el mapa

{3 "3", 1 "1", 2 "2"}

La inmutabilidad ayuda a mantener el código limpio y seguro. Esta y otras razones es la razón por la cual los lenguajes de programación funcionales tienden a inclinarse hacia la inmutabilidad y la falta de estado.

Buena pregunta.

Multihilo. Si todos los tipos son inmutables, las condiciones de la carrera no existen y estás seguro de lanzar tantos hilos en el código como desees.

Obviamente, no se puede lograr mucho sin la mutabilidad. Guarde cálculos complejos, por lo que generalmente necesita cierta mutabilidad para crear un software empresarial funcional. Sin embargo, vale la pena reconocer dónde debe estar la inmutabilidad, como algo transaccional.

Busque la programación funcional y el concepto de pureza para obtener más información sobre la filosofía. Cuanto más almacene en la pila de llamadas (los parámetros que está pasando a los métodos) en lugar de tenerlos disponibles a través de referencias, como colecciones u objetos estáticamente disponibles, más puro será su programa y menos propensos a ser las condiciones de carrera. Con más núcleos múltiples en estos días, este tema es más importante.

También la inmutabilidad reduce la cantidad de posibilidades en el programa, lo que reduce la complejidad potencial y el potencial de errores.

Un objeto inmutable es algo que usted puede asumir con seguridad que no va a cambiar; tiene la propiedad importante de que todos los que la usan pueden asumir que están viendo el mismo valor.

La inmutabilidad generalmente también significa que puede pensar que el objeto es un "valor", y que no hay una diferencia efectiva entre copias idénticas del objeto y el objeto en sí.

Déjame añadir una cosa más. Además de todo lo que se mencionó anteriormente, también desea inmutabilidad para:

Hacer las cosas inmutables evita una gran cantidad de errores comunes.

Por ejemplo, un estudiante nunca debe cambiar su número de estudiante. Si no proporciona una forma de establecer la variable (y la hace constante o final, o lo que sea que admita su idioma), puede aplicarla en el momento de la compilación.

Si las cosas son mutables y no desea que cambien cuando las pase, debe hacer una copia defensiva que usted pase. Luego, si el método / función al que llama cambia la copia del artículo, el original no se ha tocado.

Hacer las cosas inmutables significa que no tienes que recordar (o tomarte el tiempo / la memoria) para hacer copias defensivas.

Si realmente trabajas en ello y piensas en cada variable que creas, encontrarás que la gran mayoría (normalmente tengo un 90-95%) de tus variables nunca cambian una vez que se les asigna un valor. Hacer esto hace que los programas sean más fáciles de seguir y reduce la cantidad de errores.

Para responder a su pregunta sobre el estado, el estado son los valores que las variables de un objeto " " (sea que una clase o una estructura) tenga. Si tomaste una persona " objeto " el estado sería el color de los ojos, el color del cabello, la longitud del cabello, etc. Algunos de ellos (por ejemplo, el color de los ojos) no cambian, mientras que otros, como la longitud del cabello, sí cambian.

" ... ¿por qué debería preocuparme? "

Un ejemplo práctico es la concatenación repetitiva de cadenas. En .NET por ejemplo:

string SlowStringAppend(string [] files)
{
    // Declare an string
    string result="";

    for (int i=0;i<files.length;i++)
    {
        // result is a completely new string equal to itself plus the content of the new
        // file
        result = result + File.ReadAllText(files[i]);
    }

    return result;
}    

string EfficientStringAppend(string [] files)
{
    // Stringbuilder manages a internal data buffer that will only be expanded when absolutely necessary
    StringBuilder result=new SringBuilder();

    for (int i=0;i<files.length;i++)
    {
        // The pre-allocated buffer (result) is appended to with the new string 
        // and only expands when necessary.  It doubles in size each expansion
        // so need for allocations become less common as it grows in size. 
        result.Append(File.ReadAllText(files[i]));
    }

    return result.ToString();
}

Lamentablemente, el uso del primer enfoque de función (lento) todavía se usa comúnmente. Una comprensión de la inmutabilidad deja muy claro por qué es tan importante utilizar StringBuilder.

No puede cambiar un objeto inmutable, por lo tanto, debe reemplazarlo ... " para cambiarlo " ;. es decir, reemplazar y luego desechar. " Sustituyendo " en este sentido, significa cambiar el puntero de una ubicación de memoria (del valor anterior) a otra (para el nuevo valor).

Tenga en cuenta que al hacerlo ahora estamos usando memoria adicional. Algunos para el valor anterior, algunos para el nuevo valor. También tenga en cuenta que algunas personas se confunden porque miran el código, como:

string mystring = "inital value";
mystring = "new value";
System.Console.WriteLine(mystring); // Outputs "new value";

y piensen por sí mismos, "pero lo estoy cambiando, ¡mire allí, en blanco y negro! mystring genera un 'nuevo valor' ...... ¡¡Pensé que habías dicho que no podía cambiarlo? !! "

Pero en realidad bajo el capó, lo que sucede es esta asignación de nueva memoria, es decir, mystring ahora apunta a una dirección y espacio de memoria diferente. " Inmutable " en este sentido, no se refiere al valor de mystring, sino a la memoria utilizada por la variable mystring para almacenar su valor.

En ciertos idiomas, la memoria que almacena el valor anterior debe limpiarse manualmente, es decir, el programador debe liberarlo explícitamente ... y recuerde hacerlo. En otros idiomas, esta es una característica automática del lenguaje, es decir, la recolección de basura en .Net.

Uno de los lugares en los que esto realmente explota es el uso de memoria en bucles altamente iterativos, especialmente con cadenas como en la publicación de Ashs. Supongamos que estaba creando una página HTML en un bucle iterativo, en el que constantemente agregaba el siguiente bloque HTML al último y, solo por diversión, estaba haciendo esto en un servidor de alto volumen. Esta asignación constante de " memoria de nuevos valores " puede ser costoso y, en última instancia, fatal, si la "memoria de valor antiguo" no se limpia correctamente.

Otro problema es que algunas personas asumen que cosas como la recolección de basura (GC) suceden inmediatamente. Pero no lo hace. Hay varias optimizaciones que se producen de tal manera que la recolección de basura está configurada para que ocurra durante los períodos más inactivos. Por lo tanto, puede haber un retraso significativo entre cuando la memoria se marca como descartada y cuando el recolector de basura la libera ... así puede sufrir grandes picos en el uso de la memoria si simplemente difiere el problema al GC.

Si el GC no tiene la oportunidad de operar antes de quedarse sin memoria, entonces las cosas no se caerán necesariamente como en otros idiomas que no tienen recolección automática de basura. En cambio, el GC se activará como el proceso de mayor prioridad para liberar la memoria descartada, sin importar qué tan malo sea el tiempo, y se convertirá en un proceso de bloqueo mientras limpia las cosas. Obviamente, esto no es genial.

Básicamente, debe codificar con estas cosas en mente y consultar la documentación de los idiomas que está utilizando para las mejores prácticas / patrones que le permiten evitar / mitigar este riesgo.

Al igual que en la publicación de Ashs, en .Net y con cadenas, la práctica recomendada es usar la clase StringBuilder mutable, en lugar de las clases de cadena inmutables cuando se trata de la necesidad de cambiar constantemente el valor de una cadena.

Otros idiomas / tipos también tendrán sus propias soluciones.

¿Por qué la inmutabilidad?

  1. Son menos propensos a errores y son más seguros.

  2. Las clases inmutables son más fáciles de diseñar, implementar y usar que las clases mutables.

  3. Los objetos inmutables son seguros para subprocesos, por lo que no hay problemas de sincronización.

  4. Los objetos inmutables son buenas Teclas de mapa y Elementos de conjunto, ya que normalmente no se cambian una vez creados.

  5. La inmutabilidad hace que sea más fácil escribir, usar y razonar sobre el código (la clase invariante se establece una vez y luego no se modifica).

  6. La inmutabilidad facilita la paralelización del programa, ya que no hay conflictos entre objetos.

  7. El estado interno del programa será consistente incluso si tiene excepciones.

  8. Las referencias a objetos inmutables se pueden almacenar en caché ya que no van a cambiarse (es decir, en Hashing proporciona operaciones rápidas).

Vea mi blog para una respuesta más detallada:
http://javaexplorer03.blogspot.in/2015/07/minimize-mutability. html

Mira, no he leído los enlaces que has publicado.

Sin embargo, aquí está mi entendimiento.
Cada programa tiene cierto conocimiento de sus datos (Estado), que pueden cambiar por la entrada del usuario / cambios externos, etc.

Las variables (valores que cambian) se mantienen para mantener el estado. Inmutable significa algunos datos que no cambian. Se puede decir que es lo mismo que solo lectura o constante de alguna manera (se puede ver de esa manera).

AFAIK, la programación funcional tiene cosas inmutables (es decir, no puede usar la asignación a una variable que tiene el valor. Lo que puede hacer es crear otra variable que pueda contener el valor original + los cambios).

.net tiene una clase de cadena, que es un ejemplo.
es decir, no puede modificar la cadena en su lugar

cadena s = " hola " ;; Puedo escribir s.Replace (" el " ;, " a "); Pero esto no modificará el contenido de la variable s.

Lo que puedo hacer es s = s.Replace (" el ", " a ");
Esto creará una nueva variable & amp; asigne su valor a s (sobrescribiendo el contenido de s).

Los expertos pueden corregir errores si tengo, en mi opinión.

EDITAR: Inmutable = No asignable una vez que tiene algún valor & amp; no se puede reemplazar en su lugar (¿quizás?)

Un ejemplo de los beneficios potenciales de rendimiento que ofrecen los objetos inmutables está disponible en la API de WPF. Una clase base común de muchos tipos de WPF es Freezable .

Varios ejemplos de WPF sugieren que congelar objetos (haciéndolos inmutables en tiempo de ejecución) puede mejorar significativamente el rendimiento de la aplicación, ya que no es necesario bloquear ni copiar.

Personalmente deseo que el concepto de inmutabilidad sea más fácil de expresar en el lenguaje que uso con más frecuencia, C #. Hay un modificador readonly disponible para los campos. Me gustaría ver también un modificador readonly en los tipos que solo se permitiría para los tipos que solo tienen campos de solo lectura que son de tipo readonly. Esencialmente, esto significa que todo el estado debería inyectarse en el momento de la construcción, y que se congelaría todo el gráfico del objeto. Me imagino que si estos metadatos fueran intrínsecos al CLR, podrían usarse fácilmente para optimizar el análisis de basura para GC.

Lo siento, ¿por qué la inmutabilidad previene las condiciones de la carrera (en este ejemplo, escribir después de leer los peligros)?

shared v = Integer(3)
v = Integer(v.value() + 1) # in parallel

La inmutabilidad tiene que ver con valores, y los valores con hechos. Algo tiene valor si no se puede cambiar, porque si algo se puede cambiar, significa que no se puede conectar ningún valor específico a él. El objeto se inicializó con el estado A y durante la ejecución del programa se mutó al estado B y al estado C. Esto significa que el objeto no representa un valor específico único, sino que es solo un contenedor, una abstracción en un lugar de la memoria, nada más. No puede confiar en dicho contenedor, no puede creer que este contenedor tenga el valor que debe tener.

Vayamos al ejemplo: imaginemos que en el código se crea una instancia de la clase Book.

Book bookPotter =  new Book();
bookPotter.setAuthor('J.K Rowling');
bookPotter.setTitle('Harry Potter');

Esta instancia tiene algunos campos establecidos como autor y título. Todo está bien, pero en alguna parte del código se utilizan de nuevo los configuradores.

Book bookLor =  bookPotter; // only reference pass
bookLor.setAuthor('J.R.R Tolkien');
bookLor.setTitle('Lords of The Rings');

No se deje engañar por un nombre de variable diferente, en realidad es la misma instancia. El código está utilizando setters en la misma instancia otra vez. Significa que bookPotter nunca fue realmente el libro de Harry Potter, bookPotter solo es un indicador del lugar donde se encuentra un libro desconocido. Dicho esto, parece que es más un estante que el libro. ¿Qué confianza puedes tener para tal objeto? ¿Es el libro de Harry Potter o el libro LoR o ninguno?

La instancia mutable de una clase es solo un puntero a un estado desconocido con las características de la clase.

¿Cómo entonces evitar la mutación? Es bastante fácil en las reglas:

  • construir el objeto con el estado deseado a través de un constructor o constructor
  • no cree definidores para el estado encapsulado del objeto
  • no cambie ningún estado encapsulado del objeto en ninguno de sus métodos

Estas pocas reglas permitirán tener objetos más predecibles y más confiables. De vuelta a nuestro ejemplo y libro siguiendo las reglas anteriores:

Book bookPotter =  new Book('J.K Rowling', 'Harry Potter');
Book bookLor = new Book('J.R.R Tolkien', 'Lord of The Rings');

Todo se configura durante la fase de construcción, en este caso el constructor, pero para estructuras más grandes puede ser un constructor. Ningún setter existe en los objetos, el libro no puede mutar a uno diferente. En tal caso, bookPotter representa el valor del libro de Harry Potter y puede estar seguro de que esto no se puede cambiar.

Si está interesado en un alcance más amplio de la inmutabilidad, en este medio, el artículo trata más sobre el tema en relación con JavaScript - https://medium.com/@macsikora/the-state-of-immutability-169d2cd11310 .

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top