Puedo ver los beneficios de los objetos mutables frente a los inmutables, como los objetos inmutables que eliminan muchos problemas difíciles de solucionar en la programación de subprocesos múltiples debido al estado compartido y de escritura. Por el contrario, los objetos mutables ayudan a tratar con la identidad del objeto en lugar de crear una copia nueva cada vez y, por lo tanto, también mejoran el rendimiento y el uso de la memoria, especialmente para objetos más grandes.
Una cosa que estoy tratando de entender es qué puede salir mal al tener objetos mutables en el contexto de la programación funcional. Como uno de los puntos que me dijeron es que el resultado de llamar a funciones en un orden diferente no es determinista.
Estoy buscando un ejemplo concreto real en el que sea muy evidente lo que puede salir mal usando un objeto mutable en la programación de funciones. Básicamente, si es malo, es malo independientemente de OO o paradigma de programación funcional, ¿verdad?
Creo que debajo de mi propia declaración responde esta pregunta. Pero aún así necesito algún ejemplo para poder sentirlo más naturalmente.
OO ayuda a gestionar la dependencia y a escribir programas más fáciles y fáciles de mantener con la ayuda de herramientas como encapsulación, polimorfismo, etc.
La programación funcional también tiene el mismo motivo de promover el código mantenible, pero mediante el uso de un estilo que elimina la necesidad de utilizar herramientas y técnicas de OO, una de las cuales creo que es minimizar los efectos secundarios, la función pura, etc.
Respuestas:
Creo que la importancia se demuestra mejor si se compara con un enfoque OO
por ejemplo, digamos que tenemos un objeto
En el paradigma OO, el método se adjunta a los datos, y tiene sentido que esos datos sean mutados por el método.
En el paradigma funcional definimos un resultado en términos de la función. un pedido comprado ES el resultado de la función de compra aplicada a un pedido. Esto implica algunas cosas de las que debemos estar seguros
¿Esperarías order.Status == "Comprado"?
También implica que nuestras funciones son idempotentes. es decir. ejecutarlos dos veces debería producir el mismo resultado cada vez.
Si la orden de compra fue modificada, la orden de compra2 fallará.
Al definir las cosas como resultados de funciones, nos permite usar esos resultados sin calcularlos realmente. Que en términos de programación es ejecución diferida.
Esto puede ser útil en sí mismo, pero una vez que no estamos seguros de cuándo sucederá realmente una función Y estamos bien al respecto, podemos aprovechar el procesamiento paralelo mucho más de lo que podemos en un paradigma OO.
Sabemos que ejecutar una función no afectará los resultados de otra función; para que podamos dejar que la computadora los ejecute en el orden que elija, utilizando tantos hilos como desee.
Si una función muta su entrada, debemos ser mucho más cuidadosos con esas cosas.
fuente
Order Purchase() { return new Order(Status = "Purchased") }
para que el estado sea un campo de solo lectura. ? Nuevamente, ¿por qué esta práctica es más relevante en el contexto del paradigma de programación de funciones? Los beneficios que mencionó también se pueden ver en la programación OO, ¿verdad?La clave para comprender por qué los objetos inmutables son beneficiosos no radica realmente en tratar de encontrar ejemplos concretos en el código funcional. Dado que la mayoría del código funcional está escrito usando lenguajes funcionales, y la mayoría de los lenguajes funcionales son inmutables por defecto, la naturaleza misma del paradigma está diseñada para evitar que suceda lo que está buscando.
La pregunta clave es, ¿cuál es el beneficio de la inmutabilidad? La respuesta es que evita la complejidad. Digamos que tenemos dos variables,
x
yy
. Ambos comienzan con el valor de1
.y
aunque se duplica cada 13 segundos. ¿Cuál será el valor de cada uno de ellos dentro de 20 días?x
será1
. Eso es fácil. Sin embargo, tomaría un esfuerzo resolverlo,y
ya que es mucho más complejo. ¿A qué hora del día en 20 días? ¿Tengo que tener en cuenta el horario de verano? La complejidad dey
versusx
es mucho más.Y esto también ocurre en código real. Cada vez que agrega un valor mutante a la mezcla, ese se convierte en otro valor complejo para que lo tenga en su cabeza y calcule en su cabeza, o en papel, cuando intente escribir, leer o depurar el código. Cuanta más complejidad, mayores serán las posibilidades de que cometas un error e introduzcas un error. El código es difícil de escribir; difícil de leer; difícil de depurar: el código es difícil de corregir.
Sin embargo, la mutabilidad no es mala . Un programa con cero mutabilidad no puede tener resultados, lo cual es bastante inútil. Incluso si la mutabilidad es escribir un resultado en la pantalla, el disco o lo que sea, debe estar allí. Lo que es malo es una complejidad innecesaria. Una de las formas más simples de reducir la complejidad es hacer que las cosas sean inmutables por defecto y solo hacerlas mutables cuando sea necesario, debido a razones de rendimiento o funcionales.
fuente
y
tiene que mutar; Eso es un requisito. A veces tenemos que tener un código complejo para cumplir con los requisitos complejos. El punto que estaba tratando de aclarar es que se debe evitar la complejidad innecesaria . Los valores de mutación son intrínsecamente más complejos que los fijos, por lo que, para evitar una complejidad innecesaria, solo mute los valores cuando sea necesario.Las mismas cosas que pueden salir mal en la programación no funcional: puede obtener efectos secundarios no deseados e inesperados , que es una causa bien conocida de errores desde la invención de los lenguajes de programación.
En mi humilde opinión, la única diferencia real en esto entre la programación funcional y no funcional es que, en el código no funcional, generalmente esperará efectos secundarios, en la programación funcional, no lo hará.
Claro, los efectos secundarios no deseados son una categoría de errores, independientemente del paradigma. Lo contrario también es cierto: los efectos secundarios utilizados deliberadamente pueden ayudar a lidiar con los problemas de rendimiento y, por lo general, son necesarios para la mayoría de los programas del mundo real cuando se trata de E / S y de sistemas externos, también independientemente del paradigma.
fuente
Acabo de responder una pregunta de StackOverflow que ilustra su pregunta bastante bien. El principal problema con las estructuras de datos mutables es que su identidad solo es válida en un instante exacto en el tiempo, por lo que las personas tienden a meter todo lo que pueden en el pequeño punto del código donde saben que la identidad es constante. En este ejemplo en particular, está registrando mucho dentro de un bucle for:
Cuando está acostumbrado a la inmutabilidad, no hay temor de que cambie la estructura de datos si espera demasiado, por lo que puede realizar tareas que están lógicamente separadas en su tiempo libre, de una manera mucho más desacoplada:
fuente
La ventaja de usar objetos inmutables es que si uno recibe una referencia a un objeto que tendrá cierta propiedad cuando el receptor lo examine, y necesita darle a otro código una referencia a un objeto con esa misma propiedad, simplemente puede pasar junto con la referencia al objeto sin tener en cuenta quién más podría haber recibido la referencia o qué podrían hacerle al objeto [ya que no hay nada que nadie más pueda hacerle al objeto], o cuándo el receptor podría examinar el objeto [ya que todo las propiedades serán las mismas independientemente de cuándo se examinen].
Por el contrario, el código que necesita dar a alguien una referencia a un objeto mutable que tendrá una cierta propiedad cuando el receptor lo examine (suponiendo que el receptor en sí no lo cambie) tampoco necesita saber que nada más que el receptor cambiará alguna vez esa propiedad, o bien saber cuándo el receptor accederá a esa propiedad, y saber que nada va a cambiar esa propiedad hasta la última vez que el receptor la examinará.
Creo que es más útil, para la programación en general (no solo la programación funcional) pensar que los objetos inmutables se dividen en tres categorías:
Los objetos que no pueden no permitirán que nada los cambie, incluso con una referencia. Tales objetos, y referencias a ellos, se comportan como valores y pueden compartirse libremente.
Objetos que permitirían ser cambiados por un código que tiene referencias a ellos, pero cuyas referencias nunca estarán expuestas a ningún código que realmente los cambie. Estos objetos encapsulan valores, pero solo se pueden compartir con código en el que se puede confiar para que no los cambie ni los exponga al código que podría hacerlo.
Objetos que serán cambiados. Estos objetos se ven mejor como contenedores , y las referencias a ellos como identificadores .
A menudo, un patrón útil es hacer que un objeto cree un contenedor, lo llene usando un código en el que se pueda confiar para que no mantenga una referencia después, y luego tenga las únicas referencias que existirán en cualquier parte del universo que estén en un código que nunca modifique objeto una vez que está poblado. Si bien el contenedor puede ser de tipo mutable, se puede razonar sobre (*) como si fuera inmutable, ya que de hecho nada lo mutará. Si todas las referencias al contenedor se mantienen en tipos de envoltorios inmutables que nunca alterarán su contenido, dichos envoltorios se pueden pasar de forma segura como si los datos dentro de ellos estuvieran en objetos inmutables, ya que las referencias a los envoltorios se pueden compartir y examinar libremente en en cualquier momento.
(*) En el código multiproceso, puede ser necesario utilizar "barreras de memoria" para garantizar que antes de que cualquier subproceso pueda ver alguna referencia al contenedor, los efectos de todas las acciones en el contenedor serían visibles para ese subproceso, pero ese es un caso especial mencionado aquí solo para completar.
fuente
Como ya se mencionó, el problema con el estado mutable es básicamente una subclase del problema más grande de los efectos secundarios , donde el tipo de retorno de una función no describe con precisión lo que realmente hace la función, porque en este caso, también indica mutación. Este problema ha sido abordado por algunos nuevos lenguajes de investigación, como F * ( http://www.fstar-lang.org/tutorial/ ). Este lenguaje crea un Sistema de efectos similar al sistema de tipos, donde una función no solo declara estáticamente su tipo, sino también sus efectos. De esta forma, los llamadores de la función son conscientes de que puede producirse una mutación de estado al llamar a la función, y ese efecto se propaga a sus llamadores.
fuente