← Cours 7 : Prolog Accueil →

DatAlog — Fusion

Analyse de données avec règles logiques et rapports HTML

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 :

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.

Composition logicielle — L'idée que des programmes complexes peuvent être construits par assemblage de composants plus simples est au cœur du génie logiciel. OCaml, avec son système de modules, est particulièrement adapté à cette approche.

2. Architecture

L'application s'articule autour de cinq modules OCaml, chacun dans son propre fichier :

ModuleFichierProvenanceRôle
Tabletable.mlCours 5Chargement CSV, stockage tabulaire, statistiques
Domdom.mlCours 6Génération HTML, composants, style
Prologprolog.mlCours 7Moteur d'inférence, unification, prédicats natifs
Demodemo.mlCours 8Démonstration avec règles prédéfinies
Maindatalog.mlCours 8Application interactive en ligne de commande

Le pipeline de traitement est le suivant :

  1. Chargement — Lecture du fichier CSV en une structure Table.t
  2. Conversion — Transformation des lignes en faits Prolog via table_to_facts
  3. Inférence — Application des règles logiques avec le moteur Prolog
  4. 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 :

OCaml
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 :

OCaml
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 :

OCaml
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 :

OCaml
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 :

Mini-DOM — Ce module illustre une approche minimaliste de génération HTML. Dans la vraie vie, on utiliserait plutôt TyXML ou Dream pour garantir la validité du HTML produit.

5. Module Prolog (Cours 7)

Le module Prolog implémente un moteur d'inférence minimal avec unification :

OCaml
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 :

OCaml
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) :

OCaml
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 :

CSV
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 :

Prolog
% 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 :

OCaml
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êteDescriptionRésultat
fidele(X)Clients fidèles (≥10 achats)Alice, Charlie, Etienne, Helene
gros(X)Gros clients (>2000€)Charlie, Etienne, Helene
premium(X)Clients premiumCharlie, Etienne, Helene
parisien(X)Clients parisiensAlice, Charlie, Fanny
senior(X)Seniors (>60 ans)Georges
reco_remise(X)Remise premium parisienCharlie
reco_fidelite(X)Fidélité premium lyonnaisEtienne, Helene
bon_client(X)Bon client (>1000€)Alice, Charlie, Etienne, Georges, Helene
quadra(X)Plus de 40 ansBob, Diana, Georges, Helene
magie de la composition — Ces résultats sont obtenus par l'interaction des trois modules : 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 :

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.

La morale de cette série : le lambda-calcul et OCaml, c'est comme les Legos. Avec les mêmes briques, tu peux construire une calculette, un tableur, un moteur d'inférence… ou les trois à la fois !

Pour tester l'application :

Shell
# 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
    

© 2026 — laurent.thiry@uha.fr