La inmutabilidad es primordial en la mayoría de los dominios de FP, pero ¿hacen copias superficiales o profundas?

La inmutabilidad es primordial en la mayoría de los dominios de FP, pero ¿hacen copias superficiales o profundas?

Parece que clonar todos los elementos de una matriz podría ser prohibitivamente costoso, por ejemplo, para la mayoría de las aplicaciones.

En primer lugar, cada idioma puede manejar las cosas de manera diferente. Por lo tanto, me centraré en dos idiomas con los que estoy muy familiarizado: Erlang y Elixir

Estos idiomas actúan de manera diferente según lo que hagas. Hay dos casos: hacemos llamadas con el mismo actor y manejamos datos en diferentes actores.

Primero necesita saber que los actores son procesos aislados. Son completamente independientes. Esto incluye la pila de memoria. Cada actor tiene su propio stack. Entonces, cuando los datos se manejan dentro de un actor separado, estos datos se envían como un mensaje y se asignan nuevos en la pila de otros actores. Esto tiene sentido ya que ambos idiomas están diseñados para ejecutarse en clústeres que conectan varias máquinas físicas.

Cuando se manejan datos dentro del mismo actor, no tiene sentido copiar datos en profundidad. Por lo general, usted entrega una referencia a ese conjunto de datos (ya que no puede mutarlo, no es un problema tener varias funciones trabajando en el mismo conjunto de datos). También cuando, por ejemplo, extiende una lista, no copia ninguna referencia. Supongamos que tiene una lista como [2, 3, 4] y desea agregar 1 como el nuevo encabezado.

(en codigo:

x = [1, 2, 3]
y = [1 | x]

)

Aquí no se cambia en absoluto la referencia de x. Una lista es la estructura de datos principal de la mayoría de los lenguajes funcionales. Todo lo que hace es agregar un nuevo elemento de lista 1 que contiene los datos y la referencia al siguiente elemento. El siguiente elemento es el elemento principal de x. Eso es. Entonces no tocas x. Todo lo que haces es lo que haces de todos modos dentro de una lista. Conecta la nueva cabeza al resto.

Supongo que esto es más o menos lo mismo en muchos lenguajes funcionales. Por lo que sé, Clojure funciona de la misma manera. Se entregan referencias que son comparativamente baratas.

La inmutabilidad básicamente significa no poder modificar un objeto existente. Cuando tu dices

val x = 100

significa que x no se puede reasignar.

Tomando el ejemplo de Scala (que es un lenguaje fabuloso siguiendo un enfoque híbrido FP + OOP)

val myArr = Matriz (“A”, “B”, “C”)

aquí, myArr ya no se puede modificar. No puede agregar / eliminar elementos en él. No puede reemplazar un artículo en un índice.

La inmutabilidad aquí es impuesta por el lenguaje. Dado que esta matriz no se permitirá modificar de tal manera, ¿cuál es el punto de la clonación? Al pasar la matriz de aquí para allá, es la misma matriz y los mismos objetos dentro de ir.

Sin embargo, es una historia diferente con el objeto que contiene una matriz. Una instancia de una clase puede tener propiedades / funciones a las que se puede acceder para mutarla. Esto no cambia su referencia. Pero la matriz no está preocupada, no aplica esto. Por lo tanto, una matriz inmutable de objetos aún pasará alrededor de las referencias de instancia. Es deber de la instancia gestionar la mutación.

Punto de clase (var x: Int,
var y: Int) {
// Tenemos una clase simple con 2 variables mutables
}

val immPoint = nuevo punto (10,20) // immPoint es inmutable
immPoint.x = 30 // No es un problema
immPoint = nuevo punto (30, 50) // ¡Error!

val arrObjs = Array (nuevo punto (100,200))
arrObjs (0) .x = 500 // Todo genial
arrObjs (0) = nuevo punto (30, 40) // ¡Error!

Como puede ver, el objeto en sí tiene que definir qué significa la inmutabilidad para él. Si es inmutable, no podemos modificarlo de la manera que queramos y tendremos que recurrir a la creación de un objeto completamente nuevo O clonarlo.

Ahora, aquí está la frase clave, ya que la inmutabilidad dice que un objeto perticular no puede modificarse, ¿por qué el lenguaje necesita clonarlo? Muy bien puede pasar la referencia. Eso es exactamente lo que hace scala y muchos otros idiomas hacen lo mismo. Actaually, la inmutabilidad permite que el lenguaje sea más eficiente, manteniendo menos (o solo una) copias consigo mismo.

Para estar seguros, en el caso anterior, podemos requerir una copia profunda de alguna manera y Scala sí proporciona una. Pero esa es una acción explícita que el usuario toma por cualquier razón, Scala sigue las garantías de inmutabilidad de cada objeto.

En cualquier colección de objetos inmutables, no hay diferencia entre una copia superficial y una copia profunda. Es posible declarar algunos objetos como mutables, pero, por supuesto, si coloca objetos mutables en colecciones, debe preocuparse por la copia. Lo mejor es usar colecciones mutables de objetos inmutables, de esa manera obtienes la eficiencia de la mutabilidad o las cosas que cambian con frecuencia, pero nunca tienes que hacer una copia profunda de nada.

La mayoría de los lenguajes FP utilizan listas y árboles enlazados individualmente en lugar de matrices y tablas hash, porque aprovechan la inmutabilidad para compartir estructuras y hacen que la actualización incremental sea más fácil.

En general, no necesita copiar valores inmutables, porque siempre es seguro compartirlos. Si quiere decir modificación: agregar elementos a una lista inmutable o árbol de búsqueda binario, por ejemplo, tome solo las operaciones [math] O (1) [/ math] y [math] O (\ log n) [/ math] respectivamente, porque de compartir estructural. Es decir, estos son eficientes.

Conceptualmente, en lenguajes FP como Haskell, se crea una copia independiente completa para cada tarea. Estas son copias profundas.

Sin embargo, la implementación en realidad usa copia en escritura y estructuras de datos funcionales para evitar copias.

Toda la carga de la eficiencia recae sobre el compilador o el intérprete.

Es por eso que nunca pueden ser tan eficientes como un lenguaje con mutación. Mantener la pretensión de que nada cambia es una operación que necesita recursos.