Lire un refus
Fix refuse tôt et souvent — c'est son contrat : ce qui compile termine et est bien typé, donc ce qui ne peut pas être garanti est refusé avant de tourner. Un refus n'est pas une panne : c'est une information précise. Cette page apprend à la lire — chaque message ci-dessous est reproduit tel quel.
Et chaque refus routé se termine par un pointeur vers sa fiche — → :help termination — la doc arrive au moment exact où tu en as besoin, jamais ailleurs (elle ne s'invite pas sur un succès).
Les familles de refus
Type — la donnée ou la règle contredit le typage
fix: fait mal typé : `etat(auth, retabli)`
position 1 : `retabli` n'est pas une valeur du type `statut`.
Corrige : ajoute cette valeur au type, ou utilise une valeur déjà déclarée du type.
→ :help types
fix: règle mal typée : `add` — à la position 1 de `add`, une règle produit une étiquette là où le type déclaré attend un nombre
→ :help types
Le fait mal typé : une valeur n'appartient pas au type déclaré (retabli n'est pas un statut). La vérification descend dans les structures — l'intrus est trouvé même au fond d'une liste. La règle mal typée : une opération exige ce que le kind inféré du type ne permet pas (un choix de valeurs nommées passé à add) — tu n'as rien déclaré, le garde-fou a travaillé seul. → Les kinds.
Terminaison — la règle pourrait ne pas s'arrêter
fix: terminaison non prouvable : règle croissante essai (position 0)
→ :help termination
La règle fabrique des valeurs neuves (par add, ou en construisant un terme en tête) et se nourrit d'elle-même. Le vérificateur ignore les gardes — il refuse aussi des programmes qui terminent. Réécriture : forme décroissante, ou objets nommés. → La terminaison.
Sûreté — une variable de tête sortie de nulle part
fix: règle non sûre : longueur (variable de tête non liée, L₀)
→ :help safety
Toute variable de tête doit être liée par un littéral relationnel du corps — un fait, un prédicat — pas seulement testée par une garde ou laissée libre. → Bonnes pratiques.
Stratification — négation ou agrégat en circuit fermé
fix: cycle négatif (programme non stratifiable) : p
→ :help negation
fix: agrégat non-idempotent dans un cycle : v
→ :help aggregation
not et agg lisent leur source complète ; une définition circulaire à travers eux n'a pas de valeur stable. → La négation, L'agrégation.
Modules — la frontière tient
prédicat non exporté : infra::depend/2
→ :help modules
module non importé : infra::impacte/2
→ :help modules
Ce que le module n'exporte pas n'existe pas dehors ; ce que tu n'as pas importé non plus. Et un nom exporté par plusieurs modules importés doit s'utiliser qualifié (val/1 importé de plusieurs modules : m1, m2). → Les modules.
Déclaration désactivée
fix: déclaration désactivée (pull-driven, cf. backlog) : dual
→ :help ilp
dual et generalize sont volontairement hors service (leur réalisation provisoire cassait des garanties) — la déclaration est refusée plutôt que silencieusement ignorée.
Syntaxe
fix: `type paire(a, …)` : un lieur lie des VARIABLES, et une variable de type s'écrit en MAJUSCULE — écris `paire(A)`.
→ :help syntax
Un paramètre de type est une variable, et une variable s'écrit en majuscule — partout (type list(T) = nil | cons(T, list(T)).). La casse porte le sens : paire(T) accepte n'importe quel type, paire(t) désigne LE type nommé t.
Évaluation incomplète
fix: évaluation incomplète : point fixe non atteint en 100000 itérations
→ :help termination
La saturation n'a pas convergé — soit une accumulation récursive sur données cycliques (préfère la forme relaxation, voir Recettes), soit un cycle décroissant sous un agrégat récursif. Le signal arrive en temps borné, le résultat partiel n'est jamais rendu comme complet.
Le réflexe : où le refus pointe
Le message nomme toujours l'objet fautif — le fait tel que tu l'as écrit, le prédicat, la position d'argument. Trois questions dans l'ordre :
- Un fait est cité ? La valeur contredit un
typeou unsig— regarde la déclaration correspondante, l'un des deux a tort. - Un prédicat de règle est cité ? Regarde ses variables de tête (toutes liées par un littéral relationnel ?) puis son flux de valeurs (fabrique-t-il du neuf en boucle ?).
- Un cycle est cité ? Suis la boucle nommée : quelque part, un
notou un agrégat se lit lui-même.
Les messages sont des données
Le catalogue des gabarits est chargé dans la base au démarrage — interrogeable et extensible comme le reste :
?- error_template(type_error, fr, T).
T = le prédicat {} attend {}, reçu {}
?- error_severity(type_error, S).
S = fatal
Et comme c'est une relation, elle s'énumère — la liste complète des refus possibles est une requête, pas une annexe à maintenir :
?- error_template(C, fr, T).
C = type_error, T = le prédicat {} attend {}, reçu {}
C = type_error_fact, T = fait mal typé : {}
... % ~50 gabarits
Ajouter une langue est un :assert, pas une recompilation — voir Recettes.
Les bords honnêtes
- Certains refus sont conservateurs (terminaison) : programme correct refusé quand même — la réécriture fait partie du langage.
- La langue suit la session : français au REPL, anglais via MCP.