566 lines
13 KiB
Markdown
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é.
|
|
|
|
---
|