v0.2.14
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 14. Generics
|
||||
|
||||
## 14.1 Disponibilité — V1 REQUIS — FIGÉ
|
||||
## 14.1 Disponibilité et syntaxe — V1 REQUIS — FIGÉ
|
||||
|
||||
Les generics font partie de V1.
|
||||
|
||||
@@ -12,23 +12,554 @@ Result<T,E>
|
||||
OpAdd<Lhs,Rhs,Out>
|
||||
```
|
||||
|
||||
## 14.2 Points restant à fermer — V1 REQUIS — À FINALISER
|
||||
|
||||
Doivent encore être spécifiés précisément :
|
||||
Déclarations :
|
||||
|
||||
```text
|
||||
syntaxe des contraintes
|
||||
variance ou absence de variance
|
||||
contraintes multiples
|
||||
specialization éventuelle
|
||||
monomorphisation vs représentation partagée
|
||||
contraintes sur types primitifs
|
||||
const generics pour StaticArray<T,N>
|
||||
generic methods
|
||||
inférence éventuelle des arguments génériques à l'appel
|
||||
wildcards/existentials éventuels
|
||||
class Box<T> {
|
||||
...
|
||||
}
|
||||
|
||||
struct Pair<A, B> {
|
||||
...
|
||||
}
|
||||
|
||||
interface Comparable<T> {
|
||||
...
|
||||
}
|
||||
|
||||
func identity<T>(T value) -> T {
|
||||
return value;
|
||||
}
|
||||
```
|
||||
|
||||
Le type de retour ne doit pas devenir un moyen de résoudre une surcharge ambiguë.
|
||||
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é.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user