Les premiers pas
Vous trouverez ci-dessous des exemples de représentations littérales de types primitifs courants en Clojure. Toutes ces représentations sont des expressions Clojure valides.
Le point-virgule (;) insère un commentaire en fin de ligne. L'utilisation de plusieurs points-virgules pour délimiter des sections de commentaires d'entête est parfois une convention.
Types numériques
- 42 ; entier
- -1.5 ; virgule flottante
- 22/7 ; ratio
Les entiers sont lus en précision fixe 64 bits lorsqu'ils sont compris dans la plage autorisée, et en précision arbitraire dans le cas contraire. Un N à la fin permet de forcer une précision arbitraire. Clojure prend également en charge la syntaxe Java pour les entiers octaux (préfixe 0), hexadécimaux (préfixe 0x) et à base arbitraire (préfixe base, puis r, par exemple 2r pour le binaire). Les ratios sont fournis comme un type spécifique combinant un numérateur et un dénominateur.
Les nombres à virgule flottante sont lus en double précision 64 bits, ou en précision arbitraire avec un suffixe M. La notation exponentielle est également prise en charge. Les valeurs symboliques spéciales ##Inf, ##-Inf et ##NaN représentent respectivement l'infini positif, l'infini négatif et la valeur «non numérique».
Types de caractères
- "bonjour" ; chaîne de caractères
- \ ; caractère
- #"[0-9]+" ; expression régulière
Les chaînes de caractères sont placées entre guillemets doubles et peuvent s'étendre sur plusieurs lignes. Chaque caractère est précédé d'une barre oblique inverse. Il existe quelques caractères spéciaux : \newline, \space, \tab, etc. Les caractères Unicode peuvent être représentés par \uNNNN ou, en octal, par \oNNN.
Les expressions régulières littérales sont des chaînes de caractères commençant par un #. Elles sont compilées en objets java.util.regex.Pattern.
Symboles et identifiants
- map ; symbole
- + ; symbole - la plupart des signes de ponctuation sont autorisés
- clojure.core/+ ; symbole d'espace de noms
- nil ; valeur nulle
- true false ; booléennes
- :alpha ; mot-clef
- :release/alpha ; mot-clef avec espace de noms
Les symboles sont composés de lettres, de chiffres et d'autres signes de ponctuation. Ils servent à désigner un élément, comme une fonction, une valeur, un espace de noms, etc. Un symbole peut avoir un espace de noms, séparé de son nom par une barre oblique.
Trois symboles spéciaux sont interprétés comme des types différents : `nil` représente la valeur nulle, et `true` et `false` représentent les valeurs booléennes.
Les mots-clefs commencent par un deux-points et s'évaluent toujours à eux-mêmes. Ils sont fréquemment utilisés comme valeurs énumérées ou noms d'attributs en Clojure.
Collections littérales
Clojure inclut également une syntaxe littérale pour quatre types de collections :
- '(1 2 3) ; liste
- [1 2 3] ; vecteur
- #{1 2 3} ; ensemble
- {:a 1, :b 2} ; carte
Nous aborderons ces points plus en détail ultérieurement ; pour l'instant, il suffit de savoir que ces quatre structures de données peuvent être utilisées pour créer des données composites.
Évaluation
Nous allons maintenant examiner comment Clojure lit et évalue les expressions.
Évaluation traditionnelle (Java)
En Java, le code source (fichiers .java) est lu comme des caractères par le compilateur (javac), produisant du bytecode (fichiers .class) pouvant être chargé par la JVM.
Évaluation avec Clojure
En Clojure, le code source est lu comme des caractères par le lecteur. Ce dernier peut lire le code source à partir de fichiers .clj ou recevoir une série d'expressions de manière interactive. Le lecteur produit des données Clojure. Le compilateur Clojure génère ensuite le bytecode pour la JVM.
Deux points importants sont à retenir :
- L'unité de code source est une expression Clojure, et non un fichier source Clojure. Les fichiers source sont lus comme une série d'expressions, comme si vous les saisissiez interactivement dans le REPL.
- La séparation du lecteur et du compilateur est essentielle et permet l'utilisation de macros. Les macros sont des fonctions spéciales qui prennent du code (en tant que données) et en produisent (en tant que données). Voyez-vous où une boucle d'expansion de macro pourrait être insérée dans le modèle d'évaluation ?
Structure vs Sémantique
Prenons l'exemple d'une expression Clojure :
Ce diagramme illustre la différence entre la syntaxe (en vert, la structure de données Clojure produite par le lecteur) et la sémantique (en bleu, la manière dont ces données sont interprétées par l'environnement d'exécution Clojure).
La plupart des formes littérales Clojure s'évaluent directement, à l'exception des symboles et des listes. Les symboles servent à référencer un élément et, lorsqu'ils sont évalués, renvoient cet élément. Les listes (comme dans le diagramme) sont évaluées comme des invocations.
Dans le diagramme, `+ 3 4` est interprété comme une liste contenant le symbole `+` et deux nombres (3 et 4). Le premier élément (où se trouve `+`) peut être considéré comme la «position de la fonction», c'est-à-dire l'emplacement où trouver l'élément à appeler. Bien que les fonctions soient des éléments évidents à appeler, il existe également quelques opérateurs spéciaux reconnus par l'environnement d'exécution, des macros et d'autres éléments invocables.
Considérons l'évaluation de l'expression ci-dessus :
- 3 et 4 sont évalués comme étant eux-mêmes (entiers longs)
- + est évalué comme une fonction qui implémente +
- L'évaluation de la liste appellera la fonction + avec 3 et 4 comme arguments
De nombreux langages utilisent à la fois des instructions et des expressions. Les instructions ont un effet d'état, mais ne renvoient pas de valeur. En Clojure, tout est une expression qui s'évalue en une valeur. Certaines expressions (mais pas la plupart) ont également des effets de bord.
Voyons maintenant comment évaluer interactivement des expressions en Clojure.
Retarder l'évaluation grâce aux guillemets
Il est parfois utile de suspendre l'évaluation, notamment pour les symboles et les listes. Parfois, un symbole doit simplement être considéré comme tel, sans qu'on cherche à comprendre ce à quoi il fait référence :
- user=> 'x
- x
Et parfois, une liste ne devrait être qu'une liste de valeurs de données (et non du code à évaluer) :
- user=> '(1 2 3)
- (1 2 3)
Une erreur déroutante que vous pourriez rencontrer résulte d'une tentative accidentelle d'évaluer une liste de données comme s'il s'agissait de code :
- user=> (1 2 3)
- Execution error (ClassCastException) at user/eval156 (REPL:1).
- class java.lang.Long cannot be cast to class clojure.lang.IFn
Pour l'instant, ne vous préoccupez pas trop des citations, mais vous les verrez occasionnellement dans ces documents afin d'éviter l'évaluation de symboles ou de listes.
REPL
La plupart du temps, lorsque vous utilisez Clojure, vous le faites dans un éditeur ou une console REPL (Read-Eval-Print-Loop). La console REPL comprend les étapes suivantes :
- Lire une expression (une chaîne de caractères) pour produire des données Clojure.
- Évaluer les données renvoyées par l'étape 1 pour obtenir un résultat (également des données Clojure).
- Afficher le résultat en le reconvertissant en caractères.
- Retour au début.
Un aspect important du point n° 2 est que Clojure compile toujours l'expression avant de l'exécuter ; Clojure est toujours compilé en bytecode JVM. Il n'existe pas d'interpréteur Clojure.
- user=> (+ 3 4)
- 7
L'encadré ci-dessus illustre l'évaluation d'une expression (+ 3 4) et l'obtention d'un résultat.
Exploration du REPL
La plupart des environnements REPL proposent quelques astuces pour faciliter l'utilisation interactive. Par exemple, certains symboles spéciaux conservent en mémoire les résultats des trois dernières évaluations :
- *1 (dernier résultat)
- *2 (résultat de l'évaluation précédente)
- *3 (résultat de l'évaluation précédente)
- user=> (+ 3 4)
- 7
- user=> (+ 10 *1)
- 17
- user=> (+ *1 *2)
- 24
De plus, l'espace de noms clojure.repl, inclus dans la bibliothèque Clojure standard, fournit plusieurs fonctions utiles. Pour charger cette bibliothèque et rendre ses fonctions disponibles dans notre contexte actuel, appelez :
- (require '[clojure.repl :refer :all])
Pour l'instant, considérez cela comme une formule magique. Et voilà ! Nous l'expliquerons plus en détail lorsque nous aborderons les espaces de noms.
Nous avons maintenant accès à des fonctions supplémentaires utiles dans le REPL : `doc`, `find-doc`, `apropos`, `source` et `dir`.
La fonction `doc` affiche la documentation de n'importe quelle fonction. Appelons-la avec le symbole `+`:
- user=> (doc +)
-
- clojure.core/+
- ([] [x] [x y] [x y & more])
- Returns the sum of nums. (+) returns 0. Does not auto-promote
- longs, will throw on overflow. See also: +'
La fonction `doc` affiche la documentation de l'opérateur `+`, y compris les signatures valides.
La fonction `doc` affiche la documentation, puis renvoie `nil` comme résultat ; vous verrez les deux dans le résultat de l'évaluation.
On peut également appeler `doc` sur elle-même :
- user=> (doc doc)
-
- clojure.repl/doc
- ([name])
- Macro
- Prints documentation for a var or special form given its name
Vous ne savez pas comment s'appelle une fonction ? Vous pouvez utiliser la commande apropos pour trouver les fonctions correspondant à une chaîne de caractères ou à une expression régulière particulière.
- user=> (apropos "+")
- (clojure.core/+ clojure.core/+')
Vous pouvez également élargir votre recherche pour inclure les chaînes de documentation elles-mêmes avec find-doc :
- user=> (find-doc "trim")
-
- clojure.core/subvec
- ([v start] [v start end])
- Returns a persistent vector of the items in vector from
- start (inclusive) to end (exclusive). If end is not supplied,
- defaults to (count vector). This operation is O(1) and very fast, as
- the resulting vector shares structure with the original and no
- trimming is done.
-
- clojure.string/trim
- ([s])
- Removes whitespace from both ends of string.
-
- clojure.string/trim-newline
- ([s])
- Removes all trailing newline \ or return \ characters from
- string. Similar to Perl's chomp.
-
- clojure.string/triml
- ([s])
- Removes whitespace from the left side of string.
-
- clojure.string/trimr
- ([s])
- Removes whitespace from the right side of string.
Pour afficher la liste complète des fonctions d'un espace de noms particulier, vous pouvez utiliser la fonction `dir`. Ici, nous l'utilisons sur l'espace de noms `clojure.repl` :
- user=> (dir clojure.repl)
-
- apropos
- demunge
- dir
- dir-fn
- doc
- find-doc
- pst
- root-cause
- set-break-handler!
- source
- source-fn
- stack-element-str
- thread-stopper
Enfin, nous pouvons voir non seulement la documentation, mais aussi le code source sous-jacent de toute fonction accessible par l'environnement d'exécution :
- user=> (source dir)
-
- (defmacro dir
- "Prints a sorted directory of public vars in a namespace"
- [nsname]
- `(doseq [v# (dir-fn '~nsname)]
- (println v#)))
Tout au long de cet atelier, n'hésitez pas à consulter la documentation et le code source des fonctions que vous utilisez. Explorer l'implémentation de la bibliothèque Clojure est un excellent moyen d'approfondir vos connaissances sur le langage et son utilisation.
Il est également fortement conseillé de garder sous la main le guide de référence Clojure (Clojure Cheatsheet) pendant votre apprentissage. Ce guide catégorise les fonctions disponibles dans la bibliothèque standard et constitue une ressource précieuse.
Voici maintenant quelques notions de base de Clojure pour bien démarrer...
Notions de base de Clojure
def
Lors de l'évaluation de données dans un REPL, il peut être utile de sauvegarder une donnée pour une utilisation ultérieure. On peut le faire avec la fonction `def` :
- user=> (def x 7)
- #'user/x
La fonction `def` est une forme spéciale qui associe un symbole (`x`) de l'espace de noms courant à une valeur (`7`). Cette association est appelée une variable (`var`). Dans la plupart des cas, les variables font référence à une constante ou à une fonction, mais il est courant de les définir et de les redéfinir par commodité dans l'interpréteur interactif (REPL).
Notez que la valeur de retour ci-dessus est `#'user/x`, soit la représentation littérale d'une variable : `#'` suivi du symbole dans l'espace de noms. `user` est l'espace de noms par défaut.
Rappelons que les symboles sont évalués en recherchant ce à quoi ils font référence. On peut donc récupérer la valeur en utilisant simplement le symbole :
- user=> (+ x x)
- 14
Affichage
L'une des opérations les plus courantes lors de l'apprentissage d'un langage consiste à afficher des valeurs. Clojure propose plusieurs fonctions pour cela :
| Pour les humains | Lisible sous forme de données | |
|---|---|---|
| Avec saut de ligne | println | prn |
| Sans saut de ligne | pr |
Les formes lisibles par l'humain traduisent les caractères d'impression spéciaux (comme les sauts de ligne et les tabulations) en leur forme affichée et omettent les guillemets dans les chaînes de caractères. On utilise souvent `println` pour déboguer des fonctions ou afficher une valeur dans l'interpréteur interactif (REPL). `println` accepte un nombre quelconque d'arguments et insère un espace entre la valeur affichée de chaque argument :
- user=> (println "Qu'est-ce que c'est:" (+ 1 2))
- Qu'est-ce que c'est: 3
La fonction `println` a des effets de bord (affichage) et renvoie `nil`.
Notez que «Qu'est-ce que c'est:» ci-dessus n'affiche pas les guillemets et ne constitue pas une chaîne de caractères que le lecteur pourrait relire comme données.
Pour cela, utilisez `prn` afin d'afficher les données :
- user=> (prn "one\n\ttwo")
- "one\n\ttwo"
Le résultat affiché est désormais un format valide que le lecteur peut relire. Selon le contexte, vous pouvez privilégier soit le format texte, soit le format de données.