Si vous pouvez utiliser def pour redéfinir les variables, comment est-ce considéré comme immuable?


10

Essayer d'apprendre Clojure et vous ne pouvez pas vous empêcher de savoir continuellement comment Clojure est tout au sujet des données immuables. Mais vous pouvez facilement redéfinir une variable en utilisant defnon? Je comprends que les développeurs de Clojure évitent cela, mais vous pouvez éviter de changer les variables dans n'importe quelle langue de la même manière. Quelqu'un peut-il m'expliquer en quoi cela est différent, parce que je pense que cela manque dans les tutoriels et les livres que je lis.

Pour donner un exemple, comment

a = 1
a = 2

en Ruby (ou blub, si vous préférez) différent de

(def a 1)
(def a 2)

à Clojure?

Réponses:


9

Comme vous l'avez déjà remarqué, le fait que la mutabilité soit découragée dans Clojure ne signifie pas qu'elle est interdite et qu'il n'y a pas de constructions qui la supportent. Vous avez donc raison de dire que defvotre utilisation peut modifier / muter une liaison dans l'environnement d'une manière similaire à ce que fait l'affectation dans d'autres langues (voir la documentation de Clojure sur les vars ). En modifiant les liaisons dans l'environnement global, vous modifiez également les objets de données qui utilisent ces liaisons. Par exemple:

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

Notez qu'après avoir redéfini la liaison pour x, la fonction fa également changé, car son corps utilise cette liaison.

Comparez cela avec les langages dans lesquels la redéfinition d'une variable ne supprime pas l'ancienne liaison mais la masque uniquement , c'est-à-dire qu'elle la rend invisible dans la portée qui suit la nouvelle définition. Voyez ce qui se passe si vous écrivez le même code dans le 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

Notez qu'après la deuxième définition de x, la fonction futilise toujours la liaison x = 1qui était dans la portée lorsqu'elle a été définie, c'est-à-dire que la liaison val x = 100n'écrase pas la liaison précédente val x = 1.

Conclusion: Clojure permet de muter l'environnement global et de redéfinir les liaisons en son sein. Il serait possible d'éviter cela, comme le font d'autres langages comme SML, mais la defconstruction de Clojure est destinée à accéder à un environnement global et à le muter. En pratique, cela est très similaire à ce que l'affectation peut faire dans des langages impératifs comme Java, C ++, Python.

Pourtant, Clojure fournit de nombreuses constructions et bibliothèques qui évitent la mutation, et vous pouvez faire du chemin sans l'utiliser du tout. Éviter la mutation est de loin le style de programmation préféré dans Clojure.


1
Éviter la mutation est de loin le style de programmation préféré. Je suggère que cette déclaration s'applique à toutes les langues de nos jours; pas seulement Clojure;)
David Arno

2

Clojure est une question de données immuables

Clojure consiste à gérer l' état mutable en contrôlant les points de mutation (c'est-à-dire Refs, Atoms, Agents et Vars). Bien sûr, tout code Java que vous utilisez via l'interopérabilité peut faire à sa guise.

Mais vous pouvez facilement redéfinir une variable en utilisant def right?

Si vous voulez lier un Var(par opposition à, par exemple, une variable locale) à une valeur différente, alors oui. En fait, comme indiqué dans Vars et le Global Environment , les Vars sont spécifiquement inclus comme l'un des quatre «types de référence» de Clojure (bien que je dirais qu'ils font principalement référence aux s dynamiques Var là-bas).

Avec Lisps, il y a une longue histoire de réalisation d'activités de programmation exploratoires interactives via le REPL. Cela implique souvent de définir de nouvelles variables et fonctions, ainsi que de redéfinir les anciennes. Cependant, en dehors du REPL, defun a Varest considéré comme une forme médiocre.


1

De Clojure pour les braves et les vrais

Par exemple, dans Ruby, vous pouvez effectuer plusieurs affectations à une variable pour augmenter sa valeur:

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

Vous pourriez être tenté de faire quelque chose de similaire dans 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!")))

Cependant, la modification de la valeur associée à un nom comme celui-ci peut rendre plus difficile la compréhension du comportement de votre programme car il est plus difficile de savoir quelle valeur est associée à un nom ou pourquoi cette valeur peut avoir changé. Clojure dispose d'un ensemble d'outils pour gérer le changement, que vous découvrirez au chapitre 10. En apprenant Clojure, vous constaterez que vous aurez rarement besoin de modifier une association nom / valeur. Voici une façon d'écrire le code précédent:

(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!"

Ne pourrait-on pas facilement faire la même chose en Ruby? La suggestion donnée est simplement de définir une fonction qui renvoie une valeur. Ruby a aussi des fonctions!
Evan Zamir

Oui je sais. Mais au lieu d'encourager une manière impérative de résoudre le problème proposé (comme changer les liaisons), Clojure adopte le paradigme fonctionnel.
Tiago Dall'Oca
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.