Variables et l'environnement global
Clojure est un langage pratique reconnaissant le besoin ponctuel de conserver une référence persistante à une valeur changeante et propose quatre mécanismes distincts pour ce faire de manière contrôlée : les Vars, les Refs, les Agents et les Atoms. Les Vars permettent de référencer un emplacement d'entreposage mutable pouvant être dynamiquement réattribué (à un nouvel emplacement d'entreposage) pour chaque processus léger. Chaque Var peut (mais n'y est pas obligée) avoir une liaison racine, c'est-à-dire une liaison partagée par tous les threads qui ne possèdent pas de liaison spécifique. Ainsi, la valeur d'une Var est la valeur de sa liaison spécifique, ou, si elle n'est pas liée dans le processus léger demandant la valeur, la valeur de la liaison racine, le cas échéant.
La forme spéciale `def` crée (et insère) une Var. Si la Var n'existait pas déjà et qu'aucune valeur initiale n'est fournie, la Var n'est pas liée.
- user=> (def x)
- #'user/x
- user=> x
- #object[clojure.lang.Var$Unbound 0x14008db3 "Unbound: #'user/x"]
Fournir une valeur initiale lie la racine (même si elle était déjà liée).
- user=> (def x 1)
- #'user/x
-
- user=> x
- 1
Par défaut, les variables sont statiques, mais elles peuvent être déclarées dynamiques pour permettre des liaisons par processus léger via la liaison de macro. Au sein de chaque processus léger, elles obéissent à une discipline de pile :
- user=> (def ^:dynamic x 1)
- user=> (def ^:dynamic y 1)
- user=> (+ x y)
- 2
-
- user=> (binding [x 2 y 3]
- (+ x y))
- 5
-
- user=> (+ x y)
- 2
Les liaisons créées avec `binding` ne sont visibles par aucun autre processus léger. De même, les liaisons créées avec `binding` peuvent être assignées à `vars`, ce qui permet à un contexte imbriqué de communiquer avec le code avant son insertion dans la pile d'appels. Cette fonctionnalité est optionnelle et s'active en définissant la balise de métadonnées `^:dynamic` sur `true`, comme dans l'exemple de code ci-dessus. Il existe des cas où l'on peut souhaiter redéfinir des variables statiques (`vars`) au sein d'un contexte. Clojure (depuis la version 1.3) fournit les fonctions `with-redefs` et `with-redefs-fn` à cet effet.
Les fonctions définies avec `defn` sont stockées dans des variables (`vars`), ce qui permet de les redéfinir dans un programme en cours d'exécution. Ceci ouvre également de nombreuses possibilités en matière de programmation orientée aspect ou contexte. Par exemple, il est possible d'encapsuler une fonction dans un comportement de journalisation uniquement pour certains contextes d'appel ou processus léger.
Transfert de liaisons
Certaines fonctions de concurrence Clojure (futures, agents) offrent un «transfert de liaisons», permettant de transférer l'ensemble actuel de liaisons dynamiques à un autre processus léger afin de poursuivre l'exécution de manière asynchrone dans le même environnement. Cette fonctionnalité est fournie par les fonctions `future`, `send`, `send-off` et `pmap` :
- (def ^:dynamic *num* 1)
- (binding [*num* 2] (future (println *num*)))
- ;; affiche "2", pas "1"
(set! var-symbol expr)
Forme spéciale d'affectation.
Lorsque le premier opérande est un symbole, il doit être résolu en une variable globale. La valeur de la liaison du processus léger courant de la variable est définie sur la valeur de `expr`. Actuellement, toute tentative de modification de la liaison racine d'une variable à l'aide de `set!` génère une erreur; autrement dit, les affectations de variables sont locales au processus léger. Dans tous les cas, la valeur de `expr` est retournée.
Remarque : il est impossible d'affecter des valeurs aux paramètres de fonction ou aux liaisons locales. Seuls les champs Java, les variables, les références et les agents sont modifiables en Clojure.
L'utilisation de `set!` pour les champs Java est documentée dans la section «Interopérabilité Java».
Internement
Le système d'espaces de noms maintient des correspondances globales entre les symboles et les objets Var (voir Espaces de noms). Si une expression def ne trouve pas d'entrée internée dans l'espace de noms courant pour le symbole défini, elle en crée une ; sinon, elle utilise la Var existante. Ce processus de recherche ou de création est appelé internement. Cela signifie que, sauf s'ils ont été désinternés, les objets Var sont des références stables et n'ont pas besoin d'être recherchés systématiquement. Cela signifie également que les espaces de noms constituent un environnement global dans lequel, comme décrit dans la section Évaluation, le compilateur tente de résoudre tous les symboles libres comme des Vars.
La forme spéciale var ou la macro de lecture #' (voir Lecteur) peuvent être utilisées pour obtenir un objet Var interné au lieu de sa valeur courante.
Variables non internées
Il est possible de créer des vars non internées à l'aide de with-local-vars. Ces vars ne seront pas trouvées lors de la résolution des symboles libres et leurs valeurs doivent être consultées manuellement. Elles peuvent toutefois servir de cellules mutables locales au processus léger.
Métadonnées des variables
Les formulaires créant des variables (def, defn, defmacro,...) utilisent un ensemble standard de métadonnées pour décrire ces variables. Certains de ces formulaires utilisent une syntaxe explicite pour accepter les valeurs entreposées dans les métadonnées, mais généralement, vous pouvez également fournir ces métadonnées sous forme de dictionnaire associé au symbole de la variable.
Clefs courantes des métadonnées des variables (toutes facultatives lors de la définition de la variable) :
| Nom | Description |
|---|---|
| :doc | Une chaîne de caractères documentant la variable, généralement définie par le paramètre docstring. |
| :added | Une chaîne de caractères indiquant la version à laquelle cette variable a été ajoutée |
| :private | Un indicateur booléen, souvent défini par `defn-`, utilisé par l'auteur pour préciser que cette variable relève d'un détail d'implémentation. Les variables privées sont accessibles globalement, mais ne seront ni référencées ni listées dans les fonctions `ns-...` qui filtrent les variables non privées. |
| :arglists | Une liste d'arguments (column), générée automatiquement si non fournie, généralement utilisée pour documenter la syntaxe des macros |
| :macro | Un indicateur booléen ajouté automatiquement par defmacro (généralement non utilisé directement) |
| :tag | Un identificateur de type (généralement une classe) pour le type de la valeur dans la variable ou le type de retour d'une fonction stockée dans la variable. Notez que les métadonnées des variables sont évaluées ; ainsi, les indications de type comme `^long` sur la variable seront évaluées comme la fonction `long`, et non comme une indication de type primitif `long`. En général, il est préférable d'utiliser une indication de type sur la liste des arguments pour les variables définies. |
| :test | Le cadre d'application clojure.test associe les tests unitaires aux variables à l'aide de cette clé (généralement non utilisée directement). |
| :dynamic | Indique qu'une variable peut être redéfinie dynamiquement dans un contexte de processus léger (voir ci-dessus). Les variables dynamiques ne seront pas liées directement lors de la compilation avec liaison directe. |
| :redef | Indique qu'une variable ne doit pas être liée directement lors de la compilation avec liaison directe (permettant ainsi sa redéfinition). |
| :static | N'est plus utilisé (à l'origine, les variables étaient dynamiques par défaut, elles sont maintenant statiques par défaut) |
| :const | L'opérateur `-` indique qu'une variable est une constante de compilation et que le compilateur peut l'intégrer directement dans le code qui l'utilise. Remarque : ceci est rarement nécessaire et ne fonctionne qu'avec les constantes de compilation (lues, mais non évaluées), telles que les nombres, les chaînes de caractères, etc. (et non avec les classes, les fonctions, les types référence, etc.). Redéfinir ou lier dynamiquement une constante n'affectera pas le code qui utilise cette constante une fois compilée et chargée à l'exécution. |
Voir également les options du compilateur pour plus d'informations sur la liaison directe et l'élimination des métadonnées lors de la compilation.
Fonctions associées
| Catégorie | Fonctions |
|---|---|
| Variantes de def | defn defn- definline defmacro defmethod defmulti defonce defstruct |
| Collaboration avec des internes Vars | declare intern binding find-var var |
| Utilisation des objets Var | with-local-vars var-get var-set alter-var-root var? with-redefs with-redefs-fn |
| Validateurs de variables | set-validator! get-validator |
| Utilisation des métadonnées Var | doc find-doc test |