Les types dépendants : quand le type calcule
Dans la plupart des langages, un type décrit une forme : « une liste d'entiers », « une paire de chaînes ». Il ne sait rien des valeurs. Une liste de 3 éléments et une liste de 300 ont le même type — et c'est à toi de te souvenir laquelle fait quoi.
Les types dépendants (RDT, Relational Dependent Types) cassent cette frontière : le type peut porter une valeur comme indice. Pas « un vecteur d'entiers », mais « un vecteur d'entiers de longueur 3 ». Et cette longueur, Fix sait la calculer.
L'idée en une ligne
Le type n'est plus seulement une étiquette. C'est une donnée que le moteur manipule — au même titre que tes faits.
has_type(v1, vec(int, 3)).
has_type(v2, vec(int, 5)).
vec(int, 3) est un type, mais 3 dedans est une vraie valeur. Rien de spécial : c'est un terme composé ordinaire, et Fix le traite comme tout le reste.
Le moteur calcule l'indice
Concatène deux vecteurs : quelle est la longueur du résultat ? Tu ne la donnes pas — tu écris la relation entre les longueurs, et Fix la résout.
result_type(A, B, vec(T, N)) :-
has_type(A, vec(T, M)),
has_type(B, vec(T, K)),
add(M, K, N).?- result_type(v1, v2, T).
T = vec(int, 8)
add(M, K, N) n'est pas un calcul impératif : c'est la relation « M + K = N ». Fix lie M à 3, K à 5, et en déduit N = 8. La longueur du type a été calculée par inférence — avant la moindre exécution.
Le type qui refuse
C'est là que ça devient utile. Multiplier deux matrices n'a de sens que si les dimensions intérieures coïncident. Exprime cette contrainte dans le type, et le produit mal formé ne type tout simplement pas.
has_type(a, matrix(float, 2, 3)).
has_type(b, matrix(float, 3, 4)).
has_type(c, matrix(float, 5, 4)).
matmul(A, B, matrix(T, R, C)) :-
has_type(A, matrix(T, R, M)),
has_type(B, matrix(T, M, C)).
Le M partagé fait tout le travail : la colonne de gauche est la ligne de droite, par unification. Donne-lui des dimensions qui collent, il calcule le type du résultat ; donne-lui un cas absurde, il n'y a aucune solution.
?- matmul(a, b, R).
R = matrix(float, 2, 4)
?- matmul(a, c, R).
false. % 3 ≠ 5 : pas de produit
Le bug classique — « j'ai inversé deux dimensions » — est attrapé par la structure du type, pas par un test que tu aurais pu oublier d'écrire.
Programme réel :
examples/dependent-types/02-matrix.fix— et six autres exemples dans le même répertoire (unités, typestate, AST typé…).
Note le geste : le type dérivé vit dans son propre prédicat (matmul/3, result_type/3), pas dans une tête has_type(mul(X, Y), …) — construire un terme neuf en tête d'une règle récursive est refusé par la garde de terminaison (voir La terminaison). On nomme les objets, on relie leurs types.
Ce n'est pas un système de types dépendants
C'est le point qui surprend. Fix n'a pas de système de types dépendants — pas de vérificateur dédié, pas de langage de preuve, pas de théorie posée par-dessus le langage. D'autres approches ajoutent une couche entière pour soutenir la dépendance type↔valeur ; Fix n'ajoute rien.
Parce qu'en Fix, deux choses sont déjà vraies :
- un type est un terme — exactement la même substance que tes données ;
- le même moteur (unification + point fixe) les calcule.
Du coup, les types dépendants ne sont pas une feature : ils tombent de là. Un type indexé, c'est un terme composé ; la contrainte entre indices, c'est une règle ; la vérification, c'est le même calcul qui fait le reste. Tu en fais sans le savoir dès que tu écris une règle dont la tête porte une valeur calculée.
Autrement dit : Fix ne fait pas « des types dépendants ». Il fait « programmer les types comme des données » — et les types dépendants en sont un cas particulier.
Le tour relationnel
Comme une règle est une relation, l'indice se lit aussi à l'envers. Tu peux demander « quels vecteurs donneraient un résultat de longueur 8 ? » et Fix remonte la contrainte — tant que tu n'as pas jeté l'information en chemin (voir la bidirectionnalité). Le type n'est pas une voie à sens unique : c'est une relation entre des formes et des valeurs, parcourable dans les deux sens.
Les bords honnêtes
- Le mot-clé
typete donne déjà l'algèbre des types : les sommes (le|, un coproduit) et les produits (un constructeur à plusieurs champs). RDT y ajoute une valeur portée — une longueur, une dimension. - Fix dérive ces indices vers l'avant (des entrées vers le résultat) par l'arithmétique. La propagation arrière complète d'une contrainte d'indice reste limitée.
- Les indices sont des valeurs, pas des preuves : Fix calcule, il ne démontre pas de propositions. Ce n'est pas un assistant de preuve, et il ne cherche pas à l'être.
À côté
- TLLP / KLLP — écrire des règles d'inférence dont la tête est un type ou un kind.
- Référence : types dépendants — la forme exacte.
- Apprendre — 2. Les types — la première rencontre, en douceur.