Back to flin
flin

Le modèle temporel complet : ce qu'aucun autre langage n'offre

Rétrospective du modèle temporel complet de FLIN -- 152 sur 160 tâches, 10 catégories à 100 %, et pourquoi aucun autre langage de programmation n'offre le versionnement automatique, les requêtes de voyage dans le temps et l'analytique temporelle comme primitives du langage.

Thales & Claude | March 30, 2026 14 min flin
EN/ FR/ ES
flintemporalcompleteretrospectiveunique

Le 2 janvier 2026, la session 012 a ajouté la structure EntityVersion à la machine virtuelle de FLIN. Cinquante minutes. Dix tests. Le premier pas hésitant vers un modèle de données temporel.

Le 7 janvier, session 088, le modèle temporel comptait cent cinquante-deux sur cent soixante tâches complètes -- quatre-vingt-quinze pour cent. Dix sur onze catégories à cent pour cent. Plus de mille tests passant. Un système complet et prêt pour la production pour le versionnement automatique, les requêtes de voyage dans le temps, la suppression douce et définitive, la restauration, le filtrage temporel, l'analytique de comparaison, les métadonnées de version, l'arithmétique temporelle et le stockage bitemporel.

Cet article est la rétrospective. Pas le code, pas les bugs, pas les détails d'implémentation -- ils sont couverts dans les neuf articles précédents. C'est une question de ce que nous avons construit, pourquoi c'est important, et ce que cela signifie pour l'avenir du développement d'applications.

Le bilan final

CatégorieTâchesStatut
TEMP-1 : Suppression douce de base5/5Complet
TEMP-2 : Accès temporel (@)18/18Complet
TEMP-3 : Mots-clés temporels14/14Complet
TEMP-4 : Requêtes d'historique22/22Complet
TEMP-5 : Arithmétique temporelle12/12Complet
TEMP-6 : Comparaisons temporelles10/10Complet
TEMP-7 : Filtres temporels15/15Complet
TEMP-8 : Suppression/Restauration définitive12/12Complet
TEMP-9 : Politiques de rétention0/10Différé
TEMP-10 : Stockage bitemporel20/20Complet
TEMP-11 : Tests d'intégration27/27Complet
Total152/16095 %

La seule catégorie incomplète est TEMP-9 (Politiques de rétention) -- une fonctionnalité d'optimisation pour le nettoyage automatique des anciennes versions. Les dix tâches restantes sont « agréables à avoir » pour les déploiements en production mais n'affectent pas les capacités temporelles fondamentales. Chaque fonctionnalité du modèle temporel est implémentée, testée et fonctionnelle.

Ce que le modèle temporel de FLIN inclut

Un inventaire complet des capacités temporelles, construites au fil de vingt-six sessions :

Versionnement automatique Chaque entité suit son historique complet automatiquement. Aucune configuration, aucune annotation, aucun opt-in. Chaque save crée une nouvelle version immuable avec un numéro de version séquentiel et un horodatage.

flinentity Product { name: text, price: number }

product = Product { name: "Widget", price: 10 }
save product          // Version 1
product.price = 15
save product          // Version 2
product.price = 20
save product          // Version 3

Requêtes de voyage dans le temps (opérateur @) Trois formes d'accès ponctuel : numéros de version relatifs, chaînes de date absolues et mots-clés temporels.

flinproduct @ -1                   // Version précédente
product @ "2024-06-15"         // État au 15 juin 2024
product @ yesterday            // État d'hier
product @ last_month           // État il y a 30 jours

Propriété History Chronologie complète des versions pour toute entité, itérable dans les templates.

flin{for ver in product.history}
    <p>v{ver.version_number} : ${ver.price} le {ver.created_at}</p>
{/for}

Filtrage et tri temporels Opérations de type requête sur les listes d'historique.

flinexpensive = product.history.where_field("price", ">", 15)
chronological = product.history.order_by("created_at", "asc")
combined = product.history
    .where_field("price", ">=", 10)
    .order_by("price", "desc")

Suppression douce, suppression définitive et restauration Modèle de suppression à trois niveaux avec conformité RGPD intégrée.

flindelete product        // Douce : historique préservé, restaurable
restore(product)      // Annuler la suppression douce
destroy product       // Définitive : effacement permanent, conforme RGPD

Fonctions de comparaison temporelle Six fonctions natives pour la détection de changements et l'analytique.

flinfield_changed(product, "price")              // true/false
calculate_delta(old_price, new_price)         // différence numérique
percent_change(old_price, new_price)          // pourcentage
changed_from(product, "price", 15.00)         // était-ce 15 avant ?
field_history(product, "price")               // [10, 15, 20]

Métadonnées de version Chaque entité expose des métadonnées de cycle de vie comme propriétés de première classe.

flinproduct.id                // Identifiant de l'entité
product.version_number    // Version actuelle
product.created_at        // Horodatage de création
product.updated_at        // Dernière modification
product.deleted_at        // Horodatage de suppression (optionnel)

Arithmétique temporelle Littéraux de durée et opérations temporelles type-safe.

flindeadline = now + 7.days
reminder = deadline - 1.hours
time_left = deadline - now
cache_ttl = 5.minutes

Stockage bitemporel Chaque version possède des horodatages valid_from et valid_to, permettant des requêtes de plage efficaces et des consultations ponctuelles.

Persistance de l'historique L'historique des versions persiste sur disque et survit aux redémarrages de l'application. Le stockage basé sur WAL assure la durabilité sans sacrifier les performances d'écriture.

Ce qu'aucun autre langage n'offre

Nous avons examiné chaque langage de programmation et framework majeur avant et pendant le développement de FLIN. Aucun n'offre ce que le modèle temporel de FLIN fournit comme fonctionnalité au niveau du langage.

Bases de données SQL : tables temporelles PostgreSQL, MariaDB et SQL Server supportent les tables temporelles (également appelées tables versionnées par le système). Elles suivent l'historique des lignes automatiquement, similaire à l'approche de FLIN. Mais c'est une fonctionnalité de base de données, pas une fonctionnalité du langage. Le développeur doit :

  1. Créer la table temporelle avec une syntaxe spécifique.
  2. Écrire des requêtes SQL avec des clauses FOR SYSTEM_TIME AS OF.
  3. Gérer les colonnes temporelles dans le code applicatif.
  4. Construire des composants UI pour afficher l'historique.
  5. Implémenter la suppression douce, la suppression définitive et la restauration séparément.

FLIN intègre les capacités temporelles dans le langage lui-même. L'opérateur @, la propriété .history et les fonctions de comparaison ne sont pas des extensions SQL -- ce sont de la syntaxe native.

Frameworks d'Event Sourcing L'event sourcing (Axon, EventStore, Marten) stocke les événements et reconstruit l'état en les rejouant. C'est puissant pour certains domaines mais fondamentalement différent de l'approche de FLIN :

  • L'event sourcing nécessite de définir des événements, des gestionnaires, des projections et des sagas. FLIN ne nécessite rien -- le suivi temporel est automatique.
  • Interroger un état passé en event sourcing signifie rejouer les événements jusqu'à ce point. En FLIN, c'est une seule consultation indexée.
  • L'event sourcing est un pattern, pas une fonctionnalité du langage. Il nécessite l'adoption d'un framework et un engagement architectural. Le modèle temporel de FLIN fonctionne sur chaque entité, toujours.

Systèmes de bases de données immuables Datomic et XTDB (anciennement Crux) sont des bases de données immuables qui stockent tous les états historiques. Ils sont la correspondance conceptuelle la plus proche du modèle temporel de FLIN. Mais ce sont des systèmes de bases de données, pas des langages de programmation. Le développeur écrit toujours du code applicatif en Clojure (pour Datomic) ou dans tout langage JVM (pour XTDB), avec des requêtes temporelles exprimées comme des requêtes de base de données plutôt que des expressions du langage.

FLIN fait de product @ yesterday une expression du langage, vérifiée au moment de la compilation, dont le résultat est utilisable directement dans les templates et la logique métier. Pas de frontière de langage de requête. Pas de couche de traduction ORM.

Bibliothèques de pistes d'audit Chaque framework a ses gems, packages et plugins de pistes d'audit (PaperTrail pour Rails, django-simple-history pour Python, Javers pour Java). Ceux-ci ajoutent le versionnement à la couche applicative. Ils sont utiles mais limités :

  • Ils nécessitent une configuration par modèle.
  • Ils stockent des diffs ou des snapshots dans des tables séparées.
  • Ils fournissent une fonctionnalité d'audit en lecture seule, pas un voyage dans le temps à usage général.
  • Ils ne s'intègrent pas avec le système de types, le compilateur ou la couche de vue.

Le modèle temporel de FLIN n'est pas un ajout. C'est le modèle de données.

Le parcours en chiffres

MétriqueValeur
Sessions26 (012 à 088)
Jours calendaires6 (2-7 janvier 2026)
Tâches complétées152 sur 160
Catégories à 100 %10 sur 11
Tests d'intégration36 (tous passant)
Tests de bibliothèque1 011 (tous passant)
Lignes de code temporel~2 500
Bugs trouvés et corrigés8 majeurs, des dizaines de mineurs
Nouveaux opcodes de VM4 (AtVersion, AtTime, AtDate, History) + 2 (ListFilterField, ListOrderBy)
Nouvelles fonctions natives6 (fonctions de comparaison)
Nouveaux noeuds AST2 (Expr::Temporal, Expr::Duration)
Nouveaux types2 (FlinType::Duration, enum DurationUnit)

Six jours. Vingt-six sessions. Un modèle de données temporel complet tissé dans chaque couche d'un langage de programmation, du lexer à la base de données.

L'architecture qui l'a rendu possible

L'architecture en couches de FLIN -- lexer, parseur, vérificateur de types, générateur de code, VM, base de données -- était à la fois le défi et le catalyseur du modèle temporel.

Le défi : les fonctionnalités temporelles nécessitaient des changements à chaque couche. L'opérateur @ avait besoin d'un token dans le lexer, d'une règle d'analyse dans le parseur, d'une logique de vérification de types, d'une émission de bytecode, d'une exécution dans la VM et de requêtes de base de données. Un bug dans n'importe quelle couche produisait des symptômes confus dans une autre.

Le catalyseur : la séparation nette entre les couches signifiait que chaque pièce pouvait être construite et testée indépendamment. Les tokens du lexer étaient définis avant les règles du parseur. Les règles du parseur étaient définies avant la logique du vérificateur de types. La logique du vérificateur de types était définie avant le générateur de code. Cette approche en couches permettait le développement parallèle et la progression incrémentale -- la session 012 a construit la couche VM alors que le parseur et le vérificateur de types n'avaient pas encore rattrapé.

L'architecture signifiait aussi que les fonctionnalités temporelles se composaient naturellement avec les fonctionnalités existantes du langage. L'opérateur @ retourne une entité, donc il fonctionne avec l'accès aux champs, les conditionnels, les boucles et les appels de fonction. La propriété .history retourne une liste, donc elle fonctionne avec .where_field(), .order_by(), .count et les boucles {for}. Les littéraux de durée sont des entiers à l'exécution, donc ils fonctionnent avec les opcodes arithmétiques existants.

Aucun cas spécial. Aucun « mode temporel ». Juste des expressions qui retournent des valeurs, composées via les mêmes mécanismes que toute autre expression dans le langage.

Ce que nous ferions différemment

Commencer par les tests d'intégration

Le plus gros gouffre de temps a été le marathon de débogage des sessions 068-076. Nous avons construit les fonctionnalités temporelles couche par couche, testé chaque couche isolément, et supposé que le flux de bout en bout fonctionnait. Ce n'était pas le cas. Le stub AtTime était l'exemple le plus embarrassant : une fonctionnalité qui compilait, s'exécutait et retournait une valeur -- juste la mauvaise valeur.

Si nous avions écrit des tests d'intégration de bout en bout dès le départ -- code source FLIN en entrée, sortie HTML en sortie -- nous aurions détecté le stub immédiatement. Le modèle temporel aurait été complet en moitié moins de sessions.

Suivre au niveau des tâches, pas au niveau des catégories

Notre document de suivi enregistrait les pourcentages au niveau des catégories mais pas l'achèvement des tâches individuelles. Cela rendait impossible de savoir si « TEMP-2 à 89 % » signifiait « seize tâches faites, deux restantes » ou « la plupart des choses fonctionnent mais on ne sait pas exactement lesquelles ne fonctionnent pas ». Le suivi au niveau des tâches aurait empêché la découverte de six tâches à la session 079.

Définir la sémantique avant l'implémentation

Le bug de duplication de l'historique (session 075) existait parce que nous n'avions jamais explicitement défini qui était responsable d'inclure la version actuelle dans le résultat .history. Était-ce la base de données ? La VM ? Les deux ? L'ambiguïté a conduit les deux à le faire, causant la duplication.

Une spécification sémantique d'un paragraphe -- « la base de données stocke uniquement les versions passées ; la VM ajoute la version actuelle lors de la construction des résultats .history » -- aurait empêché le bug entièrement.

Les cinq pour cent restants

TEMP-9 (Politiques de rétention) représente les dix tâches restantes. Ce sont des fonctionnalités d'optimisation pour les déploiements en production :

flin// Syntaxe prévue
entity Metric {
    value: number

    @retention(90.days)          // Conserver 90 jours d'historique
}

entity Metric {
    value: number

    @compact(after: 30.days, to: "daily")  // Compacter les anciennes données
}

Les politiques de rétention élaguent automatiquement les anciennes versions pour gérer la croissance du stockage. Les stratégies de compactage fusionnent les données historiques granulaires en résumés plus grossiers (quotidien, hebdomadaire, mensuel). Les deux sont importants pour les systèmes de production long terme avec des entités à forte écriture.

Nous avons volontairement différé ces fonctionnalités. Le modèle temporel à quatre-vingt-quinze pour cent est prêt pour la production pour les applications que FLIN cible. Les politiques de rétention sont une optimisation pour l'échelle, pas un prérequis pour la fonctionnalité. Quand une application FLIN commencera à générer suffisamment de données historiques pour justifier un nettoyage automatique, les primitives pour implémenter la rétention sont déjà en place (les méthodes prune_versions_before et prune_versions_keep_last de la session 012).

Ce que cela signifie pour les développeurs

Le modèle temporel de FLIN change l'économie du développement d'applications. Des fonctionnalités qui nécessitaient auparavant une infrastructure dédiée -- pistes d'audit, fonctionnalité d'annulation, rapports de conformité, historique des prix, journaux d'activité, récupération de données -- sont maintenant gratuites. Pas « gratuites au sens de peu coûteuses ». Gratuites au sens de zéro code supplémentaire.

Un développeur construisant une plateforme e-commerce en FLIN obtient l'historique des prix, les pistes d'audit des commandes et les journaux d'activité des utilisateurs sans écrire une seule ligne de code de suivi. Un développeur construisant un système de gestion documentaire obtient l'historique des versions, la détection des changements et le rollback sans framework d'event sourcing. Un développeur construisant une application réglementée obtient des pistes d'audit de niveau conformité sans base de données d'audit séparée.

Ce n'est pas une amélioration marginale. C'est un changement de catégorie. Le modèle temporel élimine toute une classe d'infrastructure applicative. Il fait pour l'historique des données ce que le ramasse-miettes a fait pour la gestion de la mémoire : retirer la charge du développeur et la gérer automatiquement, correctement et efficacement.

« E flin nu » -- Il se souvient des choses

L'expression « E flin nu » vient du fon, une langue parlée au Bénin. Elle signifie « il se souvient des choses ». Elle est devenue la devise du modèle temporel de FLIN pendant le développement, un rappel de ce que nous construisions : un langage où les données se souviennent.

Pas des données qui peuvent être configurées pour se souvenir. Pas des données qui se souviennent si vous installez le bon plugin. Pas des données qui se souviennent si vous configurez des triggers et des tables d'historique et du middleware d'audit.

Des données qui se souviennent parce que c'est ce que font les données en FLIN. Toujours. Automatiquement. Du premier save au dernier destroy.

Cent cinquante-deux tâches. Dix catégories. Six jours. Et un langage de programmation où chaque entité se souvient de tout.


Ceci est la partie 10 de la série « Comment nous avons construit FLIN » sur le modèle temporel. La série complète documente la conception, l'implémentation, le débogage et l'achèvement du modèle de données temporel de FLIN -- une fonctionnalité qu'aucun autre langage de programmation n'offre comme primitive du langage.

Navigation dans la série : - [046] Chaque entité se souvient de tout : le modèle temporel - [047] Historique des versions et requêtes de voyage dans le temps - [048] Intégration temporelle : des bugs à 100 % de couverture de tests - [049] Destroy et Restore : la suppression douce bien faite - [050] Filtrage et tri temporels - [051] Fonctions de comparaison temporelle - [052] Accès aux métadonnées de version - [053] Arithmétique temporelle : ajouter des jours, comparer des dates - [054] Précision du suivi et validation - [055] Le modèle temporel complet : ce qu'aucun autre langage n'offre (vous êtes ici)

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles