Files
saselang-bible/chapters/014-generics.md
2026-09-12 18:12:13 +02:00

566 lines
13 KiB
Markdown

# 14. Generics
## 14.1 Disponibilité et syntaxe — V1 REQUIS — FIGÉ
Les generics font partie de V1.
Exemples :
```text
Array<T>
Result<T,E>
OpAdd<Lhs,Rhs,Out>
```
Déclarations :
```text
class Box<T> {
...
}
struct Pair<A, B> {
...
}
interface Comparable<T> {
...
}
func identity<T>(T value) -> T {
return value;
}
```
Les paramètres génériques sont toujours déclarés explicitement. Un identifiant inconnu n'est jamais transformé implicitement en paramètre générique.
Les noms de paramètres de type suivent la règle des noms de types et commencent donc par une lettre ASCII majuscule.
## 14.2 Instanciation explicite et absence de raw type — V1 REQUIS — FIGÉ
Un type générique doit être instancié avec ses arguments :
```text
Box<int32>
Pair<String, uint64>
Result<Data, ParseError>
```
La forme brute d'un type générique est invalide :
```text
Box
Result
```
Il n'existe pas en V1 de déduction du type nominal à la construction comparable à la class template argument deduction de C++.
L'identité du type générique construit doit être explicite :
```text
Box<int32> box = Box<int32>(42);
```
## 14.3 Inférence à l'appel — V1 REQUIS — FIGÉ
L'inférence des paramètres génériques est autorisée pour un appel de fonction ou de méthode lorsque les arguments fournis déterminent directement et sans ambiguïté les paramètres.
```text
func first<T>(T left, T right) -> T;
int32 a = ...;
int32 b = ...;
int32 c = first(a, b);
```
L'inférence :
- utilise les types des arguments d'appel ;
- n'utilise jamais le type de destination ou le type de retour attendu ;
- n'effectue aucune conversion implicite pour rechercher un « meilleur » type ;
- reste une commodité locale d'appel et ne déduit jamais l'identité d'un type nominal construit.
Exemple interdit :
```text
func create<T>() -> T;
String value = create(); // ERROR
```
Il faut écrire :
```text
String value = create<String>();
```
Exemple sans promotion implicite :
```text
func same<T>(T a, T b) -> bool;
int16 a = ...;
int32 b = ...;
same(a, b); // ERROR
same<int32>(a::toInt32(), b); // OK
```
## 14.4 Contraintes nominales — V1 REQUIS — FIGÉ
V1 utilise deux formes nominales de contraintes :
```text
where T extends SomeClass
where T implements SomeInterface
```
Une ligne `where` porte une contrainte.
Pour un même paramètre de type :
- au maximum une contrainte `extends` ;
- plusieurs contraintes `implements` sont autorisées ;
- toutes les contraintes sont cumulatives.
Exemple :
```text
func process<T>(T value) -> Void
where T extends Entity
where T implements Serializable
where T implements Comparable<T>
{
...
}
```
L'ordre textuel des contraintes n'a pas de sémantique. Le formatter peut choisir un ordre canonique, notamment `extends` avant `implements`.
## 14.5 Contraintes dépendantes et récursives — V1 REQUIS — FIGÉ
Un paramètre générique peut apparaître dans le contrat portant sur un autre paramètre :
```text
func compare<A, B>(A left, B right) -> Ordering
where A implements Comparable<B>
{
...
}
```
Les contraintes F-bounded sont autorisées :
```text
where T implements Comparable<T>
```
La satisfaction des contraintes est statique et nominale. Il n'existe pas de duck typing.
## 14.6 `Numeric` et absence de catégories syntaxiques — V1 REQUIS — FIGÉ EN PRINCIPE
V1 n'introduit pas de contraintes syntaxiques telles que :
```text
where T is numeric
where T is primitive
where T is struct
where T is class
```
Lorsqu'une propriété correspond à une vraie abstraction réutilisable, elle doit être représentée par un contrat Core réel.
Exemple :
```text
where T implements Numeric
```
`Numeric` est une abstraction Core nominale ; elle n'est pas un prédicat spécial du compilateur.
Des contraintes intrinsèques peuvent exister dans certaines définitions compiler/Core-known sans devenir automatiquement de nouvelles constructions de la syntaxe utilisateur.
## 14.7 Contraintes redondantes et héritées — V1 REQUIS — FIGÉ
Une contrainte nominalement redondante est interdite.
Si :
```text
interface B extends A {
}
```
alors :
```text
where T implements B
where T implements A
```
est une erreur de déclaration redondante.
Lorsqu'un type générique hérite d'un parent générique contraint, les contraintes nécessaires doivent être répétées explicitement dans le contrat du type enfant.
```text
open class Base<T>
where T implements Serializable
{
}
class Child<T> extends Base<T>
where T implements Serializable
{
}
```
Le compilateur ne rend pas silencieusement le contrat local plus contraint en important des `where` invisibles depuis le parent.
Lorsqu'un enfant ferme le paramètre sur un type concret, il suffit que ce type concret satisfasse les contraintes du parent.
## 14.8 Héritage et interfaces génériques — V1 REQUIS — FIGÉ
Les arguments génériques d'un parent ou d'une interface sont toujours explicites :
```text
class Box<T> implements Iterable<T> {
...
}
class StringList implements Iterable<String> {
...
}
class StringContainer extends Container<String> {
...
}
```
Une transformation finie des paramètres est autorisée lorsqu'elle ne constitue pas une récursivité générique interdite :
```text
class PairBox<T> implements Container<Pair<T, T>> {
...
}
```
Après substitution, les règles normales de signature et de contrat s'appliquent.
Une même interface générique peut être atteinte avec plusieurs spécialisations lorsque les contrats résultants sont compatibles.
```text
Consumer<String>
Consumer<Array<uint8>>
```
peuvent par exemple produire deux signatures distinctes.
Lorsque plusieurs chemins atteignent exactement la même spécialisation depuis la même origine, ils représentent une seule obligation :
> même contrat provenant de la même origine = une seule obligation, pas de faux conflit.
## 14.9 Overrides génériques — V1 REQUIS — FIGÉ
Une implémentation ou un override générique doit conserver une structure générique contractuellement équivalente :
```text
même nombre de paramètres génériques
même nature type/const de chaque paramètre
mêmes relations de contraintes
même contrat callable après substitution
```
Les noms des paramètres génériques peuvent être alpha-renommés.
Une implémentation ne peut pas ajouter une contrainte qui réduirait l'ensemble des appels autorisés par le contrat parent, ni supprimer une contrainte si cela modifie ce contrat.
## 14.10 Variance — V1 REQUIS — FIGÉ
Tous les paramètres génériques sont invariants en V1.
Même si :
```text
Dog extends Animal
```
cela n'implique pas :
```text
Container<Dog> -> Container<Animal>
```
V1 n'introduit pas :
```text
in T
out T
+T
-T
wildcards Java
```
La variance déclarative pourra être réévaluée après le self-hosting ou pour V3 uniquement si un besoin concret est démontré. Elle n'est pas réservée « au cas où ».
## 14.11 Spécialisation et stratégie d'implémentation — V1 REQUIS — FIGÉ
V1 n'offre aucune spécialisation sémantique des generics.
Une définition générique possède une seule sémantique visible.
Le backend reste libre d'utiliser :
```text
monomorphisation
code partagé avec metadata
stratégie hybride
```
tant que le comportement observable Saselang reste identique.
La stratégie peut varier entre LLVM, interpréteur, JVM, WASM ou futurs backends.
## 14.12 Const generics — V1 REQUIS — FIGÉ
Syntaxe :
```text
struct StaticArray<T, const uint64 N> {
...
}
struct Matrix<T, const uint64 Rows, const uint64 Columns> {
...
}
```
Les types admissibles d'un **paramètre const generic V1** sont uniquement :
```text
bool
char
uint8
uint16
uint32
uint64
uint128
uint256
enum simple sans payload
```
Les `int*` restent naturellement utilisables dans les expressions constantes générales du langage, mais ne sont pas nécessaires comme types de paramètres const generic V1.
Ne sont notamment pas admis comme types de paramètres const generic V1 :
```text
int8..int256
float16..float128
String
class
struct arbitraire
tuple
enum avec payload
```
Cette liste pourra évoluer en V2/V3 uniquement à partir de besoins concrets.
## 14.13 Expressions d'arguments const generic — V1 REQUIS — FIGÉ
Un argument const generic doit être entièrement déterminable à la compilation et produire exactement le type attendu après contextualisation normale des littéraux.
Sont admissibles directement :
```text
littéraux
const nommées déjà résolues
paramètres const generic
parenthèses
opérateurs primitifs purs autorisés sur ces valeurs
```
Exemples :
```text
StaticArray<uint8, 32>
StaticArray<uint8, PAGE_SIZE>
StaticArray<uint8, PAGE_SIZE * 2>
StaticArray<uint8, 1u64 << 12>
```
Aucun appel de callable n'est admis directement dans `<...>` :
```text
StaticArray<uint8, computeSize()> // ERROR
StaticArray<uint8, value::toUint64()> // ERROR
```
Sont également interdits directement dans l'argument const generic :
```text
fonction
méthode
constructeur
allocation
I/O
état runtime
exception
appel d'intrinsic exposé comme callable
```
S'il faut calculer une valeur par un mécanisme plus élaboré, elle doit d'abord être matérialisée dans une `const` nommée d'un type admissible, selon les règles séparées d'initialisation compile-time :
```text
const uint64 SIZE = ...;
StaticArray<uint8, SIZE>
```
La validité de l'expression qui initialise `SIZE` relève des règles des `const`, pas des const generics.
Overflow, division par zéro, shift invalide ou autre faute statiquement prouvable restent des erreurs de compilation ordinaires. Aucun wrapping, saturation ou changement de règle n'est implicite dans un contexte const generic.
## 14.14 Identité et inférence des const generics — V1 REQUIS — FIGÉ
L'identité d'une instanciation utilise la valeur constante résultante et son type, pas le texte source.
Ainsi :
```text
StaticArray<T, 2 + 2>
StaticArray<T, 4>
```
désignent la même instanciation lorsque le paramètre attendu est le même.
L'inférence d'un const generic est uniquement **structurelle**.
```text
func length<T, const uint64 N>(StaticArray<T, N> array) -> uint64
```
peut inférer `T` et `N` depuis un argument `StaticArray<int32, 32>`.
Le compilateur ne résout pas d'équation algébrique pour déduire un paramètre :
```text
func strange<const uint64 N>(StaticArray<int32, N * 2> value)
```
ne demande pas au compilateur de résoudre `N * 2 = 10`.
## 14.15 Contraintes de valeurs sur const generics — V1 REQUIS — FIGÉ
V1 n'introduit pas :
```text
where N > 0
where N <= 1024
```
`where` reste réservé aux relations nominales de types.
Les contraintes de valeur nécessaires à un type Core/compiler-known sont vérifiées par le contrat de ce type sans créer en V1 un système général de preuve ou de contraintes arithmétiques.
## 14.16 Récursivité générique régulière — V1 REQUIS — FIGÉ
Une déclaration générique peut se référencer récursivement avec exactement les mêmes arguments :
```text
class Node<T> {
Nullable<Node<T>> next;
}
```
```text
class BufferNode<T, const uint64 N> {
Nullable<BufferNode<T, N>> next;
}
```
La récursivité reste naturellement soumise aux règles générales de layout : une récursivité by-value reste interdite.
## 14.17 Récursivité générique transformante — V1 REQUIS — FIGÉ
Une auto-référence à la même déclaration générique avec des arguments transformés est interdite immédiatement en V1 :
```text
class Node<T> {
Nullable<Node<Array<T>>> next; // ERROR
}
```
```text
class BufferNode<T, const uint64 N> {
Nullable<BufferNode<T, N + 1>> next; // ERROR
}
```
Pour les cycles indirects, le compilateur rejette le cycle lorsqu'une même déclaration générique est réatteinte avec des arguments différents.
Valide :
```text
class A<T> {
Nullable<B<T>> b;
}
class B<T> {
Nullable<A<T>> a;
}
```
Invalide :
```text
class A<T> {
Nullable<B<Array<T>>> b;
}
class B<T> {
Nullable<A<T>> a;
}
```
V1 ne tente pas de prouver qu'une transformation particulière finirait éventuellement par se stabiliser.
## 14.18 Récursivité des callables génériques — V1 REQUIS — FIGÉ
La récursivité normale des fonctions et méthodes est autorisée et le compilateur ne cherche pas à prouver leur terminaison générale.
Une callable générique récursive doit cependant rappeler la même instanciation générique.
```text
func recurse<T>(T value) -> Void {
recurse<T>(value); // OK
}
```
La polymorphic recursion transformante est interdite en V1 :
```text
func recurse<T>(T value) -> Void {
recurse<Array<T>>(...); // ERROR
}
```
La même règle s'applique aux cycles mutuellement récursifs de callables génériques.
## 14.19 Parameter packs et mécanismes différés — V1 REQUIS — FIGÉ
V1 n'introduit pas :
```text
<T...>
<const uint64... N>
generic parameter packs
wildcards
specialization
variance déclarative
contraintes négatives
```
Ces mécanismes ne sont pas réservés sans besoin démontré.
---