455 lines
16 KiB
Markdown
455 lines
16 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.
|
|
|
|
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 qui existent dans une interface mais échouent ensuite au runtime comme « unsupported ».
|
|
|
|
Hiérarchie principale retenue :
|
|
|
|
```text
|
|
Iterable<T>
|
|
↓
|
|
Collection<T>
|
|
↓
|
|
List<T>
|
|
↓
|
|
SettableList<T>
|
|
↓
|
|
ResizableList<T>
|
|
```
|
|
|
|
`Set<T>` et `Map<K,V>` sont des branches distinctes à préciser.
|
|
|
|
### 22.4.1 `Iterable<T>` / `Iterator<T>`
|
|
|
|
Interfaces Core reconnues par `foreach`.
|
|
|
|
Elles ne portent pas le préfixe `Op`, car `foreach` est une construction du langage et non un opérateur symbolique.
|
|
|
|
Indexabilité et itérabilité sont indépendantes.
|
|
|
|
### 22.4.2 `Collection<T>`
|
|
|
|
`Collection<T>` étend `Iterable<T>` et représente une collection finie d'éléments.
|
|
|
|
Contrat minimal retenu :
|
|
|
|
```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.3 `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 sont néanmoins conservés parce qu'ils expriment des concepts différents : cardinalité générale de collection et longueur d'une séquence indexable.
|
|
|
|
`List<T>` ne garantit ni remplacement d'un élément, ni redimensionnement, ni complexité algorithmique particulière de l'accès indexé.
|
|
|
|
### 22.4.4 `SettableList<T>`
|
|
|
|
`SettableList<T>` étend `List<T>` et `OpIndexMut<uint64,T>`.
|
|
|
|
Elle garantit le remplacement d'un élément existant sans modification de la longueur.
|
|
|
|
```text
|
|
list[index] = value;
|
|
```
|
|
|
|
ne signifie jamais `append` lorsque `index == length()`.
|
|
|
|
### 22.4.5 `ResizableList<T>`
|
|
|
|
`ResizableList<T>` étend `SettableList<T>`.
|
|
|
|
Elle garantit des opérations structurelles capables de modifier la longueur, par exemple ajout, insertion et suppression.
|
|
|
|
Les signatures exactes seront figées lors de la définition du type concret redimensionnable.
|
|
|
|
Un `resize(newLength)` sans information d'initialisation n'est pas retenu implicitement : agrandir une collection doit toujours définir comment les nouveaux éléments sont construits.
|
|
|
|
### 22.4.6 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 propagation générale de `const` reste applicable aux éléments obtenus via cet accès.
|
|
|
|
### 22.4.7 Classification initiale
|
|
|
|
```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>
|
|
```
|
|
|
|
`Vector<T>` reste à définir précisément.
|
|
|
|
Un type persistant/immutable par nature n'a aucune obligation d'implémenter `List<T>`. Par exemple, un futur `PersistentList<T>` peut implémenter uniquement `Collection<T>` et fournir ses propres opérations si cela correspond mieux à sa sémantique.
|
|
|
|
Une interface Saselang n'est jamais implémentée uniquement parce qu'un type ressemble conceptuellement à une autre famille de types.
|
|
|
|
## 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 runtime fault lors de la construction directe. Une API Core explicite retournant `Result` pourra être fournie lorsqu'une validation récupérable est souhaitée.
|
|
|
|
`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 runtime fault lors de la construction directe de la progression. Une API explicite en `Result` pourra exister pour une validation récupérable.
|
|
|
|
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.
|