1027 lines
35 KiB
Markdown
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.
|