Si puede usar def para redefinir variables, ¿cómo se considera inmutable?

10

Intentando aprender Clojure y no puedes evitar que te digan continuamente cómo Clojure se trata de datos inmutables. Pero puede redefinir fácilmente una variable utilizando def¿verdad? Entiendo que los desarrolladores de Clojure eviten esto, pero podría evitar cambiar las variables en cualquier idioma de la misma manera. ¿Puede alguien explicarme cómo esto es diferente, porque creo que me estoy perdiendo eso de los tutoriales y libros que estoy leyendo?

Para dar un ejemplo de cómo es

a = 1
a = 2

en Ruby (o blub, si lo prefiere) diferente de

(def a 1)
(def a 2)

en Clojure?

Evan Zamir
fuente

Respuestas:

9

Como ya notó, el hecho de que se desaconseja la mutabilidad en Clojure no significa que esté prohibido y que no haya construcciones que lo respalden. Entonces tiene razón en que al usarlo defpuede cambiar / mutar un enlace en el entorno de forma similar a lo que hace la asignación en otros idiomas (consulte la documentación de Clojure en vars ). Al cambiar los enlaces en el entorno global, también cambia los objetos de datos que usan estos enlaces. Por ejemplo:

user=> (def x 1)
#'user/x
user=> (defn f [y] (+ x y))
#'user/f
user=> (f 1)
2
user=> (def x 100)
#'user/x
user=> (f 1)
101

Tenga en cuenta que después de redefinir el enlace para x, la función también fha cambiado, porque su cuerpo usa ese enlace.

Compare esto con los idiomas en los que la redefinición de una variable no elimina el enlace anterior, sino que solo lo sombrea , es decir, lo hace invisible en el alcance que viene después de la nueva definición. Vea lo que sucede si escribe el mismo código en SML REPL:

- val x = 1;
val x = 1 : int
- fun f y = x + y;
val f = fn : int -> int
- f 1;
val it = 2 : int
- val x = 100;
val x = 100 : int
- f 1;
val it = 2 : int

Tenga en cuenta que después de la segunda definición de x, la función ftodavía utiliza el enlace x = 1que estaba en el alcance cuando se definió, es decir, el enlace val x = 100no sobrescribe el enlace anterior val x = 1.

En pocas palabras: Clojure permite mutar el entorno global y redefinir los enlaces en él. Sería posible evitar esto, como lo hacen otros lenguajes como SML, pero la defconstrucción en Clojure está destinada a acceder y mutar un entorno global. En la práctica, esto es muy similar a lo que la asignación puede hacer en lenguajes imperativos como Java, C ++, Python.

Aún así, Clojure proporciona muchas construcciones y bibliotecas que evitan la mutación, y puede recorrer un largo camino sin usarlo en absoluto. Evitar la mutación es, con mucho, el estilo de programación preferido en Clojure.

Giorgio
fuente
1
Evitar la mutación es, con mucho, el estilo de programación preferido. Sugeriría que esta declaración se aplique a todos los idiomas en estos días; no solo Clojure;)
David Arno
2

Clojure se trata de datos inmutables

Clojure se trata de administrar el estado mutable controlando los puntos de mutación (es decir, Refs, Atoms, Agentsy Vars). Aunque, por supuesto, cualquier código Java que use a través de interoperabilidad puede hacer lo que le plazca.

Pero puedes redefinir fácilmente una variable usando def ¿verdad?

Si quiere decir unir a Var(en lugar de, por ejemplo, una variable local) a un valor diferente, entonces sí. De hecho, como se señaló en Vars y el entorno global , los Vars se incluyen específicamente como uno de los cuatro "tipos de referencia" de Clojure (aunque diría que se refieren principalmente a los dinámicos Var allí).

Con Lisps, existe una larga historia de realización de actividades de programación interactivas y exploratorias a través de REPL. Esto a menudo implica definir nuevas variables y funciones, así como redefinir las antiguas. Sin embargo, fuera de la REPL, la lectura defde a Varse considera deficiente.

Nathan Davis
fuente
1

De Clojure para los valientes y verdaderos

Por ejemplo, en Ruby puede realizar múltiples asignaciones a una variable para aumentar su valor:

severity = :mild
  error_message = "OH GOD! IT'S A DISASTER! WE'RE "
  if severity == :mild
    error_message = error_message + "MILDLY INCONVENIENCED!"
  else
    error_message = error_message + "DOOOOOOOMED!"
  end

Puede sentirse tentado a hacer algo similar en Clojure:

(def severity :mild)
  (def error-message "OH GOD! IT'S A DISASTER! WE'RE ")
  (if (= severity :mild)
      (def error-message (str error-message "MILDLY INCONVENIENCED!"))
  (def error-message (str error-message "DOOOOOOOMED!")))

Sin embargo, cambiar el valor asociado con un nombre como este puede dificultar la comprensión del comportamiento de su programa porque es más difícil saber qué valor está asociado con un nombre o por qué ese valor podría haber cambiado. Clojure tiene un conjunto de herramientas para lidiar con el cambio, que aprenderá en el Capítulo 10. A medida que aprenda Clojure, encontrará que rara vez necesitará alterar una asociación de nombre / valor. Aquí hay una manera de escribir el código anterior:

(defn error-message [severity]
   (str "OH GOD! IT'S A DISASTER! WE'RE "
   (if (= severity :mild)
     "MILDLY INCONVENIENCED!"
     "DOOOOOOOMED!")))

(error-message :mild)
  ; => "OH GOD! IT'S A DISASTER! WE'RE MILDLY INCONVENIENCED!"
Tiago Dall'Oca
fuente
¿No podría uno hacer lo mismo fácilmente en Ruby? La sugerencia dada es simplemente definir una función que devuelve un valor. ¡Ruby también tiene funciones!
Evan Zamir
Sí, lo sé. Pero en lugar de fomentar una forma imperativa de resolver el problema propuesto (como cambiar enlaces), Clojure adopta el paradigma funcional.
Tiago Dall'Oca