Files
saselang-bible/chapters/022-collections-et-iteration.md
2026-09-13 21:59:15 +02:00

1027 lines
35 KiB
Markdown

# 22. Collections et itération
## 22.1 `Array<T>` — V1 REQUIS — FIGÉ EN PRINCIPE
`Array<T>` est une séquence contiguë dont la longueur est déterminée au runtime lors de la construction et reste fixe ensuite.
Il n'expose pas d'opération structurelle de redimensionnement.
Les éléments suivent exactement la sémantique normale de `T` :
```text
T primitif -> valeur by-value
T struct -> valeur by-value
T class -> référence de classe
```
Copier un `Array<T>` crée une collection indépendante. Les éléments eux-mêmes sont copiés selon la sémantique normale de `T`.
Pour `Array<User>`, deux arrays copiés peuvent donc contenir des références vers les mêmes objets `User`, tout en restant indépendants quant au remplacement de leurs cases.
### 22.1.1 Initialisation complète
Un `Array<T>` safe doit être entièrement initialisé avant de devenir observable.
Il n'existe aucune initialisation implicite en zéro, `null`, `None` ou valeur indéfinie.
```text
Array<int32> values = [1, 2, 3];
Array<int32> empty = [];
```
Le littéral `[...]` est contextualisé par le type attendu ; il ne reçoit pas automatiquement un type autonome hors contexte suffisant.
Les expressions d'un littéral d'array sont évaluées exactement une fois, de gauche à droite. En cas d'échec pendant la construction, aucun array partiellement initialisé ne devient observable.
### 22.1.2 `filled`
```text
Array<T>::filled(uint64 length, T value)
```
`value` est évaluée une seule fois, puis répétée selon la sémantique normale de copie de `T`.
```text
User firstUser = ...;
User otherUser = ...;
Array<User> users = Array<User>::filled(3, firstUser);
users[1] = otherUser; // OK si l'accès n'est pas const
```
Initialement, les trois cases contiennent la même référence `firstUser`. Chaque case reste cependant un emplacement indépendant et peut recevoir ultérieurement une autre référence.
`filled()` ne clone jamais implicitement un objet référencé.
### 22.1.3 `empty` et `generate`
```text
Array<T>::empty()
```
construit un array de longueur zéro.
Une opération `generate` est retenue conceptuellement pour construire une valeur distincte par index. Sa syntaxe exacte sera figée avec les callables/lambdas.
La factory devra être appelée exactement une fois par index, dans l'ordre croissant, sans parallélisation implicite.
Le safe code ne peut pas obtenir un `Array<T>` observable partiellement/non initialisé. Le compilateur/runtime reste libre d'utiliser de la mémoire non initialisée en interne tant qu'elle n'est jamais observable.
## 22.2 `StaticArray<T,N>` — V1 REQUIS — FIGÉ
`StaticArray<T,N>` est une séquence contiguë dont la longueur `N` est connue à la compilation et reste fixe.
```text
StaticArray<int32, 3> values = [10, 20, 30];
StaticArray<int32, 0> empty = [];
```
Le nombre d'éléments du littéral doit correspondre exactement à `N`.
Une initialisation répétée utilise :
```text
StaticArray<T,N>::filled(T value)
```
avec la même sémantique de copie que `Array<T>::filled`.
`StaticArray<T,N>` n'autorise pas davantage qu'`Array<T>` l'observation d'éléments non initialisés.
## 22.3 `Slice<T>` — V1 REQUIS — FIGÉ EN PRINCIPE
`Slice<T>` est une vue contiguë, de longueur fixe, sur une portion d'un stockage existant.
Une slice ne réalise aucune copie sémantique des éléments.
```text
Array<int32> values = ...;
Slice<int32> part = values::slice(10..<20);
part[0] = 42; // modifie values[10]
```
Une slice :
```text
reste attachée à la portion qu'elle désigne
conserve une longueur fixe
n'est pas redimensionnable
n'est pas déplaçable vers une autre portion
peut modifier les éléments uniquement si l'accès source l'autorise
ne peut jamais augmenter les droits de mutabilité de la source
```
La constness se propage :
```text
const Array<User> users = ...;
const Slice<User> part = users::slice(0..<4);
part[0] = otherUser; // ERROR
part[0]::setName("Alice"); // ERROR
part[0]::getName(); // OK
```
Le backing storage nécessaire à une slice reste vivant aussi longtemps que nécessaire. Cette garantie ne requiert aucune syntaxe de lifetime utilisateur.
Lorsqu'une slice provient d'un `Vector<T>`, elle référence un backing storage stable/indirect et non une adresse physique brute dont la validité dépendrait d'une allocation précise. Une relocalisation physique du buffer est donc invisible sémantiquement.
En V1 :
```text
remplacement d'un élément du Vector
reserve(...) / relocalisation physique
append(...)
-> les Slice existantes restent valides
insert(...)
removeAt(...)
clear() ayant effectivement supprimé au moins un élément
-> toutes les Slice dérivées de ce Vector sont invalidées
utilisation ultérieure d'une Slice invalidée
-> SliceInvalidatedFault
```
`append` préserve les slices existantes parce qu'il ne change ni la position ni l'identité logique des éléments déjà présents. `insert` et `removeAt` peuvent déplacer les positions existantes ; V1 choisit donc une invalidation globale simple plutôt qu'un suivi dynamique de toutes les plages actives.
Un `clear()` sur un vector déjà vide n'effectue aucune mutation structurelle et n'introduit pas une invalidation supplémentaire.
Une slice vide est une valeur valide.
Les indices d'une sous-slice sont relatifs à la slice source.
`Slice<T>` garantit la contiguïté. Une éventuelle vue non contiguë constituerait un autre concept et n'est pas réservée sans besoin réel.
## 22.4 Interfaces de collections — V1 REQUIS — FIGÉ EN PRINCIPE
Les interfaces décrivent des capacités réellement garanties. Les classes/structs génériques fournissent le stockage et l'implémentation.
Saselang ne retient pas le modèle d'opérations optionnelles présentes dans une interface mais susceptibles d'échouer ensuite comme « unsupported ».
Hiérarchie principale :
```text
Iterable<T>
Collection<T>
List<T>
SettableList<T>
ResizableList<T>
```
`Set<T>` et `Map<K,V>` forment des branches sémantiques distinctes.
### 22.4.1 `Iterable<T>` et `Iterator<T>`
`foreach` dépend uniquement de `Iterable<T>`.
```text
interface Iterable<T> {
const method iterator() -> Iterator<T>;
}
```
Créer un iterator ne constitue pas une mutation sémantique de la source.
`Iterator<T>` représente une itération particulière et possède un état de progression mutable :
```text
interface Iterator<T> {
method next() -> Option<T>;
}
```
`Some(value)` fournit l'élément suivant ; `None` marque la fin normale de l'itération.
Saselang n'impose pas le couple `hasNext()` / `next()`. `Option<T>` rend l'état de fin explicite dans une seule opération.
`Iterator<T>` n'étend pas `Iterable<T>` par défaut. Une source capable de créer des itérations et une itération déjà en cours sont deux capacités différentes, même si certains types concrets peuvent éventuellement fournir les deux.
Pour les collections ordinaires non concurrentes, une mutation structurelle invalide par défaut les iterators actifs lorsque le type concret ne garantit pas explicitement une autre politique.
```text
insert / remove / clear / append / changement structurel
-> peut invalider l'iterator
utilisation ultérieure d'un iterator invalidé
-> IteratorInvalidatedFault
```
Le simple remplacement d'une valeur existante n'est pas automatiquement une mutation structurelle. Le type concret documente sa politique exacte.
Les collections concurrentes ou spécialisées peuvent fournir une autre politique (`snapshot`, stabilité, weak consistency, etc.) ; elle doit être explicite.
### 22.4.2 `View<T>`
`View<T>` représente une vue légère sur une source existante :
```text
interface View<T> extends Iterable<T> {
const method count() -> uint64;
const method isEmpty() -> bool;
}
```
`View<T>` n'étend pas `Collection<T>`.
Le contrat général ne contient pas `contains()`. Une classe ou une interface plus spécialisée peut l'ajouter si elle en a réellement besoin.
Une `View<T>` :
```text
n'impose aucune copie des éléments
n'impose aucun stockage indépendant
n'impose aucune contiguïté
ne possède pas nécessairement les données qu'elle expose
ne peut jamais augmenter les permissions const de sa source
```
Par défaut, une vue est **vivante**, pas un snapshot : une nouvelle itération peut observer les modifications ultérieures de la source selon le contrat du type concret.
Le runtime/compilateur garantit que le stockage ou l'état source nécessaire reste valide aussi longtemps que la vue l'exige, sans syntaxe de lifetime utilisateur.
Le comportement d'un iterator déjà créé avant une modification reste régi par la politique d'invalidation/stabilité du type concret.
### 22.4.3 `Collection<T>`
`Collection<T>` étend `Iterable<T>` et représente une collection finie d'éléments.
Contrat minimal :
```text
count() -> uint64
isEmpty() -> bool
contains(const T value) -> bool
```
`count()` exprime le nombre d'éléments au sens général de collection.
### 22.4.4 `List<T>`
`List<T>` étend `Collection<T>` et `OpIndex<uint64,T>`.
Elle garantit :
```text
ordre stable
indexation positionnelle en lecture
length() -> uint64
```
Pour une `List<T>` :
```text
count() == length()
```
Les deux noms restent distincts parce qu'ils expriment la cardinalité générale d'une collection et la longueur d'une séquence indexable.
`List<T>` ne garantit ni remplacement, ni redimensionnement, ni complexité algorithmique particulière de l'accès indexé.
### 22.4.5 `SettableList<T>`
`SettableList<T>` étend `List<T>` et `OpIndexMut<uint64,T>`.
Elle garantit le remplacement d'un élément existant sans modification de longueur :
```text
list[index] = value;
```
`index` doit désigner une case existante ; l'affectation ne signifie jamais `append`.
### 22.4.6 `ResizableList<T>`
`ResizableList<T>` étend `SettableList<T>` et garantit les opérations structurelles fondamentales suivantes :
```text
interface ResizableList<T> extends SettableList<T> {
method append(T value) -> Void;
method insert(uint64 index, T value) -> Void
faults IndexOutOfBoundsFault;
method removeAt(uint64 index) -> T
faults IndexOutOfBoundsFault;
method clear() -> uint64;
}
```
`insert(index,value)` accepte `0 <= index <= length()`. `index == length()` est donc un append positionnel valide, même si `append(value)` reste la forme explicite recommandée pour cette intention.
`removeAt(index)` exige `0 <= index < length()` et retourne l'élément retiré. Le modèle futur copy/move fixe son transfert physique sans changer cette signature sémantique.
Un `resize(newLength)` sans information d'initialisation n'est pas retenu : agrandir une collection ne doit jamais inventer implicitement des valeurs zéro, nulles ou non initialisées.
`clear()` retourne le nombre d'éléments effectivement supprimés.
### 22.4.7 Capacité du type et permission `const`
La capacité intrinsèque du type et la permission d'un accès sont deux dimensions distinctes.
```text
SettableList<User> users = ...;
const SettableList<User> readonly = users;
```
Le type sait remplacer des éléments, mais l'accès `readonly` ne peut pas utiliser cette capacité.
La même règle s'applique à `Map`, `Set`, `View` et aux éléments obtenus à travers ces accès : une projection ne peut jamais augmenter les droits reçus de sa source.
### 22.4.8 Classification initiale des séquences
```text
StaticArray<T,N>
List<T>
SettableList<T>
pas ResizableList<T>
Array<T>
List<T>
SettableList<T>
pas ResizableList<T>
Slice<T>
List<T>
SettableList<T>
pas ResizableList<T>
Vector<T>
List<T>
SettableList<T>
ResizableList<T>
```
Un type persistant/immutable par nature n'a aucune obligation d'implémenter `List<T>` s'il ne garantit pas sa sémantique.
### 22.4.9 `Vector<T>` — V1 REQUIS — FIGÉ
`Vector<T>` est l'implémentation standard SDK d'une séquence contiguë redimensionnable. Il implémente `ResizableList<T>`.
Surface fondamentale :
```text
Vector<T>::empty() -> Vector<T>
Vector<T>::filled(uint64 length, T value) -> Vector<T>
Vector<T>::generate(...) -> Vector<T> // signature finale avec les callables
Vector<T>::builder() -> VectorBuilder<T>
length() -> uint64
count() -> uint64
isEmpty() -> bool
vector[index] -> T
vector[index] = value
append(T value) -> Void
insert(uint64 index, T value) -> Void
faults IndexOutOfBoundsFault
removeAt(uint64 index) -> T
faults IndexOutOfBoundsFault
clear() -> uint64
capacity() -> uint64
reserve(uint64 additional) -> Void
slice(Range<uint64> range) -> Slice<T>
```
`capacity()` et `reserve()` sont propres à `Vector<T>` et ne font pas partie de `ResizableList<T>`.
`reserve(additional)` garantit, si l'opération réussit, une capacité suffisante pour ajouter au moins `additional` éléments supplémentaires sans nouvelle croissance de capacité. Il ne change jamais `length()`.
Le contrat standard ne fournit pas :
```text
reserveExact()
resize(newLength) sans valeur/factory
shrink général
remove(value) générique
```
Une collection spécialisée peut exposer une opération de shrink lorsqu'un besoin réel le justifie ; cette capacité n'appartient ni à `Vector<T>` général ni à `ResizableList<T>`.
Les littéraux `[...]` restent les littéraux contextualisés d'`Array<T>` / `StaticArray<T,N>` et ne construisent pas implicitement un `Vector<T>` :
```text
Vector<int32> values = [1, 2, 3]; // ERROR
```
La construction multi-éléments dynamique passe par une class method/factory explicite, notamment le builder.
`Vector<T>` garantit :
```text
indexation [] / []= O(1)
length / count / isEmpty O(1)
append O(1) amorti
insert O(n) au pire
removeAt O(n) au pire
slice O(1)
capacity O(1)
reserve O(n) au pire si relocalisation nécessaire
```
Ces garanties appartiennent au type concret `Vector<T>`, pas à `List<T>` ou `ResizableList<T>` en général.
### 22.4.10 `Set<T>` et `ResizableSet<T>`
`Set<T>` étend `Collection<T>`.
Il garantit :
```text
unicité des éléments
aucun index ordinal
aucun ordre d'itération général garanti
```
La capacité structurelle est séparée :
```text
interface ResizableSet<T> extends Set<T> {
method add(T value) -> bool;
method remove(const T value) -> bool;
method clear() -> uint64;
}
```
Sémantique :
```text
add(value)
true -> élément ajouté
false -> élément déjà présent, set inchangé
remove(value)
true -> élément supprimé
false -> élément absent
clear()
-> nombre d'éléments effectivement supprimés
```
Il n'existe pas de niveau `SettableSet<T>` : remplacer un élément d'un set équivaut conceptuellement à une modification structurelle de son ensemble de valeurs.
### 22.4.11 `Map<K,V>`, `SettableMap<K,V>` et `ResizableMap<K,V>`
`Map<K,V>` n'étend pas `Collection<T>` et n'implémente pas directement `Iterable<...>`. Une map possède trois projections naturelles différentes : clés, valeurs et entrées ; aucune ne doit être choisie implicitement comme itération « par défaut ».
Contrat de lecture :
```text
interface Map<K,V> extends OpIndex<K,V> {
const method count() -> uint64;
const method isEmpty() -> bool;
const method containsKey(const K key) -> bool;
const method get(const K key) -> Option<V>;
const method keys() -> View<K>;
const method values() -> View<V>;
const method entries() -> View<MapEntry<K,V>>;
}
```
Deux intentions sont séparées :
```text
map[key] -> V
accès strict
la clé doit exister
absence dynamique -> KeyNotFoundFault
map::get(key) -> Option<V>
accès conditionnel
absence normale -> None
```
`SettableMap<K,V>` étend `Map<K,V>` et `OpIndexMut<K,V>`.
```text
map[key] = value;
```
remplace uniquement la valeur d'une clé existante. Cette syntaxe n'insère jamais implicitement une nouvelle clé. Une clé absente produit `KeyNotFoundFault`.
`ResizableMap<K,V>` étend `SettableMap<K,V>` et ajoute les changements structurels :
```text
insert(K key, V value) -> Void
faults DuplicateKeyFault
remove(const K key) -> V
faults KeyNotFoundFault
clear() -> uint64
```
`insert` affirme que la clé est nouvelle ; `remove` affirme qu'elle existe et retourne la valeur retirée. Leurs violations dynamiques sont des `Fault` unchecked et capturables.
Le Core ne fournit pas mécaniquement `tryInsert` / `tryRemove` ayant pour seule fonction de dupliquer ces opérations dans `Result` ou `bool`. Une variante conditionnelle ne sera ajoutée que si elle porte une sémantique réellement utile distincte.
Il n'existe pas de `put()` implicite « insert ou replace » tant qu'un besoin concret ne justifie pas cette troisième intention.
### 22.4.12 `MapEntry<K,V>`
`MapEntry<K,V>` représente une paire clé/valeur observée lors d'une projection `entries()`.
Conceptuellement :
```text
struct MapEntry<K,V> {
const method key() -> K;
const method value() -> V;
}
```
`MapEntry` n'est pas un proxy mutable vers un bucket interne de la map et n'expose pas de `setValue()` implicite.
La mutation d'une map reste explicite via :
```text
map[key] = value;
```
La sémantique de copie/référence de `K` et `V` suit les règles normales de leurs types.
### 22.4.13 Ordre : `Ordering`, `Comparable<T>` et `Comparator<T>`
L'ordre total applicatif utilise :
```text
enum Ordering {
Less,
Equal,
Greater
}
```
`Comparable<T>` exprime l'ordre naturel ou principal du type dans son domaine :
```text
interface Comparable<T> {
const method compareTo(const T other) -> Ordering;
}
```
Un type peut tout à fait implémenter `Comparable<T>` tout en supportant plusieurs ordres alternatifs via des comparators. Par exemple, un `User` d'un jeu peut avoir un classement principal naturel et rester comparable autrement par nom, niveau ou date.
`Comparator<T>` exprime un ordre externe/alternatif :
```text
interface Comparator<T> {
const method compare(const T left, const T right) -> Ordering;
}
```
Règle de sélection :
```text
Comparator explicitement fourni
-> utilisé exclusivement
aucun Comparator fourni
-> Comparable<T> requis
```
Aucun mélange/fallback entre les deux n'est effectué pendant une même opération ou dans une même collection ordonnée.
`Ordering::Equal` signifie équivalence selon cette relation d'ordre. Il n'implique pas nécessairement `left == right`.
Les implémentations de `Comparable` et `Comparator` doivent respecter cohérence, symétrie d'ordre et transitivité. Le compilateur n'est pas tenu de prouver ces propriétés générales.
### 22.4.14 `SortedSet<T>` et `SortedMap<K,V>`
`SortedSet<T>` étend `Set<T>` et garantit un ordre total stable selon le comparator actif :
```text
interface SortedSet<T> extends Set<T> {
const method comparator() -> Option<Comparator<T>>;
const method first() -> Option<T>;
const method last() -> Option<T>;
}
```
`None` pour `comparator()` signifie que l'ordre naturel `Comparable<T>` est utilisé. `Some(comparator)` expose l'ordre externe choisi pour cette instance.
`SortedMap<K,V>` étend `Map<K,V>` et ordonne les entrées par clé :
```text
interface SortedMap<K,V> extends Map<K,V> {
const method comparator() -> Option<Comparator<K>>;
const method firstKey() -> Option<K>;
const method lastKey() -> Option<K>;
}
```
Pour une `SortedMap`, les vues `keys()`, `values()` et `entries()` parcourent les données dans l'ordre des clés.
Les opérations de bornes/ranges (`floor`, `ceiling`, `lower`, `higher` ou noms définitifs) seront fermées séparément afin de ne pas introduire de noms ambigus par simple imitation d'une autre plateforme.
### 22.4.15 `TreeSet<T>` et `TreeMap<K,V>`
Les types standard ordonnés généraux sont nommés :
```text
TreeSet<T>
TreeMap<K,V>
```
Le nom n'impose pas une structure arborescente précise au niveau ABI/implémentation tant que le contrat observable reste respecté.
Un futur `BTreeMap<K,V>` peut exister comme implémentation explicitement fondée sur un B-tree ; il ne remplace pas le nom général `TreeMap`.
Comme Saselang n'autorise qu'un `construct` par classe, les deux modes de création passent par des factories de classe distinctes :
```text
TreeSet<T>::natural()
requiert T implements Comparable<T>
TreeSet<T>::withComparator(Comparator<T> comparator)
n'exige pas Comparable<T>
TreeMap<K,V>::natural()
requiert K implements Comparable<K>
TreeMap<K,V>::withComparator(Comparator<K> comparator)
n'exige pas Comparable<K>
```
Les contraintes appartiennent aux factories concernées et non nécessairement au type générique entier.
### 22.4.16 `Equatable<T>`, `Hashable` et `Hasher`
`Equatable<T>` est un contrat Core de haut niveau fondé sur l'égalité totale `OpEqual<T>` ; il n'ajoute aucune seconde méthode d'égalité :
```text
interface Equatable<T> extends OpEqual<T> {
}
```
`Hashable` est un contrat Core indépendant de `Comparable<T>` et de `Equatable<T>` au niveau de l'héritage :
```text
interface Hashable {
const method hash(Hasher hasher) -> Void;
}
```
Obligation normative de `Hashable` : **toute donnée participant à l'identité hashable d'une valeur doit rester stable pendant toute la durée de vie de cette valeur**.
Un objet peut donc rester mutable dans ses autres propriétés. Si `id` seul définit son hash et son égalité, des champs tels que `name` ou `score` peuvent changer sans violer `Hashable`.
Lorsqu'un type est à la fois `Equatable<T>` et `Hashable`, le contrat exige :
```text
a == b
-> même résultat de hash avec le même Hasher et la même configuration
```
L'inverse n'est pas requis : une collision de hash entre valeurs non égales est normale.
`Hasher` appartient au Core parce que son contrat doit être connu des valeurs `Hashable` :
```text
interface Hasher {
method writeBytes(const Slice<uint8> bytes) -> Void;
const method finish() -> uint64;
}
```
Les implémentations concrètes de `Hasher` appartiennent au SDK/runtime et peuvent employer des algorithmes/seeds différents.
`finish()` observe l'état courant sans le modifier : deux appels successifs sans `writeBytes()` intermédiaire retournent le même résultat.
Le Core définit le framing canonique utilisé par ses types fondamentaux `Hashable`. Les composants logiques successifs ne doivent pas devenir indistinguables par simple concaténation ambiguë d'octets ; par exemple les couples `("ab","c")` et `("a","bc")` ne peuvent pas être traités comme une unique séquence logique identique.
La représentation injectée dans un `Hasher` est un protocole de hashing interne, pas un format de sérialisation, une ABI ou un format de stockage public.
Les `float*` bruts conservent leur égalité IEEE partielle et ne satisfont pas directement `Equatable<float*>`; ils ne sont donc pas directement admissibles comme clés des collections hashées standard qui exigent `Equatable`.
### 22.4.17 `HashSet<T>` et `HashMap<K,V>`
Les implémentations standard hashées appartiennent au SDK :
```text
HashSet<T>::empty() -> HashSet<T>
HashSet<T>::builder() -> HashSetBuilder<T>
HashMap<K,V>::empty() -> HashMap<K,V>
HashMap<K,V>::builder() -> HashMapBuilder<K,V>
HashSet<T>
implements ResizableSet<T>
where T implements Equatable<T>
where T implements Hashable
HashMap<K,V>
implements ResizableMap<K,V>
where K implements Equatable<K>
where K implements Hashable
```
Elles ne garantissent aucun ordre logique ou d'itération stable. Deux instances contenant les mêmes éléments, ou deux exécutions du même programme, peuvent parcourir les données dans des ordres différents.
Le hasher concret et sa seed ne font pas partie du contrat observable général. Deux instances peuvent employer des seeds distinctes.
Une collision est normale :
```text
même hash + égalité vraie
-> même clé / même élément logique
même hash + égalité fausse
-> collision, les deux valeurs peuvent coexister
```
Il n'existe pas de `HashCollisionFault`.
Garanties de complexité moyennes :
```text
contains / containsKey / get O(1) moyen
add / insert / remove O(1) moyen
count / isEmpty O(1)
```
Le pire cas peut être O(n). Le contrat ne fige ni chaining, ni open addressing, ni Robin Hood, ni autre stratégie physique.
Pour `HashMap`, toutes les règles strictes de `Map`/`ResizableMap` restent valables : `map[key]` exige la clé, `get()` représente l'absence normale, `insert()` exige une clé nouvelle, `remove()` exige une clé existante et retourne `V`.
Pour `HashSet`, `add()` et `remove()` conservent leurs retours booléens ordinaires.
Une réorganisation interne des buckets/rehash est physiquement invisible. Une `View<T>` existante sur `keys()`, `values()` ou `entries()` reste une vue valide de la collection ; un `Iterator<T>` déjà créé peut néanmoins être invalidé par la mutation structurelle selon la règle générale des iterators.
### 22.4.18 Builders des collections concrètes
Les collections dynamiques concrètes utilisent des class methods explicites plutôt que de détourner le littéral `[...]` :
```text
Vector<T>::builder() -> VectorBuilder<T>
HashSet<T>::builder() -> HashSetBuilder<T>
HashMap<K,V>::builder() -> HashMapBuilder<K,V>
```
Contrats minimaux :
```text
class VectorBuilder<T> {
method append(T value) -> VectorBuilder<T>;
const method build() -> Vector<T>;
}
class HashSetBuilder<T> {
method add(T value) -> HashSetBuilder<T>;
const method build() -> HashSet<T>;
}
class HashMapBuilder<K,V> {
method insert(K key, V value) -> HashMapBuilder<K,V>
faults DuplicateKeyFault;
const method build() -> HashMap<K,V>;
}
```
Les builders sont des objets mutables ordinaires. Les méthodes de configuration peuvent retourner le même builder afin de permettre le chaînage :
```text
Vector<int32> values =
Vector<int32>::builder()
::append(1)
::append(2)
::append(3)
::build();
```
`build()` ne consomme pas le builder. Un builder peut produire plusieurs collections successives ; chaque résultat est sémantiquement indépendant des modifications ultérieures du builder. Un partage physique interne/COW reste possible tant que cette indépendance observable est respectée.
Un builder vide produit la collection vide correspondante.
`HashSetBuilder<T>` suit la sémantique du set : ajouter deux valeurs égales ne produit pas un fault et la valeur n'apparaît qu'une fois.
`HashMapBuilder<K,V>` suit la sémantique stricte de `insert` : une seconde clé égale produit `DuplicateKeyFault`; aucune règle implicite « first wins » ou « last wins » n'existe.
Les builders standard ne permettent pas de choisir une seed ou une implémentation de hasher. Ils ne fournissent pas non plus un `reserveExact` caché.
### 22.4.19 Collections concurrentes
Les garanties concurrentes sont orthogonales aux capacités structurelles et relèvent du SDK.
Une future :
```text
ConcurrentMap<K,V>
```
exprime des garanties de concurrence/atomicité et ne remplace pas `ResizableMap<K,V>`.
Un type concret peut par exemple implémenter les deux :
```text
ConcurrentHashMap<K,V>
implements ResizableMap<K,V>, ConcurrentMap<K,V>
```
Le SDK n'introduit pas une variante `ConcurrentX` pour chaque type uniquement par symétrie. Les interfaces concurrentes ne sont ajoutées que lorsqu'un contrat atomique/concurrent concret existe.
Une opération composée n'est pas atomique simplement parce que chaque appel individuel est thread-safe. Les futures opérations atomiques (`putIfAbsent`, comparaison/remplacement, etc.) seront spécifiées avec le modèle de concurrence.
## 22.5 `Range<T>` — V1 REQUIS — FIGÉ EN PRINCIPE
`Range<T>` est une vraie valeur Saselang immuable représentant un intervalle ordonné. Il ne représente ni un pas ni une progression d'itération.
Les quatre syntaxes de bornes sont :
```text
a..b [a, b] bornes basse et haute incluses
a>..b (a, b] borne basse exclue, borne haute incluse
a..<b [a, b) borne basse incluse, borne haute exclue
a>..<b (a, b) bornes basse et haute exclues
```
Les deux bornes doivent avoir exactement le même type `T`. Saselang ne possède pas de `Range<L,U>` et ne recherche aucun type commun implicite entre deux bornes déjà typées différemment.
Un littéral non encore typé peut toutefois être contextualisé normalement :
```text
Range<uint64> ids = 1..100;
```
### 22.5.1 Domaine ordonné
`Range<T>` est disponible lorsque les valeurs admissibles utilisées comme bornes appartiennent à un domaine totalement ordonné selon le contrat de comparaison retenu par Saselang.
La règle est fondée sur la capacité du type, pas sur une whitelist fermée. Elle peut donc s'appliquer à des entiers, `char`, des enums ou structs explicitement ordonnés, et à des types Core/utilisateur satisfaisant le contrat requis.
Les enums n'obtiennent pas automatiquement un ordre à partir de leur ordre de déclaration : ils doivent fournir explicitement la capacité d'ordre total requise.
Les floats sont autorisés comme bornes de `Range<floatN>` uniquement pour leur sous-domaine fini :
```text
NaN interdit comme borne
+Infinity interdit comme borne
-Infinity interdit comme borne
valeur finie autorisée
```
`-0.0` et `+0.0` occupent la même position dans l'ordre numérique d'un range, même si leur représentation binaire reste distincte et observable par les mécanismes bas niveau appropriés.
`NaN` et les infinities n'appartiennent à aucun `Range<floatN>` défini avec ces règles ; `contains()` retourne donc `false` pour ces valeurs.
### 22.5.2 Construction et ranges vides
Les bornes sont évaluées de gauche à droite, exactement une fois chacune, avant la construction du range.
Une borne statiquement prouvée invalide provoque une erreur de compilation. Une borne calculée dynamiquement mais invalide selon le contrat de `Range<T>` provoque un `Fault` runtime lors de la construction directe. Le nom concret du fault sera fixé avec l'API Core ; aucune variante `try... -> Result` parallèle n'est créée automatiquement pour la même validation.
`lower > upper` ne constitue pas une erreur : le résultat est un range vide.
Lorsque les bornes sont égales :
```text
a..a contient exactement a
a>..a vide
a..<a vide
a>..<a vide
```
Un range vide est une valeur valide. En revanche, un range statiquement vide utilisé comme pattern de `match` est interdit parce que la branche serait inatteignable.
Les ranges ouverts/infinis tels que `..b`, `a..` ou `..` ne font pas partie de V1.
### 22.5.3 API fondamentale
`Range<T>` doit exposer au minimum les capacités conceptuelles suivantes, dont les noms sont retenus sauf raison ultérieure forte de les changer :
```text
range::lower()
range::upper()
range::includesLower()
range::includesUpper()
range::isEmpty()
range::contains(value)
```
Le range est immuable : modifier une borne signifie construire un autre `Range<T>`.
Le `match` utilisant un range-pattern emploie la même sémantique d'appartenance sans invoquer un prédicat utilisateur arbitraire.
## 22.6 Progression et itération des ranges — V1 REQUIS — FIGÉ EN PRINCIPE
L'existence d'un `Range<T>`, son itérabilité et sa progression sont trois concepts distincts :
```text
Range<T> intervalle ordonné
RangeIterable<T> parcours canonique vers l'avant
RangeStep<T,Step> parcours avant avec pas explicite
RangeReverse<T> parcours canonique vers l'arrière
RangeReverseStep<T,Step> parcours arrière avec pas explicite
RangeProgression<T,Step> valeur de progression produite
```
Ces noms sont retenus comme noms Core significatifs ; leur namespace exact sera décidé lors de l'organisation générale de Core, probablement dans une famille dédiée aux ranges.
Un type n'est pas obligé de fournir toutes ces capacités. Un range peut être valide sans être itérable ; il peut être itérable vers l'avant sans supporter `reverse()` ou `step()`.
`foreach` continue à consommer `Iterable<T>` : un `Range<T>` ou une `RangeProgression<T,Step>` n'est utilisable par `foreach` que lorsque les capacités Core concernées fournissent cette itérabilité.
### 22.6.1 Progression canonique
Lorsqu'un type fournit `RangeIterable<T>`, le parcours direct d'un range utilise sa progression canonique. Celle-ci n'est pas définie génériquement comme un appel caché à `step(1)`.
Pour les entiers Core, la progression canonique avance naturellement d'une unité. Pour `char`, elle avance parmi les Unicode scalar values valides et saute les valeurs surrogate `U+D800..U+DFFF`, qui ne sont pas des `char` Saselang valides.
Un `Range<floatN>` est un intervalle valide lorsque ses bornes sont finies, mais il n'est pas automatiquement itérable et ne reçoit pas automatiquement une capacité de `step` flottant.
### 22.6.2 `step(...)`
Le pas ne fait jamais partie du `Range`. La syntaxe de parcours explicite est :
```text
(range)::step(step)
```
Elle produit une progression itérable et ne modifie pas le `Range` d'origine.
La compatibilité entre le type des bornes `T` et le type du pas `Step` est définie explicitement par `RangeStep<T,Step>` ou `RangeReverseStep<T,Step>`. Elle n'est pas déduite automatiquement d'une conversion numérique générale.
Cela permet notamment de définir des couples adaptés au domaine :
```text
RangeStep<int32,int32>
RangeStep<int32,uint32>
RangeStep<char,uint32>
RangeStep<Date,Duration>
```
et de refuser des couples qui introduiraient une perte ou n'auraient pas de sens. Par exemple, un range `float16` peut recevoir seulement les types de pas explicitement supportés sans imposer qu'un `float32` plus large soit accepté.
Le pas exprime une magnitude de progression, pas une direction. Pour les types numériques Core, il doit être strictement supérieur à zéro. Un pas statiquement nul ou négatif est une erreur de compilation ; une valeur dynamique invalide provoque un `Fault` runtime lors de la construction directe de la progression. Aucune variante `try... -> Result` n'est ajoutée uniquement pour dupliquer ce contrôle.
Pour les types utilisateur, la validité du pas est définie par le contrat Range correspondant et n'est pas réduite artificiellement à une comparaison numérique avec zéro.
### 22.6.3 `reverse()`
La direction inverse est explicite :
```text
(range)::reverse()
(range)::reverse()::step(step)
```
`reverse()` ne construit pas un range aux bornes inversées. Le `Range` reste identique ; seule la direction de parcours change.
La forme canonique compose la direction avant le pas :
```text
range
[::reverse()]
[::step(step)]
```
Une progression déjà matérialisée par `step()` n'est pas requise de fournir à son tour `reverse()`. Cela évite plusieurs chaînes d'appels synonymes pour le même parcours.
L'inclusion ou l'exclusion des bornes reste une propriété du range et ne change jamais avec la direction.
### 22.6.4 Arrêt, overflow et pas non aligné
Une progression s'arrête avant de produire une valeur hors du range. Elle ne doit jamais provoquer un overflow uniquement pour constater qu'elle a atteint la fin.
Par exemple :
```text
(250u8..255u8)::step(2u8)
```
produit conceptuellement :
```text
250 252 254
```
sans tenter de former `256`.
De même :
```text
(1..10)::step(4)
```
produit :
```text
1 5 9
```
La borne finale n'est pas ajoutée artificiellement si elle ne tombe pas naturellement sur la progression.
Un range vide produit simplement une progression vide.