Structures de données
Clojure possède un ensemble riche de structures de données. Elles partagent un ensemble de propriétés :
- Elles sont immuables
- Elles sont lisibles
- Elles prennent en charge une sémantique d'égalité de valeurs correcte dans leur implémentation de la méthode `equals`
- Elles fournissent de bonnes valeurs de hachage
- De plus, les collections :
- Sont manipulées via des interfaces.
- Prise en charge du séquençage
- Prise en charge de la manipulation persistante.
- Prise en charge des métadonnées
- Implémentent `java.lang.Iterable`
- Implémentent la partie non optionnelle (lecture seule) de `java.util.Collection` ou `java.util.Map`.
nil
En Clojure, `nil` peut prendre n'importe quelle valeur, quel que soit son type. `nil` a la même valeur que `null` en Java. Le système de conditions de Clojure repose sur `nil` et `false`, qui représentent respectivement la fausseté et la vérité dans les tests conditionnels. De plus, `nil` est utilisé comme sentinelle de fin de séquence dans le protocole `sequence`.
Nombres
Clojure prend en charge par défaut les valeurs primitives de la JVM, permettant ainsi d'écrire du code Clojure performant et idiomatique pour les applications numériques.
Clojure prend également en charge les types numériques Java dérivés de java.lang.Number, notamment `BigInteger` et `BigDecimal`, ainsi que son propre type `Ratio`. Un traitement spécifique est prévu :
Longs
Par défaut, Clojure manipule les nombres naturels comme des instances du type primitif `long` de Java. Lorsqu'une opération sur un entier primitif produit une valeur trop grande pour être contenue dans un entier primitif, une exception java.lang.ArithmeticException est levée. Clojure propose des opérateurs mathématiques alternatifs, précédés d'une apostrophe : `+`, `-`, `*`, `inc` et `dec`. Ces opérateurs sont automatiquement convertis en `BigInt` en cas de dépassement de capacité, mais sont moins performants que les opérateurs mathématiques classiques.
Ratio
Représente un rapport entre entiers. La division d'entiers qui ne peuvent pas être réduits à un entier donne un rapport, par exemple 22/7 = 22/7, plutôt qu'une valeur à virgule flottante ou tronquée.
Contagion
Les types BigInt et à virgule flottante sont «contagieux» lors des opérations. Autrement dit, toute opération sur un entier impliquant un BigInt renverra un BigInt, et toute opération impliquant un double ou un float renverra un double.
Littéraux BigInt et BigDecimal
Les valeurs numériques littérales pour BigInt et BigDecimal sont spécifiées respectivement à l'aide des suffixes N et M.
| Exemple d'expression | Valeur de retour |
|---|---|
| (== 1 1.0 1M) | true |
| (/ 2 3) | 2/3 |
| (/ 2.0 3) | 0.6666666666666666 |
| (map #(Math/abs %) (range -3 3)) | (3 2 1 0 1 2) |
| (class 36786883868216818816N) | clojure.lang.BigInt |
| (class 3.14159265358M) | java.math.BigDecimal |
Fonctions associées
| Catégorie | Fonctions |
|---|---|
| Calcul | + - * / inc dec quot rem min max |
| Calcul d'autopromotion | +' -' *' inc' dec' |
| Comparaison | == < <= > >= zero? pos? neg? |
| Opérations bit à bit | bit-and bit-or bit-xor bit-not bit-shift-right bit-shift-left |
| Ratios | numerator denominator |
| Coercitions | int bigdec bigint double float long num short |
Chaînes de caractères
Les chaînes de caractères Clojure sont des chaînes de caractères Java. Voir aussi Affichage.
- user=> (map (fn [x] (.toUpperCase x)) (.split "Dasher Dancer Prancer" " "))
- ("DASHER" "DANCER" "PRANCER")
Fonctions associées
str string? pr-str prn-str print-str println-str with-out-str
Caractères
Les caractères Clojure sont des caractères Java.
Fonctions associées
char char-name-string char-escape-string
Mots clefs
Les mots clefs sont des identificateurs symboliques s'évaluant eux-mêmes. Ils permettent des tests d'égalité très rapides. Comme les symboles, ils possèdent un nom et un espace de noms optionnel, tous deux des chaînes de caractères. Le caractère «:» initial n'est pas inclus dans l'espace de noms ni dans le nom.
Les mots clefs implémentent une instruction `ifn` pour la méthode `invoke()` prenant un argument (une carte) et un second argument optionnel (une valeur par défaut). Par exemple, `(:mykey my-hash-map :none)` est équivalent à `(get my-hash-map :mykey :none)`. Voir `get`.
Fonctions associées
keyword keyword?
Symboles
Les symboles sont des identificateurs généralement utilisés pour faire référence à autre chose. Ils peuvent servir, dans les formulaires de programme, à désigner les paramètres de fonctions, les liaisons `let`, les noms de classes, les membres de classes et les variables globales. Ils possèdent un nom et, éventuellement, un espace de noms, tous deux de type chaîne de caractères. Les symboles peuvent contenir des métadonnées (voir `with-meta`).
À l'instar des mots-clefs, les symboles implémentent une instruction `ifn` pour la méthode `invoke()` prenant un argument (une carte) et un second argument optionnel (une valeur par défaut). Par exemple, `('mysym my-hash-map :none)` est équivalent à `(get my-hash-map 'mysym :none)`. Voir `get`.
Fonctions associées
symbol symbol? gensym (see also the #-suffix reader macro)
Collections
Toutes les collections Clojure sont immuables et persistantes. Elles permettent notamment la création efficace de versions modifiées grâce au partage structurel et garantissent des performances optimales pour une utilisation persistante. Les collections sont performantes et intrinsèquement sûres pour le multithreading. Elles sont représentées par des abstractions et peuvent avoir une ou plusieurs implémentations concrètes. En particulier, les opérations de modification générant de nouvelles collections, ces dernières peuvent avoir un type concret différent de la collection source, mais auront le même type logique (interface).
Toutes les collections prennent en charge `count` pour obtenir leur taille, `conj` pour y ajouter des éléments et `seq` pour obtenir une séquence parcourant l'intégralité de la collection. Leur comportement spécifique varie légèrement selon le type de collection.
Comme les collections prennent en charge la fonction `seq`, toutes les fonctions de séquence peuvent être utilisées avec n'importe quelle collection.
Hachage des collections Java
Les interfaces de collections Java spécifient les algorithmes de calcul des valeurs de `hashCode()` pour les listes (Lists), les ensembles (Sets) et les dictionnaires (Maps). Toutes les collections Clojure se conforment à ces spécifications dans leur implémentation de `hashCode()`.
Hachage des collections Clojure
Clojure propose son propre algorithme de hachage, offrant de meilleures propriétés de hachage pour les collections (et autres types), connues sous le nom de valeur `hashq`.
L'interface `IHashEq` identifie les collections fournissant la fonction `hashq()` pour obtenir la valeur `hashq`. En Clojure, la fonction `hash` peut être utilisée pour calculer cette valeur.
Les collections ordonnées (vector, list, seq,...) doivent utiliser l'algorithme suivant pour calculer `hashq` (où `hash` calcule `hashq`). Notez que les fonctions `unchecked-add-int` et `unchecked-multiply-int` sont utilisées pour gérer les dépassements de capacité des entiers.
- (defn hash-ordered [collection]
- (-> (reduce (fn [acc e] (unchecked-add-int
- (unchecked-multiply-int 31 acc)
- (hash e)))
- 1
- collection)
- (mix-collection-hash (count collection))))
Les collections non ordonnées (maps, sets) doivent utiliser l'algorithme suivant pour le calcul de leur hachage. Une entrée de map est traitée comme une collection ordonnée de clés et de valeurs. Notez que l'opérateur d'addition d'entiers non vérifié est utilisé pour gérer les dépassements de capacité des entiers.
- (defn hash-unordered [collection]
- (-> (reduce unchecked-add-int 0 (map hash collection))
- (mix-collection-hash (count collection))))
L'algorithme de hachage par mélange de collections est un détail d'implémentation susceptible d'être modifié.
Listes (IPersistentList)
Les listes sont des collections. Elles implémentent directement l'interface ISeq. (Notez qu'une liste vide implémente également ISeq, mais la fonction seq renverra toujours nil pour une séquence vide.) count est en O(1). conj place l'élément en tête de liste.
Fonctions associées
| Catégorie | Fonctions |
|---|---|
| Créer une liste | list list* |
| Considérez une liste comme une pile | peek pop |
| Examinez une liste | list? |
Vecteurs (IPersistentVector)
Un vecteur est une collection de valeurs indexées par des entiers contigus. L'accès aux éléments par leur index se fait en log32N sauts. La complexité temporelle de `count` est O(1). `conj` place l'élément à la fin du vecteur. Les vecteurs prennent également en charge `rseq`, qui renvoie les éléments dans l'ordre inverse. Ils implémentent `IFn`, pour l'appel de `invoke()` avec un argument, qu'ils considèrent comme un index et consultent en interne comme s'il s'agissait du n-ième élément; autrement dit, les vecteurs sont des fonctions de leurs indices. La comparaison des vecteurs s'effectue d'abord par longueur, puis élément par élément.
Fonctions associées
| Catégorie | Fonctions |
|---|---|
| Créer un vecteur | vector vec vector-of |
| Examiner un vecteur : | get nth peek rseq vector? |
| Modifier un vecteur | assoc pop subvec replace |
Voir aussi zippers
Cartes (IPersistentMap)
Une carte est une collection associant des clefs à des valeurs. Deux types de cartes sont disponibles : les cartes de hachage et les cartes triées. Les cartes de hachage requièrent des clés qui implémentent correctement `hashCode` et `equals`. Les cartes triées requièrent des clés qui implémentent `Comparable` ou une instance de `Comparator`. Les cartes de hachage offrent un accès plus rapide (log32N sauts) que les cartes triées (logN sauts), mais les cartes triées sont, comme leur nom l'indique, triées. La complexité temporelle de `count` est O(1). La fonction `conj` attend une autre carte (éventuellement à entrée unique) comme argument et renvoie une nouvelle carte qui est la combinaison de la carte d'origine et des entrées de la nouvelle, lesquelles peuvent écraser les entrées de l'ancienne. `conj` accepte également un `MapEntry` ou un vecteur de deux éléments (clef et valeur). La fonction `seq` renvoie une séquence d'entrées de la carte, c'est-à-dire des paires clef/valeur. Les cartes triées prennent également en charge `rseq`, qui renvoie les entrées dans l'ordre inverse. Les maps implémentent la fonction IFn, qui appelle la méthode invoke() avec un argument (une clef) et un second argument optionnel (une valeur par défaut) ; autrement dit, les maps sont des fonctions de leurs clés. Les clés et valeurs nulles sont acceptées.
Fonctions associées
| Catégorie | Fonctions |
|---|---|
| Créer une nouvelle carte | hash-map sorted-map sorted-map-by |
| Modifier une carte | assoc dissoc select-keys merge merge-with zipmap |
| Examinez une carte | get contains? find keys vals map? |
| Examiner une entrée de carte | key val |
StructMaps
La plupart des cas d'utilisation de StructMaps seraient désormais mieux couverts par les enregistrements.
Souvent, de nombreuses instances de map partagent le même ensemble de clés de base, par exemple lorsque les maps sont utilisées comme des structures ou des objets dans d'autres langages. Les StructMaps prennent en charge ce cas d'utilisation en partageant efficacement les informations de clef, tout en fournissant des accesseurs optionnels plus performants à ces clefs. Les StructMaps sont en tous points des maps : elles prennent en charge le même ensemble de fonctions, sont interopérables avec toutes les autres maps et sont extensibles de manière persistante (c'est-à-dire que les struct maps ne sont pas limitées à leurs clefs de base). La seule restriction est qu'il est impossible de dissocier une struct map de l'une de ses clés de base. Une struct map conserve ses clés de base dans l'ordre.
Les StructMaps sont créées en créant d'abord un objet de base de structure à l'aide de `create-struct` ou `defstruct`, puis en créant des instances avec `struct-map` ou `struct`.
- (defstruct desilu :fred :ricky)
- (def x (map (fn [n]
- (struct-map desilu
- :fred n
- :ricky 2
- :lucy 3
- :ethel 4))
- (range 100000)))
- (def fred (accessor desilu :fred))
- (reduce (fn [n y] (+ n (:fred y))) 0 x)
- -> 4999950000
- (reduce (fn [n y] (+ n (fred y))) 0 x)
- -> 4999950000
Fonctions associées
| Catégorie | Fonctions |
|---|---|
| Configuration de StructMap | create-struct defstruct accessor |
| Créer une structure individuelle | struct-map struct |
ArrayMaps
Lors de la manipulation de code, il est souvent souhaitable d'utiliser une table de hachage conservant l'ordre des clefs. Une table de hachage est une telle table : elle est simplement implémentée comme un tableau de paires clef-valeur... De ce fait, ses performances de recherche sont linéaires et elle ne convient qu'aux très petites tables de hachage. Elle implémente l'interface complète des tables de hachage. De nouvelles tables de hachage peuvent être créées avec la fonction `array-map`. Notez qu'une table de hachage ne conserve l'ordre de tri que lorsqu'elle n'est pas modifiée. Les associations ultérieures la transformeront finalement en table de hachage.
Sets
Les ensembles sont des collections de valeurs uniques.
Il existe une prise en charge littérale des ensembles de hachage :
- #{:a :b :c :d}
- -> #{:d :a :b :c}
Vous pouvez créer des ensembles avec les fonctions hash-set et sorted-set :
- (hash-set :a :b :c :d)
- -> #{:d :a :b :c}
-
- (sorted-set :a :b :c :d)
- -> #{:a :b :c :d}
Vous pouvez également obtenir un ensemble de valeurs dans une collection à l'aide de la fonction set :
- (set [1 2 3 2 1 2 3])
- -> #{1 2 3}
Les ensembles sont des collections :
- (def s #{:a :b :c :d})
- (conj s :e)
- -> #{:d :a :b :e :c}
-
- (count s)
- -> 4
-
- (seq s)
- -> (:d :a :b :c)
-
- (= (conj s :e) #{:a :b :c :d :e})
- -> true
Les ensembles prennent en charge la suppression avec `disj`, ainsi que `contains?` et `get`, ce dernier renvoyant l'objet contenu dans l'ensemble correspondant à la clef, s'il est trouvé :
- (disj s :d)
- -> #{:a :b :c}
-
- (contains? s :b)
- -> true
-
- (get s :a)
- -> :a
Les ensembles sont des fonctions de leurs éléments, en utilisant get :
- (s :b)
- -> :b
-
- (s :k)
- -> nil
Clojure fournit des opérations ensemblistes de base comme l'union / la différence (difference) / l'intersection, ainsi qu'une certaine prise en charge de l'algèbre pseudo-relationnelle pour les « relations », qui sont simplement des ensembles de cartographies - sélectionner (select) / index / renommer (rename) / joindre (join).