Xalg / Fix / Recettes
Navigation
Apprendre
Recettes
Référence
Comprendre
Bonnes pratiques
En pratique
Explications

Recettes Fix

Des réponses courtes à « comment je fais X ? ». Chaque recette est autonome : copie, adapte, lance.


Calculer une fermeture transitive

Tu as des liens directs, tu veux tous les liens indirects (atteignabilité, dépendances, ancêtres).

edge(a, b).  edge(b, c).  edge(c, d).

path(X, Y) :- edge(X, Y).
path(X, Z) :- path(X, Y), path(Y, Z).
?- path(a, d).
true.

La deuxième règle se relit telle quelle : « un chemin, c'est deux chemins mis bout à bout ». Tu n'écris aucune boucle. (C'est le programme examples/graph.fix, le même qui ouvre Apprendre.)


Empêcher les valeurs invalides

Tu veux qu'un champ ne prenne qu'un jeu de valeurs connu — et qu'une faute soit attrapée à la compilation, pas en production.

type statut = ouvert | en_cours | clos.
sig(etat, [statut]).

etat(ouvert).        % ✓
etat(livre).         % ✗ refusé
fix: fait mal typé : `etat(livre)`
    position 0 : `livre` 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

Rien à activer : le typage nominal est strict par défaut, l'écart est une erreur de compilation — et le message propose la correction. Un type avec ses membres ferme le type ; un nom de type sans déclaration reste ouvert (toute valeur cohérente passe). Au passage : les identifiants s'écrivent sans accents (livre, pas livré).


Faire une somme (ou un max) par clé

Tu veux replier des valeurs groupées par une clé.

prix(o1, 100).
prix(o1, 200).
prix(o2,  50).

total(O, T) :- agg(T, sum, P, prix(O, P)).
?- total(O, T).
O = o1, T = 300
O = o2, T = 50

Remplace sum par max, min, count, product ou avg selon le besoin.


Trouver ce qui n'a pas de relation

Tu veux les éléments sans correspondance (orphelins, manquants, non couverts). C'est de la négation — stratifiée.

compte(c1, alice).  compte(c2, bob).
client(alice).

orphelin(C) :- compte(C, P), not client(P).
?- orphelin(X).
X = c2

Dériver un schéma à partir d'un modèle (TDE)

Tu décris ton modèle une fois ; tu en déduis sa forme SQL par règle.

field(user, id,    int).
field(user, email, str).

sql_type(int, integer).
sql_type(str, varchar).

column(E, F, S) :- field(E, F, T), sql_type(T, S).
?- column(user, F, S).
F = id,    S = integer
F = email, S = varchar

Et dans l'autre sens — ?- column(user, F, integer). retrouve les champs entiers. Le détail dans Comprendre.


Tester un programme

Tu veux des tests qui vivent avec le programme et tournent en CI. Un test est un fait test/2 ordinaire : holds = doit être dérivé, fails = ne doit pas l'être.

stock(vis, 80). stock(vis, 150).
plafond(200).
sous_plafond(A) :- stock(A, Q), plafond(P), Q < P.

test(vis_sous_plafond, holds) :- sous_plafond(vis).
test(pas_de_boulon, fails)    :- stock(boulon, Q).
$ fix --test inventaire.fix
suite (principal)
  ✓ pas_de_boulon
  ✓ vis_sous_plafond
─────────────────────────────────
1 suite(s) · 2 passé(s) · 0 échoué(s)

Code de sortie 0 si tout passe — branchable tel quel en CI. Les autres attentes (skip, xfail…) et le groupement par module : Référence.


Trouver le plus court chemin

Tu as des arcs pondérés, tu veux le coût minimal depuis une source : la forme qui marche est la relaxation — l'agrégat min borne la récursion à chaque pas (Bellman-Ford), au lieu d'accumuler tous les chemins avant de replier.

arc(a, b, 1).  arc(b, c, 2).  arc(a, c, 5).
source(a).

atteint(X, 0) :- source(X).
candidat(Y, D) :- atteint(X, DX), arc(X, Y, W), add(DX, W, D).
dist(Y, D) :- agg(D, min, C, candidat(Y, C)).
atteint(Y, D) :- dist(Y, D).
?- dist(c, D).
D = 3
D = 5

La plus courte distance est la plus petite (imprimée en premier) : les paliers intermédiaires de la relaxation restent visibles — Fix est cumulatif, un fait dérivé n'est jamais retiré. Pour obtenir une seule réponse par nœud, replie la clôture avec un second agrégat (une strate au-dessus) :

meilleur(Y, D) :- agg(D, min, C, dist(Y, C)).
?- meilleur(c, D).
D = 3

Cette forme termine même si le graphe a des cycles (poids ≥ 0) : le min borne chaque clé, la relaxation se stabilise. Deux garde-fous du moteur :


Déboguer une erreur de type

Le programme ne charge plus : fait mal typé. Le message cite l'objet fautif tel que tu l'as écrit, la position, et propose une correction — commence par le lire ; s'il ne suffit pas, le réflexe en trois questions :

  1. Un fait est cité ? Compare la valeur à la déclaration (type ou sig) de sa position : l'un des deux a tort — corrige la donnée, ou élargis la déclaration si c'est elle qui était trop étroite.
  2. règle mal typée sur un builtin ? Un argument de calcul n'est pas un number — remonte la variable jusqu'au littéral qui la lie.
  3. Tu ne vois pas pourquoi ? Interroge les déclarations en session : ?- kind_of(statut, K). et compare avec ce que la valeur est vraiment.

Les familles de messages et le réflexe pour chacune : Lire un refus.


Organiser en modules

Tu veux isoler une partie du programme derrière une frontière explicite.

module(infra, [impacte/2]).
    depend(web, api).
    depend(api, db).
    impacte(S, T) :- depend(S, T).
    impacte(S, T) :- impacte(S, U), impacte(U, T).
end_module.

use_module(infra).
alerte(S) :- infra::impacte(S, db).
?- alerte(X).
X = web
X = api

Seul l'exporté (impacte/2) existe de l'extérieur. L'appel qualifié marche dans un corps de règle comme en requête directe (?- infra::impacte(web, X).) — le tour complet : Les modules.


Donner une notation à un modèle (mini-DSL)

Tu veux lire et écrire tes données dans la syntaxe de ton domaine, pas en termes bruts. Déclare le type, puis habille chaque constructeur :

module(er, []).                       % le langage er : son AST ET sa syntaxe
  type list(T) = nil | cons(T, list(T)).  % liste générique (type paramétré)
  type atype   = string | int.        % l'AST ; types FERMÉS, noms OUVERTS
  type attr_t  = attr(aname, atype).  % aname OUVERT · atype fermé
  type ent_t   = ent(ename, list(attr_t)).  % ename OUVERT · liste = list(attr_t)
  ent(N, L)  := "entity" N "fields" L "end".   % puis la syntaxe
  attr(N, T) := "attr" N T.
end_module.

La même déclaration lit le texte vers l'AST et écrit l'AST vers le texte. Programme complet (deux notations, une transformation ER → UML) : examples/er-to-uml.fix — le principe : La syntaxe de surface.


Brancher Fix à un agent (MCP)

Tu veux qu'un agent (Claude Code…) charge, interroge et fasse évoluer une session Fix. Le serveur fix-mcp expose les six services du solveur en MCP — opt-in, par projet :

./install.sh mcp-on      # écrit .mcp.json pour CE projet
./install.sh mcp-off     # le retire
./install.sh mcp-restart # après réinstallation du binaire

Outils exposés : load, query, assert, retract, listing, reload — les mêmes gestes que le REPL. Les erreurs reviennent en anglais (lang(en)) et en JSON structuré (code + message + position), prêtes à consommer.


Consulter ou traduire un message d'erreur

Les messages d'erreur de Fix sont des données : un catalogue interrogeable, dans la langue de la session (français au REPL, anglais via MCP).

?- error_template(type_error, fr, T).
T = le prédicat {} attend {}, reçu {}

Pour ajouter une langue, tu assertes un gabarit — aucune recompilation :

:assert error_template(type_error, de, "Prädikat {} erwartet {}, erhielt {}").

Les trous {} sont remplis dans l'ordre des arguments de l'erreur.


→ Tu débutes ? Apprendre. La liste complète des constructions : Référence.