DatAlog — Fusion
1. Introduction
Après avoir exploré le lambda-calcul (cours 1), les types algébriques (cours 2), le filtrage par motif (cours 3), les foncteurs (cours 4), le tableur (cours 5), le mini-DOM (cours 6) et le mini-Prolog (cours 7), il est temps de tout rassembler.
DatAlog est une application OCaml qui fusionne les trois derniers cours en un seul programme cohérent :
- charger et analyser des données CSV (Cours 5 —
Table), - générer des pages HTML (Cours 6 —
Dom), - appliquer des règles logiques (Cours 7 —
Prolog).
L'objectif est de montrer comment des briques logicielles indépendantes peuvent être composées pour créer une application plus grande, démontrant ainsi la puissance de la composition modulaire en OCaml.
2. Architecture
L'application s'articule autour de cinq modules OCaml, chacun dans son propre fichier :
| Module | Fichier | Provenance | Rôle |
|---|---|---|---|
Table | table.ml | Cours 5 | Chargement CSV, stockage tabulaire, statistiques |
Dom | dom.ml | Cours 6 | Génération HTML, composants, style |
Prolog | prolog.ml | Cours 7 | Moteur d'inférence, unification, prédicats natifs |
Demo | demo.ml | Cours 8 | Démonstration avec règles prédéfinies |
Main | datalog.ml | Cours 8 | Application interactive en ligne de commande |
Le pipeline de traitement est le suivant :
- Chargement — Lecture du fichier CSV en une structure
Table.t - Conversion — Transformation des lignes en faits Prolog via
table_to_facts - Inférence — Application des règles logiques avec le moteur Prolog
- Génération — Production d'un rapport HTML avec le module DOM
3. Module Table (Cours 5)
Le module Table gère le chargement et l'analyse des données tabulaires :
type cell = | Text of string | Num of float | Empty type row = cell list type table = { headers : string list; rows : row list; }
Le chargement se fait par load_csv qui parse une chaîne CSV en une table, et load_file qui lit un fichier :
let load_csv data = let lines = String.split_on_char '\n' data in let lines = List.filter (fun l -> l <> "") lines in match lines with | [] -> None | h :: t -> let headers = String.split_on_char ',' h in let rows = List.map (fun line -> String.split_on_char ',' line |> List.map parse_cell ) t in Some { headers; rows }
Le module offre aussi des fonctions de statistiques :
let sum t name = match col_index t name with | None -> 0.0 | Some i -> t.rows |> List.map (fun r -> List.nth r i) |> List.filter_map (function Num x -> Some x | _ -> None) |> List.fold_left (+.) 0.0
4. Module DOM (Cours 6)
Le module Dom génère du HTML par concaténation de chaînes. Chaque fonction correspond à un élément HTML :
let tag name content = Printf.sprintf "<%s>%s</%s>\n" name content name let table_html headers rows = let header = tag "thead" (tag "tr" (String.concat "" (List.map (fun h -> tag "th" h) headers))) in ...
Les éléments clés du module :
tag name content— crée un élément HTML quelconquetable_html headers rows— génère un tableau HTML completsummary stats— grille de cartes pour les statistiquesresult_block name results— bloc de résultats d'inférencereport_page ~style title body— page HTML complète avec style intégré
5. Module Prolog (Cours 7)
Le module Prolog implémente un moteur d'inférence minimal avec unification :
type term = | Atom of string | Var of string | Struct of string * term list type clause = { head : term; body : term list; } type subst = (string * term) list
La fonction d'unification implémente l'algorithme classique :
let rec unify t1 t2 s = let t1 = apply s t1 in let t2 = apply s t2 in match (t1, t2) with | Atom a, Atom b when a = b -> s | Var x, t | t, Var x -> if t = Var x then s else if occurs x t then raise UnifyError else (x, t) :: s | Struct (f1, a1), Struct (f2, a2) when f1 = f2 && List.length a1 = List.length a2 -> List.fold_left2 unify s a1 a2 | _ -> raise UnifyError
Le pont entre les deux mondes est assuré par table_to_facts, qui convertit chaque cellule
de la table en un fait Prolog cell(id, colonne, valeur) :
let table_to_facts t = let col_atoms = List.map Table.atom_of_header t.headers in let row_facts i r = let id = Atom (string_of_int i) in List.map2 (fun col val -> Struct ("cell", [id; Atom col; Atom (Table.show_cell val)])) ) col_atoms r in List.flatten (List.mapi row_facts t.rows) |> List.map (fun h -> { head = h; body = [] })
6. Démonstration
Un jeu de données exemple est fourni avec l'application :
Client,Ville,Montant,NbAchats,Age Alice,Paris,1500,12,32 Bob,Lyon,800,5,45 Charlie,Paris,2500,20,28 Diana,Marseille,300,2,55 Etienne,Lyon,4200,15,38 Fanny,Paris,950,8,29 Georges,Marseille,1800,3,62 Helene,Lyon,5000,25,41
Les règles Prolog suivantes sont appliquées :
% Clients fidèles (plus de 10 achats) fidele(X) :- cell(X, nbachats, N), gt(N, 10). % Gros clients (montant total > 2000) gros(X) :- cell(X, montant, M), gt(M, 2000). % Premium = fidèle ET gros premium(X) :- fidele(X), gros(X). % Parisiens parisien(X) :- cell(X, ville, "Paris").
Le rapport HTML généré combine toutes ces informations dans une page structurée :
let report = Dom.report_page ~style:demo_style "DatAlog — Rapport de démonstration" (data_section ^ stats_section ^ rules_section ^ results_section ^ summary_block)
7. Résultats
Voici les résultats obtenus avec les données et règles ci-dessus :
| Requête | Description | Résultat |
|---|---|---|
fidele(X) | Clients fidèles (≥10 achats) | Alice, Charlie, Etienne, Helene |
gros(X) | Gros clients (>2000€) | Charlie, Etienne, Helene |
premium(X) | Clients premium | Charlie, Etienne, Helene |
parisien(X) | Clients parisiens | Alice, Charlie, Fanny |
senior(X) | Seniors (>60 ans) | Georges |
reco_remise(X) | Remise premium parisien | Charlie |
reco_fidelite(X) | Fidélité premium lyonnais | Etienne, Helene |
bon_client(X) | Bon client (>1000€) | Alice, Charlie, Etienne, Georges, Helene |
quadra(X) | Plus de 40 ans | Bob, Diana, Georges, Helene |
Table fournit les données, Prolog applique les règles, et Dom génère
le rapport. Aucun de ces modules n'a besoin de connaître les autres pour fonctionner.
8. Extensions possibles
Voici quelques pistes pour enrichir DatAlog :
- Prédicats natifs supplémentaires —
sum,avg,countpour des agrégations dans les règles - Jointures — Charger plusieurs CSV et écrire des règles qui les relient
- Négation — Ajouter
not/1pour filtrer les résultats - Interface web — Utiliser Dream ou Eliom pour exposer DatAlog via un navigateur
- Export PDF — Convertir le rapport HTML en PDF avec un outil externe
- Cache de déduction — Mémoïsation pour accélérer les requêtes récurrentes
9. Conclusion
DatAlog illustre comment des composants logiciels indépendants, développés séparément, peuvent être assemblés pour créer une application plus grande et plus utile. C'est l'essence même de la composition modulaire que OCaml facilite grâce à son système de modules, son typage fort, et sa sûreté d'exécution.
Pour tester l'application :
# Lancer la démonstration cd cours8 make demo # Lancer le mode interactif make run # Analyse rapide d'un fichier CSV ./datalog data/ventes.csv # Analyse avec règles personnalisées ./datalog data/ventes.csv data/regles.pl