immutability - objetos - porque los string son inmutables
¿Las variables de Elixir son realmente inmutables? (4)
Erlang y, obviamente, Elixir que se construye encima de él, abarca la inmutabilidad. Simplemente no permiten que cambien los valores en una determinada ubicación de memoria. Nunca hasta que la variable obtenga basura recolectada o esté fuera de alcance.
Las variables no son lo inmutable. Los datos que señalan son lo inmutable. Es por eso que cambiar una variable se conoce como volver a vincular.
Lo apuntas a otra cosa, no cambias a lo que apunta.
x = 1
seguido de
x = 2
no cambia los datos almacenados en la memoria de la computadora donde el 1 era un 2. Coloca un 2 en un nuevo lugar y señala
x
en él.
x
es accesible solo por un proceso a la vez, por lo que esto no tiene impacto en la concurrencia y la concurrencia es el lugar principal para preocuparse si algo es inmutable de todos modos.
Rebinding no cambia el estado de un objeto en absoluto, el valor todavía está en la misma ubicación de memoria, pero su etiqueta (variable) ahora apunta a otra ubicación de memoria, por lo que se preserva la inmutabilidad. Rebinding no está disponible en Erlang, pero mientras está en Elixir, esto no está frenando ninguna restricción impuesta por Erlang VM, gracias a su implementación. Josè Valim explica bien las razones detrás de esta elección en esta esencia .
Digamos que tienes una lista
l = [1, 2, 3]
y tenía otro proceso que consistía en tomar listas y luego realizar "cosas" en su contra repetidamente y cambiarlas durante este proceso sería malo. Puede enviar esa lista como
send(worker, {:dostuff, l})
Ahora, su próximo fragmento de código puede querer actualizar l con más valores para un trabajo posterior que no esté relacionado con lo que está haciendo ese otro proceso.
l = l ++ [4, 5, 6]
Oh no, ahora ese primer proceso tendrá un comportamiento indefinido porque cambiaste la lista, ¿verdad? Incorrecto.
Esa lista original permanece sin cambios. Lo que realmente hizo fue hacer una nueva lista basada en la anterior y volver a vincularla a esa nueva lista.
El proceso separado nunca tiene acceso a l. Los datos a los que apunté originalmente no cambian y el otro proceso (presumiblemente, a menos que lo ignore) tiene su propia referencia separada a esa lista original.
Lo que importa es que no puede compartir datos entre procesos y luego cambiarlos mientras otro proceso lo está mirando. En un lenguaje como Java donde tiene algunos tipos mutables (todos los tipos primitivos más referencias en sí mismas) sería posible compartir una estructura / objeto que contuviera decir un int y cambiar ese int de un hilo mientras otro lo estaba leyendo.
De hecho, es posible cambiar un tipo entero grande en Java parcialmente mientras lo lee otro hilo. O al menos, solía ser, no estoy seguro de si redujeron ese aspecto de las cosas con la transición de 64 bits. De todos modos, el punto es que puede sacar la alfombra de debajo de otros procesos / hilos cambiando los datos en un lugar que ambos estén mirando simultáneamente.
Eso no es posible en Erlang y, por extensión, en Elixir. Eso es lo que significa inmutabilidad aquí.
Para ser un poco más específico, en Erlang (el lenguaje original para VM Elixir se ejecuta) todo era variables inmutables de asignación única y Elixir está ocultando un patrón que los programadores de Erlang desarrollaron para solucionar esto.
En Erlang, si a = 3, eso era lo que iba a ser su valor durante la existencia de esa variable hasta que se saliera del alcance y se recogiera la basura.
Esto fue útil a veces (nada cambia después de la asignación o la coincidencia de patrones, por lo que es fácil razonar sobre lo que está haciendo una función), pero también un poco engorroso si estaba haciendo varias cosas a una variable o colección durante el curso al ejecutar una función.
El código a menudo se vería así:
A=input,
A1=do_something(A),
A2=do_something_else(A1),
A3=more_of_the_same(A2)
Esto fue un poco torpe e hizo que la refactorización fuera más difícil de lo necesario. Elixir está haciendo esto detrás de escena, pero ocultándolo del programador a través de macros y transformaciones de código realizadas por el compilador.
En el libro de Dave Thomas, Programming Elixir, dice "Elixir impone datos inmutables" y continúa diciendo:
En Elixir, una vez que una variable hace referencia a una lista como [1,2,3], sabe que siempre hará referencia a esos mismos valores (hasta que vuelva a vincular la variable).
Esto suena como "nunca cambiará a menos que lo cambies", así que estoy confundido acerca de cuál es la diferencia entre la mutabilidad y el reenlace. Un ejemplo que destaque las diferencias sería realmente útil.
La inmutabilidad significa que las estructuras de datos no cambian.
Por ejemplo, la función
HashSet.new
devuelve un conjunto vacío y siempre que mantenga la referencia a ese conjunto, nunca dejará de estar vacío.
Sin embargo, lo que
puede
hacer en Elixir es descartar una referencia variable a algo y volver a vincularla a una nueva referencia.
Por ejemplo:
s = HashSet.new
s = HashSet.put(s, :element)
s # => #HashSet<[:element]>
Lo que no puede suceder es que el valor bajo esa referencia cambie sin que lo vuelva a vincular explícitamente:
s = HashSet.new
ImpossibleModule.impossible_function(s)
s # => #HashSet<[:element]> will never be returned, instead you always get #HashSet<[]>
Compare esto con Ruby, donde puede hacer algo como lo siguiente:
s = Set.new
s.add(:element)
s # => #<Set: {:element}>
Las variables realmente son inmutables en sentido, cada nueva vinculación (asignación) solo es visible para el acceso que viene después de eso. Todos los accesos anteriores, todavía se refieren a los valores anteriores en el momento de su llamada.
foo = 1
call_1 = fn -> IO.puts(foo) end
foo = 2
call_2 = fn -> IO.puts(foo) end
foo = 3
foo = foo + 1
call_3 = fn -> IO.puts(foo) end
call_1.() #prints 1
call_2.() #prints 2
call_3.() #prints 4
No piense en "variables" en Elixir como variables en lenguajes imperativos, "espacios para valores". Más bien míralos como "etiquetas de valores".
Quizás lo entendería mejor cuando observe cómo funcionan las variables ("etiquetas") en Erlang. Cada vez que vincula una "etiqueta" a un valor, permanece unida a ella para siempre (las reglas de alcance se aplican aquí, por supuesto).
En Erlang no puedes escribir esto:
v = 1, % value "1" is now "labelled" "v"
% wherever you write "1", you can write "v" and vice versa
% the "label" and its value are interchangeable
v = v+1, % you can not change the label (rebind it)
v = v*10, % you can not change the label (rebind it)
en su lugar debes escribir esto:
v1 = 1, % value "1" is now labelled "v1"
v2 = v1+1, % value "2" is now labelled "v2"
v3 = v2*10, % value "20" is now labelled "v3"
Como puede ver, esto es muy inconveniente, principalmente para la refactorización de código. Si desea insertar una nueva línea después de la primera línea, deberá renumerar todas las v * o escribir algo como "v1a = ..."
Entonces, en Elixir puede volver a vincular variables (cambiar el significado de la "etiqueta"), principalmente para su conveniencia:
v = 1 # value "1" is now labelled "v"
v = v+1 # label "v" is changed: now "2" is labelled "v"
v = v*10 # value "20" is now labelled "v"
Resumen: en lenguajes imperativos, las variables son como maletas con nombre: tiene una maleta llamada "v". Al principio le pones un sándwich. De lo que pones una manzana (el sándwich se pierde y quizás el recolector de basura se lo coma). En Erlang y Elixir, la variable no es un lugar para colocar algo. Es solo un nombre / etiqueta para un valor. En Elixir puede cambiar el significado de la etiqueta. En Erlang no puedes. Esa es la razón por la que no tiene sentido "asignar memoria para una variable" en Erlang o Elixir, porque las variables no ocupan espacio. Los valores lo hacen. Ahora quizás veas la diferencia claramente.
Si quieres cavar más profundo:
1) Observe cómo funcionan las variables "no vinculadas" y "vinculadas" en Prolog. Esta es la fuente de este concepto Erlang, quizás un tanto extraño, de "variables que no varían".
2) Tenga en cuenta que "=" en Erlang realmente no es un operador de asignación, ¡es solo un operador de coincidencia! Al hacer coincidir una variable independiente con un valor, vincula la variable a ese valor. Hacer coincidir una variable enlazada es como igualar un valor al que está enlazado. Entonces esto generará un error de coincidencia :
v = 1,
v = 2, % in fact this is matching: 1 = 2
3) No es el caso en Elixir. Entonces, en Elixir debe haber una sintaxis especial para forzar la coincidencia:
v = 1
v = 2 # rebinding variable to 2
^v = 3 # matching: 2 = 3 -> error