Introduction
Bienvenue sur le livre d’introduction au langage de programmation dédié Catala !
Le langage dédié Catala vous permet d’annoter une spécification juridique avec du code exécutable et déployable de manière efficace et fiable. Automatiser l’application d’une loi ou d’un règlement par un programme informatique n’est pas neutre pour l’État de droit ainsi que pour les droits des usagers. Veuillez faire preuve de prudence lorsque vous envisagez d’automatiser autre chose que le calcul d’impôts ou de prestations sociales.
Contrairement à un moteur de règles, Catala est un langage de programmation à part entière doté d’abstractions modulaires inspirées de la programmation fonctionnelle. Mais surtout, il permet une véritable collaboration entre juristes et informaticiens en permettant la pratique de la programmation en binôme via la programmation littéraire pour l’écriture et la mise à jour des programmes et de leurs spécifications juridiques.
À qui s’adresse Catala
Catala est un langage de programmation dédié (DSL), son utilisation cible donc une classe spécifique d’utilisateurs et d’organisations.
Équipes pluridisciplinaires
Catala est conçu pour être utilisé par des équipes mêlant expertises juridique et informatique. En effet, automatiser l’application d’une loi ou d’un règlement nécessite de combler le fossé de l’ambiguïté entre le texte juridique et un programme informatique qui doit savoir quoi faire dans toutes les situations possibles. Combler cette ambiguïté est l’une des missions des juristes, formés à l’interprétation des textes juridiques et à la recherche juridique pour aboutir à une décision justifiée dans le cadre de l’État de droit.
Si les juristes doivent décider de ce que fait le programme, ils ne sont pas formés aux concepts et abstractions de la programmation, ni à l’écriture de milliers de lignes de code de manière propre et maintenable. Ainsi, le rôle de l’informaticien est de s’assurer que le programme automatisant l’application d’une loi ou d’un règlement est propre, concis, et doté d’abstractions correctement définies qui évitent la duplication de code et facilitent la maintenance et les changements futurs. De plus, l’informaticien est également responsable du déploiement du programme dans un système informatique plus vaste qui pourrait briser ses hypothèses ou mettre à l’épreuve ses performances.
En réutilisant les techniques et outils existants du génie logiciel, tout en présentant des concepts accessibles aux juristes en surface, Catala permet aux juristes et aux informaticiens de collaborer de manière véritablement agile, dépassant la séparation entre les équipes de développement et d’assurance qualité qui ne communiquent que via des cas de test et des documents de spécification.
Agences gouvernementales et organismes de service public
Automatiser l’application d’une loi ou d’un règlement par un programme informatique n’est pas une mince affaire. Pour s’assurer que tous sont traités équitablement en vertu de l’État de droit, votre programme doit prendre en compte de manière exhaustive chaque situation décrite par la loi ou le règlement. Bien qu’automatiser uniquement la situation la plus simple correspondant à une majorité d’utilisateurs puisse être acceptable dans certaines situations, cela ne peut constituer la base d’un service public prêt pour la production. En effet, créer une différence entre un “parcours simple” automatisé et un traitement non automatisé des situations complexes creuse la fracture numérique et accroît la confusion tant pour les usagers que pour les agents.
Catala brille lorsque l’objectif est d’automatiser de manière exhaustive et fiable une loi ou un règlement donné, et de maintenir cette automatisation dans le temps. Le langage et l’outillage vous aideront à gérer la complexité croissante, la maintenance face aux changements juridiques, les cas limites et le déploiement en production au sein d’un système informatique existant. Si vous recherchez un outil pour créer un chatbot d’aide aux utilisateurs de premier niveau qui ne répond qu’aux questions de base, ou un modèle simplifié d’une loi pour une étude économique, vous devriez envisager d’utiliser d’autres outils. En revanche, si vous faites partie d’une organisation responsable de l’exécution d’un service public comme les impôts ou les prestations sociales, vous devriez avoir les moyens de concevoir correctement le programme informatique responsable de l’automatisation, et utiliser Catala pour vous y aider.
Parce que Catala se compile vers des langages de programmation grand public comme Python ou C, il produit des bibliothèques de code source portables qui sont intégrables dans pratiquement n’importe quel système informatique, ancien ou moderne. Plus particulièrement, les programmes Catala peuvent être déployés derrière une API Web ou embarqués dans une application de bureau, permettant d’écrire une fois pour utiliser partout (“write once, use anywhere”). Ainsi, Catala est particulièrement adapté aux agences gouvernementales historiques qui exploitent leurs systèmes informatiques depuis des décennies et continueront de le faire pour les décennies à venir.
Étudiants et universitaires en informatique et/ou en droit
La formalisation du droit, terme plus général pour désigner la traduction du droit en code informatique exécutable, fait l’objet d’une attention académique depuis très longtemps. Le domaine “IA & Droit” a historiquement été la communauté faîtière pour cette ligne de travail, avec trois tendances séquentielles : la programmation logique, les ontologies, et maintenant l’apprentissage automatique et en particulier le traitement du langage naturel. Bien que ces tendances aient mis au jour des résultats importants pour la formalisation du droit, de grandes questions de recherche demeurent. Comment modéliser des dispositions juridiques complexes couvrant de vastes corpus ? La formalisation du droit peut-elle être automatisée ? La rédaction juridique peut-elle être augmentée technologiquement ? Comment détecter les incohérences ou les failles par analyse statique ou dynamique ?
Avec de fortes racines dans la communauté de recherche, son engagement envers l’open source, sa sémantique formalisée et son compilateur extensible, Catala en tant que langage de programmation est une opportunité pour l’apprentissage des étudiants et les projets de recherche. Alors que les enseignants et les étudiants peuvent utiliser Catala pour explorer de manière pratique le droit fiscal et social, les chercheurs peuvent utiliser les programmes Catala comme jeux de données ou programmer une nouvelle analyse qui peut être facilement déployée auprès des utilisateurs de Catala.
Si vous êtes étudiant, professeur, ou qui que ce soit d’autre d’ailleurs, vous êtes invité à utiliser Catala gratuitement et à y contribuer en signalant des problèmes, en proposant des “pull requests”, ou en développant des plugins et des outils autour du langage.
À qui s’adresse ce livre
Ce livre s’adresse principalement aux programmeurs qui souhaitent apprendre Catala, mettre en place un projet l’utilisant pour traduire un texte juridique en code exécutable, et être guidés tout au long du processus. Le livre suppose une connaissance de base des idiomes de la programmation fonctionnelle et, plus généralement, une expérience en génie logiciel avec un autre langage de programmation grand public.
Si vous, programmeur, travaillez dans une équipe pluridisciplinaire avec un ou plusieurs juristes, c’est à vous de leur expliquer ce qu’est Catala et comment travailler avec. Ce guide couvrira donc également divers sujets autour de la collaboration entre juristes et programmeurs.
Si vous êtes juriste et tombez sur ce livre, vous êtes également invité à le lire, bien que certaines parties ne soient pas pertinentes pour vous. Vous pourriez consulter à la place des articles introductifs qui posent le contexte autour du code informatique, de la traduction du droit en code informatique et présentent les spécificités de Catala :
Publications sur Catala et le codage du droit accessibles aux juristes
Publications sur Catala et le codage du droit accessibles aux juristes
- James Grimmelmann. 2023. “The Structure and Legal Interpretation of Computer Programs”. Journal of Cross-Disciplinary Research in Computational Law 1 (3).
- Liane Huttner, Denis Merigoux. 2022. “Catala: Moving Towards the Future of Legal Expert Systems”. Artificial Intelligence and Law.
- Sarah B. Lawsky. 2022. “Coding the Code: Catala and Computationally Accessible Tax Law”. 75 SMU Law Review 535.
Comment utiliser ce livre
Le livre est organisé en deux parties : le guide utilisateur et le guide de référence. Alors que le guide utilisateur est destiné à être lu de manière linéaire et vise les nouveaux utilisateurs, le guide de référence est le point de chute pour vérifier des éléments concernant le langage et l’outillage Catala au fur et à mesure de votre utilisation, que vous soyez un utilisateur débutant ou expérimenté.
Le guide utilisateur commence par le Chapitre 1 et l’équivalent Catala du programme “Hello, world!”. Le Chapitre 2 explique les concepts fondamentaux de Catala avec un tutoriel pratique centré sur l’automatisation d’un système fiscal fictif de base. Passant aux choses sérieuses, le Chapitre 3 plonge dans la mise en place d’un projet Catala réel avec contrôle de version, suivi des évolutions juridiques, tests, intégration continue, déploiement automatisé, etc. Enfin, le Chapitre 4 accélère votre apprentissage en répondant à (presque) toutes les questions sur lesquelles vous tomberez normalement en codant la loi avec Catala.
Dans le guide de référence, le Chapitre 5 détaille toutes les
fonctionnalités disponibles dans le langage Catala, tandis que le Chapitre
6 se concentre sur les interfaces en ligne de commande et les
fonctionnalités de l’outil avec lequel vous travaillerez : clerk.
Les fichiers sources à partir desquels ce livre est généré se trouvent sur GitHub. Bien que le contenu de ce livre soit censé correspondre à la dernière version de Catala, certaines incohérences peuvent apparaître. Si vous en repérez une, ou si vous avez des commentaires ou des suggestions sur le livre, n’hésitez pas à ouvrir un ticket !
Prise en main
Bienvenue dans le chapitre de prise en main ! Ce chapitre a pour but de vous préparer au début de votre voyage dans l’univers de Catala. Tout d’abord, vous devrez installer Catala sur votre machine, car Catala n’est pas un logiciel en tant que service (SaaS) mais bien une chaîne d’outils de langage de programmation à part entière. Ensuite, vous devrez vérifier que tout fonctionne correctement en créant et exécutant votre premier programme Catala, équivalent du célèbre “Hello, world!”.
Vous découvrirez :
- le système de construction de Catala,
clerk; - l’outillage de l’IDE Catala (plugin de langage et outil de mise en forme).
Installer Catala sur votre machine
Actuellement, Catala n’est disponible que via la compilation depuis les sources. L’équipe Catala prévoit de distribuer Catala sous forme binaire à l’avenir, ce qui facilitera grandement le processus d’installation.
Catala est un langage de programmation principalement conçu pour être installé sur votre machine et exécuté localement dans votre environnement de développement préféré. Concrètement, Catala est composé de plusieurs exécutables qui forment ensemble l’outillage du langage de programmation :
- le compilateur Catala
catala, accompagné du système de constructionclerk; - le serveur de protocole de langage (LSP) Catala
catala-lsp; - l’outil de formatage automatique Catala
catala-format; - le plugin Catala pour votre éditeur de texte ou IDE.
En coulisses, la plupart de ces exécutables sont produits à l’aide de la chaîne
d’outils logicielle OCaml, le processus d’installation
commence donc avec opam, le gestionnaire de paquets et système de construction
pour OCaml.
Les instructions d’installation supposent toutes une maîtrise de la ligne de commande et des connaissances de base sur le système de fichiers et le processus général de construction d’exécutables à partir des sources à l’aide d’un gestionnaire de paquets.
Si votre installation a échoué alors que vous suiviez le guide d’installation, veuillez ouvrir un ticket ou lancer une discussion sur le chat en ligne de la communauté Catala.
Dans votre ticket ou message, veuillez indiquer :
- votre plateforme et système d’exploitation ;
- un journal des commandes que vous avez exécutées avec leur sortie en ligne de commande.
Les instructions d’installation diffèrent selon que vous êtes sur un système compatible Unix (Linux, MacOS, sous-système Windows pour Linux), ou sur Windows classique. Veuillez choisir le guide approprié à votre situation.
Pendant les étapes d’installation, plusieurs invites peuvent apparaître. Choisir
l’option par défaut (en appuyant sur Entrée à chaque fois) ou répondre oui (en
tapant y puis Entrée) est suffisant.
Linux/Mac/WSL
Sauf indication contraire, l’installation de l’outillage Catala s’effectue sur un terminal en ligne de commande classique.
Pour les utilisateurs de WSL2
Pour les utilisateurs de WSL2
Nous supposons que toutes les commandes données sont invoquées dans un
environnement WSL2. WSL2 peut être installé en exécutant > wsl --install
dans un PowerShell Windows (Touche Windows + R puis tapez powershell dans
l’invite). WSL2 installera par défaut une machine virtuelle de type Ubuntu.
Ensuite, vous pouvez entrer dans l’environnement WSL2 et la machine virtuelle
en tapant wsl dans le PowerShell.
Obtenir opam
Installez la dernière version d’opam (version >= 2.2), via les
instructions d’installation officielles
que nous répétons ici pour plus de commodité.
Avec aptitude (distributions linux de type debian) :
$ sudo apt update
$ sudo apt install opam
Sans aptitude :
$ bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh)"
À ce stade, opam devrait être initialisé sur votre machine. Mais ce n’est pas
fini, car opam doit créer un switch avec une version spécifique d’OCaml, où
tous les paquets que nous installerons plus tard seront compilés et stockés.
Pour initialiser opam et créer ce premier switch, entrez ce qui suit :
$ opam init -c 4.14.2
$ eval $(opam env)
Alors vous pouvez conserver votre switch actuel pour installer Catala, ou en créer un nouveau spécifique avec
$ opam switch create 4.14.2
Catala supporte les versions d’OCaml de 4.14.0 jusqu’à 5.4.X.
Obtenir Catala
Exécutez les commandes suivantes pour installer la dernière version de Catala via opam :
$ opam update && opam install catala.1.0.0
Une fois terminé, le système de construction Catala devrait être installé. Vous
devriez pouvoir appeler avec succès $ clerk --version dans votre terminal. Si
ce n’est pas le cas, essayez d’invoquer $ eval $(opam env) avant.
Mettre à jour Catala
À tout moment, vous pouvez récupérer la dernière version de Catala en utilisant ces commandes simples :
$ opam update
$ opam upgrade catala
Cette méthode fonctionne également pour les autres paquets opam présentés
ci-dessous : remplacez simplement upgrade catala par upgrade catala-lsp, etc.
À la pointe : obtenir les versions de développement
À la pointe : obtenir les versions de développement
Si vous vous sentez l’âme aventurière, vous pouvez récupérer la dernière version
de développement de l’outillage Catala au lieu des versions stables éprouvées.
Pour ce faire, vous devez épingler
les paquets opam catala, catala-lsp et catala-format dans votre switch
vers une version dev pointant vers les dépôts git de l’outillage Catala. Voici
la commande à invoquer :
$ opam pin catala.dev --dev-repo
$ opam pin catala-lsp.dev --dev-repo
$ opam pin catala-format.dev --dev-repo
Si vous êtes fatigué d’être à la pointe et souhaitez revenir aux versions normales, désépinglez simplement les versions de développement :
$ opam unpin catala.dev
$ opam unpin catala-lsp.dev
$ opam unpin catala-format.dev
Et réinstallez Catala :
$ opam reinstall catala
$ opam reinstall catala-lsp
$ opam reinstall catala-format
Obtenir le serveur LSP (nécessaire pour l’extension VSCode)
L’extension VSCode nécessite que le protocole de serveur de langage (LSP) de Catala soit installé. Cela peut être fait en exécutant :
$ opam install catala-lsp.1.0.0
Obtenir l’extension VSCode
Installez VSCode et ouvrez-le. Parcourez le marché des extensions et installez
l’extension Catala.
Pour les installations WSL2
Pour les installations WSL2
VSCode doit atteindre l’environnement WSL installé pour récupérer les outils
Catala. Cela peut être fait en installant l’extension officielle WSL VSCode.
Une fois installée, vous devrez charger une fenêtre VSCode WSL en appuyant sur
F1 (qui ouvre l’invite VSCode) et exécuter la commande suivante
WSL: Connect to WSL.
Obtenir l’outil de mise en forme de code Catala
Exécutez la commande suivante :
$ opam install catala-format.1.0.0
Cette installation prendra un certain temps car elle nécessite l’installation
d’une chaîne d’outils Rust. Si vous avez déjà une chaîne d’outils Rust installée
(vérifiez en tapant cargo dans le terminal), sélectionnez ignore pin depends
lorsqu’on vous le demande.
Une fois installé, vous pouvez rafraîchir votre environnement VSCode (F1, puis
Developer: Reload Window) ce qui notifiera l’extension Catala que l’outil de mise en forme
est maintenant disponible. Vous pouvez invoquer l’outil de mise en forme en utilisant F1,
puis Format Document ou par un raccourci clavier défini par l’utilisateur.
Windows
L’installation sous Windows est actuellement expérimentale, car le support Windows pour la chaîne d’outils logicielle OCaml date du début des années 2020. Si possible, utilisez plutôt WSL (Sous-système Windows pour Linux).
Installateur binaire
Vous pouvez télécharger et installer Catala en utilisant cet installateur binaire - vous pourriez avoir besoin des privilèges d’administrateur :
Actuellement, cet installateur fournit le compilateur Catala de base avec le serveur LSP utilisé par l’extension VS Code ainsi que l’outil de mise en forme de code Catala. C’est pratique pour expérimenter avec le langage mais cela n’inclut pas la chaîne de compilation complète et le système de construction Catala requis pour utiliser les modules. Pour obtenir ceux-ci, vous devrez installer directement depuis les sources.
Une fois ce fichier d’installation Catala installé, vous devrez peut-être
redémarrer VS Code s’il était déjà lancé. Pour vous assurer que tout a été
correctement installé, vous pouvez ouvrir un terminal VS Code et taper $ catala --version. Si cela n’affiche pas d’erreur, tout devrait être correctement
configuré.
Pour installer l’extension VS Code Catala, veuillez vous référer à cette section.
Installation depuis les sources
Obtenir Opam
Ouvrez un PowerShell et installez opam en invoquant
$ Invoke-Expression "& { $(Invoke-RestMethod https://opam.ocaml.org/install.ps1) }"
Si une erreur inattendue se produit, essayez une autre méthode d’installation
d’opam telle que listée sur la page web officielle OCaml sur Windows.
Ensuite, initialisez opam :
$ opam init -c 4.14.2
Obtenir Catala
Actuellement, le paquet opam Catala n’est pas directement compilable sur
Windows. Cependant, le serveur lsp de Catala intègre un sous-ensemble de Catala
qui est suffisant. Cela peut être installé avec la commande suivante
$ opam install catala.1.0.0 catala-lsp.1.0.0
Si l’étape d’installation ne parvient pas à trouver l’outil “ninja”, vous pouvez
l’installer en utilisant winget. Dans un powershell, tapez winget install Ninja-build.Ninja comme décrit ici.
Configurer le serveur LSP Catala
Après l’étape précédente, le serveur LSP Catala devrait être construit dans le
répertoire des binaires d’opam. Pour que VS Code puisse l’obtenir, ce
répertoire doit être ajouté à la variable d’environnement PATH de Windows.
Pour modifier la variable d’environnement PATH, suivez ces instructions.
Le répertoire en question devrait être situé dans
%LOCALAPPDATA%\opam\default\bin (n.b., default pourrait être nommé
autrement comme “4.14.2”, vérifiez l’emplacement du répertoire).
Obtenir l’extension VS Code
Installez VS Code et ouvrez-le. Parcourez le marché des extensions et installez
l’extension Catala.
Obtenir l’outil de mise en forme de code Catala
Actuellement, l’outil de mise en forme de code n’est pas encore disponible sur Windows.
Créer votre premier programme Catala
Maintenant que vous avez installé l’outillage Catala, vous pouvez tester sa
bonne exécution avec l’équivalent d’un programme Hello, world!.
Les programmes Catala sont de simples fichiers texte qui peuvent être manipulés par n’importe quel éditeur de texte ou IDE. Ainsi, pour démarrer votre développement Catala, l’équipe Catala vous recommande d’ouvrir votre éditeur de texte favori.
L’équipe de développement de Catala ne fournit actuellement un support complet que pour l’éditeur de texte VSCode (coloration syntaxique, serveur de langage, outil de mise en forme).
Cependant, la communauté Catala a écrit un certain nombre de plugins pour d’autres éditeurs de texte et IDE, dont la maintenance est assurée au mieux des efforts possibles. N’hésitez pas à contribuer si vous ajoutez le support pour votre éditeur de texte préféré !
Dans votre éditeur de texte/IDE, créez un nouveau dossier pour vos développements
Catala (par exemple nommé catala) et à l’intérieur, un fichier texte vide (par
exemple nommé bonjour_monde.catala_fr).
Écrire des spécifications dans un fichier Catala
Copiez-collez le texte suivant dans votre fichier :
# Tutoriel Catala
## Bonjour tout le monde !
Votre premier programme Catala devrait afficher l'entier `42` comme
Réponse à la Grande Question sur la Vie, l'Univers et le Reste.
À ce stade, votre fichier ressemble à un fichier Markdown classique et ne contient aucun code source Catala per se. En effet, comme Catala utilise la programmation littéraire, tout texte à l’intérieur de votre fichier est considéré par défaut comme une spécification Markdown. Voyons maintenant comment écrire réellement du code !
Écrire votre premier bloc de code Catala
Sous le paragraphe ## Bonjour, monde !, ouvrez un bloc de code Markdown
indiquant le langage catala :
```catala
# <Insérez votre code Catala ici !>
```
Ces blocs de code Catala peuvent être placés n’importe où au milieu du Markdown classique de votre fichier source. En fait, si vous suivez la méthodologie Catala pour traduire la loi en code, votre fichier source ressemblera principalement à un gros document Markdown parsemé de nombreux petits blocs de code Catala.
Votre code source Catala doit toujours être placé à l’intérieur d’un bloc de
code Catala introduit par une ligne avec ```catala et terminé par une
ligne avec ```. Sinon, le compilateur Catala ignorera simplement votre
code.
Maintenant, à l’intérieur du bloc de code Catala, copiez-collez ce qui suit :
déclaration champ d'application BonjourMonde:
résultat réponse_univers contenu entier
champ d'application BonjourMonde:
définition réponse_univers égal à 42
Il n’est pas important de comprendre ce que fait ce code pour l’instant. Vous l’apprendrez plus tard dans le tutoriel.
Vérification des types et exécution du programme Catala
Puisque Catala est un langage fortement typé, vous pouvez vérifier les types
de votre programme sans l’exécuter pour voir s’il y a des erreurs de syntaxe ou
de typage. Cela se fait via la commande clerk typecheck :
$ clerk typecheck bonjour_monde.catala_fr
Le résultat de cette commande devrait être :
┌─[RESULT]─
│ Typechecking successful!
└─
Si le programme passe la vérification des types, nous pouvons l’exécuter via
l’interpréteur contenu dans le compilateur catala. Cela se fait avec la
commande suivante, en indiquant que nous voulons exécuter le champ d’application
(--scope) nommé BonjourMonde à l’intérieur du fichier bonjour_monde.catala_fr :
$ clerk run bonjour_monde.catala_fr --scope=BonjourMonde
Le résultat de cette commande devrait être, comme il est de coutume :
┌─[RESULT]─
│ réponse_univers = 42
└─
Vous devriez maintenant être prêt à poursuivre votre voyage Catala à travers le tutoriel !
Tutoriel : calculer vos impôts
Bienvenue dans ce tutoriel, dont l’objectif est de vous guider à travers les fonctionnalités du langage Catala et de vous apprendre à annoter un texte législatif simple en utilisant le langage, pour obtenir un programme exécutable qui calcule vos impôts !
Ce tutoriel ne couvre pas l’installation de Catala. Pour plus d’informations à ce sujet, veuillez vous référer à la section d’installation. Si vous souhaitez suivre ce tutoriel localement, lisez la section sur la création de votre premier programme et copiez-collez simplement les extraits de code du tutoriel dans votre fichier de programme Catala.
À tout moment, n’hésitez pas à consulter l’aide-mémoire de la syntaxe Catala ou le guide de référence pour une vue exhaustive de la syntaxe et des fonctionnalités de Catala ; ce tutoriel est plutôt conçu pour vous familiariser avec le langage et ses modèles d’utilisation courants.
Le tutoriel comporte trois sections, conçues pour être complétées dans l’ordre car elles couvrent des fonctionnalités de plus en plus difficiles et avancées du langage :
- la première section concerne les éléments de base des programmes du langage avec un flux de données simple ;
- la deuxième section porte sur ce qui rend Catala unique : sa gestion de premier ordre des définitions conditionnelles et des exceptions ;
- la troisième section traite de la montée en charge de la base de code avec des listes d’éléments et de multiples champs d’application ;
- la quatrième section termine avec les états de variables et l’appel dynamique de champs d’application.
Ce tutoriel est conçu pour être une expérience interactive. Tout en lisant le
texte des différentes sections, nous vous encourageons à créer un fichier texte
vide tutoriel.catala_fr et à le remplir en copiant-collant les extraits de
code présentés. Grâce à ce fichier compagnon, vous pourrez voir directement
comment le vérificateur de types et l’interpréteur Catala se comportent sur les
différents exemples, et même faire vos propres expériences en modifiant le code
vous-même.
De plus, après chaque section, des exercices pratiques vous permettront de mettre à l’épreuve ce que vous avez appris. Nous vous encourageons à terminer ces exercices avant de passer à la section suivante.
Vous devriez être prêt à commencer maintenant. Bonne chance !
Éléments de base d’un programme Catala
Dans cette section, le tutoriel présente les éléments de base d’un programme Catala : la différence entre la loi et le code, les structures de données, les champs d’application, les variables et les formules. À la fin de la section, vous devriez être capable d’écrire un programme Catala simple équivalent à une seule fonction avec des variables locales dont les définitions peuvent se référer les unes aux autres.
Un récapitulatif de la section du tutoriel avec le code complet attendu dans
votre fichier compagnon tutoriel.catala_fr est joint à la fin de cette page.
Veuillez vous y référer si vous vous sentez perdu pendant la lecture et
souhaitez vérifier si vous êtes sur la bonne voie pour compléter votre fichier
tutoriel.catala_fr.
“Tisser” la loi et le code
Catala est un langage conçu autour du concept de programmation littéraire, c’est-à-dire le mélange (ou tissage – de l’anglais weaving) entre le code informatique et sa spécification dans un seul document. Pourquoi la programmation littéraire ? Parce qu’elle permet une correspondance fine entre la spécification et le code. Chaque fois que la spécification est mise à jour, savoir où mettre à jour le code est trivial avec la programmation littéraire. C’est absolument crucial pour permettre la maintenance à long terme de programmes complexes et de haute assurance comme le calcul des impôts ou des prestations sociales.
Ainsi, un fichier de code source Catala ressemble à un document Markdown
classique, avec la spécification écrite et formatée comme du texte Markdown,
et le code Catala présent uniquement dans des blocs de code Catala bien délimités
introduits par une ligne avec ```catala et terminés par une ligne avec ```.
Avant d’écrire du code Catala, nous devons introduire la spécification du code pour ce tutoriel. Cette spécification sera basée sur un Code des Impôts fictif définissant un impôt sur le revenu simple. Mais en général, n’importe quoi peut être utilisé comme spécification pour un programme Catala : lois, décrets, motivations de décisions de justice, doctrine juridique, instructions internes, spécifications techniques, etc. Ces sources peuvent être mélangées pour former un programme Catala complet qui s’appuie sur ces multiples sources. Concrètement, incorporer une source juridique de spécification dans le programme Catala revient à copier-coller le texte et à le formater en syntaxe Markdown à l’intérieur du fichier de code source.
Voici le premier paragraphe de spécification pour notre impôt sur le revenu fictif, l’article 1 du CITC (Code des Impôts du Tutoriel Catala) :
L’impôt sur le revenu pour un individu est défini comme un pourcentage fixe du revenu de l’individu sur une année.
Catala utilise un formatage de type Markdown pour le texte juridique dans les
fichiers .catala_fr. Ainsi, pour copier le texte de l’article dans votre
fichier tutoriel.catala_fr, balisez l’en-tête de l’article avec ## et
mettez le texte en dessous, comme ceci :
## Article 1
L'impôt sur le revenu pour un individu est défini comme un pourcentage fixe du
revenu de l'individu sur une année.
L’esprit de l’écriture de code en Catala est de coller à la spécification à tout moment afin de placer les extraits de code là où ils doivent être. Par conséquent, nous introduirons ci-dessous les extraits de code Catala qui traduisent l’article 1, qui doivent être placés juste en dessous de l’article 1 dans le fichier de code source Catala.
Ces extraits de code doivent décrire le programme qui calcule l’impôt sur le revenu, et contenir la règle le définissant comme une multiplication du revenu par un taux. Il est temps de plonger dans Catala en tant que langage de programmation.
# Nous apprendrons bientôt quoi écrire ici pour traduire le sens de l'article 1
# en code Catala.
# Pour créer un bloc de code Catala dans votre fichier, délimitez-le avec les
# balises de style Markdown "```catala" et "```". Vous pouvez écrire des
# commentaires dans les blocs de code Catala en préfixant les lignes par "#"
Dans la suite du tutoriel, lors de la présentation d’extraits de code Catala,
il est implicitement supposé que vous devez les copier-coller dans votre
fichier tutoriel.catala_fr à l’intérieur d’un bloc de code Catala délimité
par ```catala et ```, et placé près de l’article de loi qu’il implémente.
Mettre en place les structures de données
Le contenu de l’article 1 suppose beaucoup de contexte implicite : il existe un individu avec un revenu, ainsi qu’un impôt sur le revenu que l’individu doit payer chaque année. Même si ce contexte implicite n’est pas verbatim dans la loi, nous devons l’expliciter dans le code informatique, sous la forme de structures de données et de signatures de fonctions.
Catala est un langage fortement typé et compilé statiquement, donc toutes les structures de données et signatures de fonctions doivent être explicitement déclarées. Nous commençons donc par déclarer les informations de type pour l’individu, le contribuable qui sera le sujet du calcul de l’impôt. Cet individu a un revenu et un nombre d’enfants, deux informations qui seront nécessaires à des fins fiscales :
# Les déclarations de structures de données et généralement toute déclaration
# en Catala ne correspondent souvent à aucun article de loi spécifique. Ainsi,
# vous pouvez mettre toutes les déclarations en haut de votre fichier
# tutoriel.catala_fr, avant l'article 1.
# Le nom de la structure, "Individu", doit commencer par une majuscule :
# c'est la convention CamelCase.
déclaration structure Individu:
# Dans cette ligne, "revenu" est le nom du champ de la structure et
# "argent" est le type de ce qui est stocké dans ce champ.
# Les types disponibles incluent : "entier", "décimal", "argent", "date",
# "durée", et toute autre structure ou énumération que vous déclarez.
donnée revenu contenu argent
# Les noms de champs "revenu" et "nombre_enfants" commencent par une
# minuscule, ils suivent la convention snake_case.
donnée nombre_enfants contenu entier
Cette structure contient deux champs de données, revenu et nombre_enfants.
Les structures sont utiles pour regrouper des données qui vont ensemble.
Généralement, vous obtenez une structure par objet concret sur lequel la loi
s’applique (comme l’individu). C’est à vous de décider comment regrouper les
données, mais nous vous conseillons de viser l’optimisation de la lisibilité du
code.
Parfois, la loi donne une énumération de différentes situations. Ces énumérations sont modélisées en Catala à l’aide d’un type énumération, comme :
# Le nom "CréditImpôt" est également écrit en CamelCase.
déclaration énumération CréditImpôt:
# La ligne ci-dessous dit que "CréditImpôt" peut être une situation
# "PasDeCréditImpôt".
-- PasDeCréditImpôt
# La ligne ci-dessous dit qu'alternativement, "CréditImpôt" peut être une
# situation "CréditImpôtEnfants". Cette situation porte un contenu de type
# entier correspondant au nombre d'enfants concernés par le crédit d'impôt.
# Cela signifie que si vous êtes dans la situation "CréditImpôtEnfants",
# vous aurez également accès à ce nombre d'enfants.
-- CréditImpôtEnfants contenu entier
En termes informatiques, une telle énumération est appelée un “type somme” ou simplement une enum. La combinaison de structures et d’énumérations permet au programmeur Catala de déclarer toutes les formes possibles de données, car elles sont équivalentes à la puissante notion de types de données algébriques.
Notez que ces structures de données que nous avons déclarées ne peuvent pas toujours être rattachées naturellement à un morceau particulier du texte de spécification. Alors, où mettre ces déclarations dans votre fichier de programmation littéraire ? Puisque vous reviendrez souvent à ces déclarations de structures de données pendant la programmation, nous vous conseillons de les regrouper dans une sorte de prélude dans votre fichier de code source. Concrètement, cette section de prélude contenant la déclaration des structures de données sera votre point de référence unique pour essayer de comprendre les données manipulées par les règles ailleurs dans le fichier de code source.
Les champs d’application comme blocs de calcul de base
Nous avons défini et typé les données que le programme manipulera. Maintenant, nous devons définir le contexte logique dans lequel ces données évolueront. Parce que Catala est un langage de programmation fonctionnelle, tout code existe au sein d’une fonction. Et l’équivalent d’une fonction en Catala est appelé un champ d’application (scope). Un champ d’application est composé de :
- un nom,
- des variables d’entrée (similaires aux arguments de fonction),
- des variables internes (similaires aux variables locales),
- des variables de résultat (qui forment ensemble le type de retour de la fonction).
Par exemple, l’article 1 déclare un champ d’application pour calculer l’impôt sur le revenu :
# Les noms de champs d'application utilisent la convention de nommage CamelCase,
# comme les noms de structures ou d'énums. Les variables de champ d'application,
# en revanche, utilisent la convention de nommage snake_case, comme les champs
# de structure.
déclaration champ d'application CalculImpôtRevenu:
# La ligne suivante déclare une variable d'entrée du champ d'application,
# ce qui s'apparente à un paramètre de fonction en termes informatiques.
# C'est la donnée sur laquelle le champ d'application va opérer.
entrée individu contenu Individu
interne taux_imposition contenu décimal
résultat impôt_revenu contenu argent
Le champ d’application est l’unité d’abstraction de base dans les programmes Catala, et les champs d’application peuvent être composés. Puisqu’une fonction peut appeler d’autres fonctions, les champs d’application peuvent aussi appeler d’autres champs d’application. Nous verrons plus tard comment faire cela, mais concentrons-nous d’abord sur les entrées et les sorties des champs d’application.
La déclaration du champ d’application s’apparente à une signature de fonction :
elle contient une liste de tous les arguments avec leurs types. Mais en Catala,
les variables de champ d’application peuvent être entrée, interne ou
résultat. entrée signifie que la variable doit être fournie chaque fois que
le champ d’application est appelé, et ne peut pas être définie à l’intérieur du
champ d’application. interne signifie que la variable est définie à l’intérieur
du champ d’application et ne peut pas être vue de l’extérieur ; elle ne fait pas
partie de la valeur de retour du champ d’application. résultat signifie qu’un
appelant peut récupérer la valeur calculée de la variable. Notez qu’une variable
peut être simultanément une entrée et un résultat du champ d’application, dans
ce cas elle doit être annotée avec entrée résultat.
Une fois le champ d’application déclaré, nous pouvons l’utiliser pour définir nos règles de calcul et enfin coder l’article 1 !
Définir des variables et des formules
L’article 1 donne en fait la formule pour définir la variable impôt_revenu du
champ d’application CalculImpôtRevenu, ce qui se traduit par le code Catala
suivant :
L’impôt sur le revenu pour un individu est défini comme un pourcentage fixe du revenu de l’individu sur une année.
champ d'application CalculImpôtRevenu:
définition impôt_revenu égal à
individu.revenu * taux_imposition
Décortiquons le code ci-dessus. Chaque définition d’une variable (ici,
impôt_revenu) est rattachée à un champ d’application qui la déclare (ici,
CalculImpôtRevenu). Après égal à, nous avons l’expression réelle pour la
variable : individu.revenu * taux_imposition. La syntaxe des formules utilise
les opérateurs arithmétiques classiques. Ici, * signifie multiplier un montant
d’argent par un décimal, renvoyant un nouveau montant d’argent. Le
comportement exact de chaque opérateur dépend des types de valeurs sur lesquels
il est appliqué. Par exemple, ici, parce qu’une valeur de type argent est
toujours un nombre entier de centimes, * arrondit le résultat de la
multiplication au centime le plus proche pour fournir la valeur finale de type
argent (voir la FAQ pour plus d’informations sur
l’arrondi en Catala). Concernant individu.revenu, nous voyons que la notation
. nous permet d’accéder au champ revenu de individu, qui est en fait une
structure de type Individu.
De manière similaire à l’accès aux champs de structure, Catala vous permet
d’inspecter le contenu d’une valeur d’énumération avec le filtrage par motif
(pattern matching), comme il est d’usage dans les langages de programmation
fonctionnelle. Concrètement, si crédit_impôt est une variable dont le type est
CréditImpôt tel que déclaré ci-dessus, alors vous pouvez définir le montant
d’un crédit d’impôt qui dépend d’un nombre d’enfants éligibles avec le filtrage
par motif suivant :
selon crédit_impôt sous forme
-- PasDeCréditImpôt: 0 €
-- CréditImpôtEnfants contenu nombre_enfants_éligibles:
10 000 € * nombre_enfants_éligibles
Dans la branche -- CréditImpôtEnfants contenu nombre_enfants_éligibles:, vous
savez que crédit_impôt est dans la variante CréditImpôtEnfants, et
nombre_enfants_éligibles vous permet de lier le contenu entier de la
variante. Comme dans un langage de programmation fonctionnelle classique, vous
pouvez donner le nom que vous voulez à nombre_enfants_éligibles, ce qui est
utile si vous imbriquez des filtrages par motif et souhaitez différencier le
contenus de deux variantes différentes.
Maintenant, revenons à notre champ d’application CalculImpôtRevenu. À ce stade,
il nous manque encore la définition de taux_imposition. C’est un schéma courant
lors du codage de la loi : les définitions des différentes variables sont
dispersées dans différents articles. Heureusement, le compilateur Catala
collecte automatiquement toutes les définitions pour chaque champ d’application
et les met dans le bon ordre. Ici, même si nous définissons taux_imposition
après impôt_revenu dans notre code source, le compilateur Catala inversera
l’ordre des définitions en interne car taux_imposition est utilisé dans la
définition de impôt_revenu. Plus généralement, l’ordre des définitions et
déclarations de haut niveau dans les fichiers de code source Catala n’a pas
d’importance, et vous pouvez remanier le code librement sans avoir à vous soucier
de l’ordre des dépendances.
Dans ce tutoriel, nous supposerons que notre spécification CITC fictive définit le pourcentage dans l’article suivant. Le code Catala ci-dessous ne devrait pas vous surprendre à ce stade.
Le pourcentage fixe mentionné à l’article 1 est égal à 20 %.
champ d'application CalculImpôtRevenu:
# Écrire 20% est juste une alternative pour le décimal "0,20".
définition taux_imposition égal à 20 %
Types de base et calculs en Catala
Jusqu’à présent, nous avons vu des valeurs qui ont des types comme décimal,
argent, entier. On pourrait objecter qu’il n’y a pas lieu de distinguer ces
trois concepts, car ce ne sont que des nombres. Cependant, la philosophie de
Catala est de rendre explicite chaque choix qui affecte le résultat du calcul,
et la représentation des nombres affecte le résultat du calcul. En effet, les
calculs financiers varient selon que l’on considère un montant d’argent comme un
nombre exact de centimes, ou que l’on stocke des chiffres fractionnaires
supplémentaires après le centime. Puisque le type de programmes pour lesquels Catala est conçu
implique de lourdes conséquences pour de nombreux utilisateurs, le langage est
assez strict sur la façon dont les nombres sont représentés. La règle générale
est que, en Catala, les nombres se comportent exactement selon la sémantique
mathématique commune que l’on peut associer aux calculs arithmétiques de base
(+, -, *, /).
En particulier, cela signifie que les valeurs entier sont illimitées et ne
peuvent jamais déborder.
De même, les valeurs décimal peuvent être arbitrairement précises (bien
qu’elles soient toujours rationnelles, appartenant à ℚ) et ne souffrent pas des
imprécisions de la virgule flottante. Pour argent, le langage prend une
décision arrêtée : une valeur de type argent est toujours un nombre entier de
centimes.
Ces choix ont plusieurs conséquences :
entierdivisé parentierdonne undécimal;argentne peut pas être multiplié parargent(multipliez plutôtargentpardécimal) ;argentmultiplié (ou divisé) pardécimalarrondit le résultat au centime le plus proche ;argentdivisé parargentdonne undécimal(qui n’est absolument pas arrondi).
Concrètement, cela donne :
10 / 3 = 3,333333333... et
10 € / 3,0 = 3,33 € et
20 € / 3,0 = 6,67 € et
10 € / 3 € = 3,33333333...
Le compilateur Catala vous guidera pour utiliser explicitement les opérations correctes, en signalant des erreurs de compilation lorsque ce n’est pas le cas.
Par exemple, essayer d’ajouter un entier et un décimal donne le message
d’erreur suivant du compilateur Catala :
┌─[ERROR]─
│
│ I don't know how to apply operator + on types integer and decimal
│
├─➤ tutoriel_fr.catala_fr
│ │
│ │ définition x égal à 1 + 2,0
│ │ ‾‾‾‾‾‾‾
│
│ Type integer coming from expression:
├─➤ tutoriel_fr.catala_fr
│ │
│ │ définition x égal à 1 + 2,0
│ │ ‾
│
│ Type decimal coming from expression:
├─➤ tutoriel_fr.catala_fr
│ │
│ │ définition x égal à 1 + 2,0
│ │ ‾‾‾
└─
Pour corriger cette erreur, vous devez utiliser une conversion explicite, par
exemple en remplaçant 1 par décimal de 1. Référez-vous à la référence du langage
pour toutes les conversions possibles, les opérations et leurs sémantiques associées.
Catala possède également des types intégrés date et durée avec les opérations
associées courantes (ajouter une durée à une date, soustraire deux dates pour
obtenir une durée, etc.). Pour un aperçu plus approfondi des calculs de dates
(qui sont très délicats !),
consultez la référence du langage.
Tester le code
Maintenant que nous avons implémenté quelques articles en Catala, il est temps de tester notre code pour vérifier qu’il se comporte correctement. Nous vous encourageons à tester votre code souvent, le plus tôt possible, et à vérifier le résultat du test dans un système d’intégration continue pour éviter les régressions.
Le test du code Catala se fait avec l’interpréteur à l’intérieur du compilateur,
accessible avec la commande interpret et l’option --scope qui spécifie le
champ d’application à interpréter.
Tester CalculImpôtRevenu directement ?
Tester CalculImpôtRevenu directement ?
Le réflexe à ce stade est d’exécuter la commande suivante :
$ clerk run tutoriel.catala_fr --scope=CalculImpôtRevenu
┌─[ERROR]─
│
│ Invalid scope for execution or testing: it defines input variables. If
│ necessary, a wrapper scope with explicit inputs to this one can be defined.
│
├─➤ tutoriel.catala_fr:41.9-41.19:
│ │
│ 41 │ entrée individu contenu Individu
│ │ ‾‾‾‾‾‾‾‾
└─
Comme le dit le message d’erreur, essayer d’interpréter directement
CalculImpôtRevenu revient à essayer de calculer les impôts de
quelqu’un sans connaître le revenu de la personne ! Pour être exécuté,
le champ d’application doit être appelé avec des valeurs concrètes
pour le revenu et le nombre d’enfants de l’individu. Sinon, Catala se
plaindra que les variables d’entrée du champ d’application manquent
pour l’interprétation. Toutefois, il est possible de fournir ces
entrées sous forme de données JSON à l’aide de l’option --input. La
section Support du JSON décrit la manière
d’y parvenir. Notez que, pour des raisons de robustesse du code, nous
recommendons d’écrire vos tests directement en Catala lorsque cela est
possible.
Le modèle de test utilise des concepts qui seront vus plus tard
dans le tutoriel, il est donc acceptable de considérer une partie de ce qui suit
comme une syntaxe mystérieuse qui fait ce que nous voulons. Fondamentalement,
nous allons créer pour notre cas de test un nouveau test qui passera des
arguments spécifiques à CalculImpôtRevenu qui est testé :
déclaration champ d'application Test:
# La ligne suivante est mystérieuse pour l'instant
résultat calcul contenu CalculImpôtRevenu
champ d'application Test:
définition calcul égal à
# La ligne suivante est mystérieuse pour l'instant
résultat de CalculImpôtRevenu avec {
# Ci-dessous, nous passons les variables d'entrée pour "CalculImpôtRevenu"
-- individu:
# "individu" a un type structure, nous devons donc construire la
# structure "Individu" avec la syntaxe suivante
Individu {
# "revenu" et "nombre_enfants" sont les champs de la structure ;
# nous leur donnons les valeurs que nous voulons pour notre test
-- revenu: 20 000 €
-- nombre_enfants: 0
}
}
Ce test peut maintenant être exécuté via l’interpréteur Catala :
$ clerk run tutoriel.catala_fr --scope=Test
┌─[RESULT]─
│ calcul = CalculImpôtRevenu { -- impôt_revenu: 4 000,00 € }
└─
Nous pouvons maintenant vérifier que 4 000 € = 20 000 € * 20% ; le résultat est correct.
Utilisez ce test pour jouer régulièrement avec le code pendant le tutoriel et inspecter ses résultats sous divers scénarios d’entrée. Cela vous aidera à comprendre le comportement des programmes Catala, et à repérer les erreurs dans votre code 😀
Vous pouvez également vérifier qu’il n’y a pas d’erreur de syntaxe ou de typage dans votre code, sans le tester, avec la commande suivante :
$ clerk typecheck tutoriel.catala_fr
┌─[RESULT]─
│ Typechecking successful!
└─
Récapitulons
Ceci conclut la première section du tutoriel. En mettant en place des structures
de données comme structure et énumération, en représentant les types des
variables de champ d'application, et la définition de formules pour ces
variables, vous devriez maintenant être capable de coder en Catala l’équivalent
de programmes à fonction unique qui effectuent des opérations arithmétiques
courantes et définissent des variables locales.
Récapitulatif de la section actuelle
Récapitulatif de la section actuelle
Pour référence, voici la version finale du code Catala consolidé à la fin de cette section du tutoriel.
# Corrigé du tutoriel
## Article 1
L'impôt sur le revenu pour un individu est défini comme un pourcentage fixe du
revenu de l'individu sur une année.
```catala
déclaration structure Individu:
donnée revenu contenu argent
donnée nombre_enfants contenu entier
déclaration champ d'application CalculImpôtRevenu:
entrée individu contenu Individu
interne taux_imposition contenu décimal
résultat impôt_revenu contenu argent
champ d'application CalculImpôtRevenu:
définition impôt_revenu égal à
individu.revenu * taux_imposition
```
## Article 2
Le pourcentage fixe mentionné à l'article 1 est égal à 20 %.
```catala
champ d'application CalculImpôtRevenu:
définition taux_imposition égal à 20%
```
## Test
```catala
déclaration champ d'application Test:
résultat calcul contenu CalculImpôtRevenu
champ d'application Test:
définition calcul égal à
résultat de CalculImpôtRevenu avec {
-- individu:
Individu {
-- revenu: 20 000 €
-- nombre_enfants: 0
}
}
```
Exercice pratique : “Rien n’est certain sauf la mort et les impôts”
Dans cette section, nous présentons un exercice pratique visant à familiariser
avec l’écriture de programmes Catala et la compréhension de ses concepts de base.
Nous vous invitons fortement à configurer un environnement Catala fonctionnel,
à ouvrir un éditeur (tel que vscode) et à copier-coller le modèle d’exercice
suivant dans un fichier catala ; vous pouvez le nommer exercice-2-1-1.catala_fr.
Fichier modèle ‘exercice-2-1-1.catala_fr’
Fichier modèle ‘exercice-2-1-1.catala_fr’
# Livre Catala : Exercice 2-1-1
```catala
déclaration structure Individu:
donnée date_naissance contenu date
donnée date_décès contenu DateDécès
déclaration énumération DateDécès:
-- Décédé contenu date
-- ToujoursVivant
```
# Question 1
En vous basant sur la déclaration `test_personne1`, déclarez une `test_personne2`
née le 21 décembre 1977 qui est toujours vivante.
```catala
# Déclaration et définition d'une valeur constante nommée test_personne1
déclaration test_personne1 contenu Individu égal à
Individu {
-- date_naissance: |1981-10-05|
# Le format de date est |AAAA-MM-JJ|
-- date_décès: Décédé contenu |2012-05-12|
}
déclaration test_personne2 contenu Individu égal à
test_personne1 # <= Supprimez cette ligne et remplacez-la par votre réponse
```
```catala
déclaration champ d'application TuerPersonne:
entrée date_fatidique contenu date
entrée victime contenu Individu
résultat individu_tué contenu Individu
```
# Question 2
Étant donné la déclaration du champ d'application `TuerPersonne`, définissez une
variable de résultat `individu_tué` du champ d'application `TuerPersonne` qui
utilise les variables d'entrée `date_fatidique` et `victime` pour créer un nouvel
Individu qui est une copie de l'entrée `victime` mais avec sa `date_décès` mise
à jour. Par souci de simplicité, nous ne vérifierons pas si nous tuons un
individu déjà décédé.
Vous pouvez tester votre solution en invoquant le champ d'application Catala
TestTuerPersonne : `clerk run exercice-2-1-1.catala_fr --scope TestTuerPersonne`.
```catala
déclaration champ d'application TestTuerPersonne:
résultat calcul contenu TuerPersonne
champ d'application TestTuerPersonne:
définition calcul égal à
résultat de TuerPersonne avec {
-- date_fatidique: |2025-01-20|
-- victime: test_personne2
}
# Définissez votre champ d'application TuerPersonne ici
# champ d'application TuerPersonne:
# ...
```
```catala
déclaration structure Couple:
donnée revenu_annuel_foyer contenu argent
donnée personne_1 contenu Individu
donnée personne_2 contenu Individu
# Définissez votre définition test_couple ici
# déclaration test_couple contenu Couple égal à
# ...
```
```catala
déclaration champ d'application CalculImpôt:
entrée date_traitement contenu date
entrée couple contenu Couple
interne personne1_décédée_avant_date_traitement contenu booléen
interne personne2_décédée_avant_date_traitement contenu booléen
résultat montant_impôt contenu argent
champ d'application CalculImpôt:
définition montant_impôt égal à
si personne1_décédée_avant_date_traitement
ou personne2_décédée_avant_date_traitement alors
0 €
sinon
couple.revenu_annuel_foyer * 15%
```
## Question 4
Définissez une autre définition de champ d'application `CalculImpôt` qui définit
les deux variables booléennes internes `personne1_décédée_avant_date_traitement`
et `personne2_décédée_avant_date_traitement`.
```catala
# Définissez votre nouveau champ d'application CalculImpôt ici
# champ d'application CalculImpôt:
# ...
```
## Question 5
Enfin, nous devons définir un test pour notre calcul. En vous basant sur les
tests définis précédemment, écrivez une définition du champ d'application de test
`TestCalculImpôt`. Ensuite, ajustez les valeurs de test données pour vous assurer
que votre implémentation est correcte.
```catala
déclaration champ d'application TestCalculImpôt:
résultat test_calcul_impôt contenu CalculImpôt
# Définissez votre champ d'application TestCalculImpôt ici
# champ d'application TestCalculImpôt:
# ...
```
Tout au long de cet exercice, n’hésitez pas à vous référer à l’aide-mémoire de la syntaxe Catala ! En particulier si vous avez du mal avec les constructions syntaxiques du langage.
Dans cet exercice, nous voulons définir (encore un autre !) calcul d’impôt. Cette fois, le montant de l’impôt qu’un foyer doit payer dépend de si les individus sont toujours vivants ou non. Afin de modéliser un tel mécanisme, nous introduisons les structures Catala suivantes représentant les individus.
Un individu est référencé en utilisant seulement deux informations : sa date de
naissance et sa possible date de décès. Si un individu est toujours vivant,
il n’a pas de date de décès. Exprimer cette possibilité peut se faire en
utilisant une énumération : si l’individu est toujours vivant, son entrée
date_décès sera ToujoursVivant sinon ce sera Décédé qui doit être
accompagné d’une valeur de date comme spécifié dans la déclaration suivante.
déclaration structure Individu:
donnée date_naissance contenu date
donnée date_décès contenu DateDécès
déclaration énumération DateDécès:
-- Décédé contenu date
-- ToujoursVivant
# Déclaration et définition d'une valeur constante nommée test_personne1
déclaration test_personne1 contenu Individu égal à
Individu {
-- date_naissance: |1981-10-05|
# Le format de date est |AAAA-MM-JJ|
-- date_décès: Décédé contenu |2012-05-12|
}
Question 1
En vous basant sur la déclaration test_personne1, essayez de définir une
test_personne2 née le 21 décembre 1977 qui est toujours vivante.
Solution de la Question 1
Solution de la Question 1
Réponse :
déclaration test_personne2 contenu Individu égal à
Individu {
-- date_naissance: |1977-12-21|
-- date_décès: ToujoursVivant
}
Nous voulons maintenant un moyen de mettre à jour le statut d’une personne de
vivant à décédé. Nous le faisons via un champ d’application dédié TuerPersonne
dont la déclaration est :
déclaration champ d'application TuerPersonne:
entrée date_fatidique contenu date
entrée victime contenu Individu
résultat individu_tué contenu Individu
Question 2
Étant donné la déclaration du champ d’application TuerPersonne, définissez une
variable de résultat individu_tué du champ d’application TuerPersonne qui
utilise les variables d’entrée date_fatidique et victime pour créer un nouvel
Individu qui est une copie de l’entrée victime mais avec sa date_décès mise
à jour. Par souci de simplicité, nous ne vérifierons pas si nous tuons un
individu déjà décédé.
Vous pouvez tester votre solution en utilisant le champ d’application TestTuerPersonne suivant en invoquant cette commande dans une console :
clerk run exercice-2-1-1.catala_fr --scope TestTuerPersonne
déclaration champ d'application TestTuerPersonne:
résultat calcul contenu TuerPersonne
champ d'application TestTuerPersonne:
définition calcul égal à
résultat de TuerPersonne avec {
-- date_fatidique: |2025-01-20|
-- victime: test_personne2
}
Solution de la Question 2
Solution de la Question 2
champ d'application TuerPersonne:
définition individu_tué égal à
Individu {
-- date_naissance: victime.date_naissance
-- date_décès: Décédé contenu date_fatidique
}
Vous pouvez également utiliser la syntaxe en remplaçant pour modifier des
champs spécifiques d’une structure. Cela vous permet de ne modifier que des
champs spécifiques, ce qui peut être utile surtout lorsqu’une structure définit
beaucoup de champs !
champ d'application TuerPersonne:
définition individu_tué égal à
victime mais en remplaçant { -- date_décès: Décédé contenu date_fatidique }
Nous définissons maintenant une structure Couple qui représente un foyer
simple. Cette structure a trois entrées différentes :
- Deux individus :
personne_1etpersonne_2; - Et leur
revenu_annuel_foyercombiné.
déclaration structure Couple:
donnée personne_1 contenu Individu
donnée personne_2 contenu Individu
donnée revenu_annuel_foyer contenu argent
Question 3
Encore une fois, définissez une valeur de test nommée test_couple qui réutilise
les individus précédemment définis (à savoir test_personne_1 et
test_personne_2) et fixe leur revenu_annuel_foyer à 80 000 €.
Solution de la Question 3
Solution de la Question 3
déclaration test_couple contenu Couple égal à
Couple {
-- revenu_annuel_foyer: 80 000 €
-- personne_1: test_personne1
-- personne_2: test_personne2
}
Considérons maintenant un article de loi sur le calcul de l’impôt avec une définition très simple :
L’impôt sur le revenu pour le foyer d’un couple est défini comme 15% de leur revenu annuel, sauf si l’un (ou les deux) des individus est décédé avant la date de traitement du dossier.
Cela se traduit par le code Catala suivant :
déclaration champ d'application CalculImpôt:
entrée date_traitement contenu date
entrée couple contenu Couple
interne personne1_décédée_avant_date_traitement contenu booléen
interne personne2_décédée_avant_date_traitement contenu booléen
résultat montant_impôt contenu argent
champ d'application CalculImpôt:
définition montant_impôt égal à
si
personne1_décédée_avant_date_traitement
ou personne2_décédée_avant_date_traitement
alors 0 €
sinon couple.revenu_annuel_foyer * 15%
Afin de banaliser la définition de montant_impôt, nous avons introduit deux
variables de champ d’application interne qui représentent des valeurs
booléennes (vrai ou faux). Cependant, même si nous avons fourni la définition
de la variable de résultat montant_impôt, ces variables internes restent à
définir sinon nous ne pourrons pas effectuer le calcul efficacement.
Question 4
En Catala, les définitions de champ d’application peuvent être dispersées dans tout le fichier. Ce faisant, cela permet d’implémenter localement la logique définie par un article de loi sans introduire de code passe-partout qui gênerait un processus de révision.
Définissez une autre définition de champ d’application CalculImpôt qui définit
les deux variables booléennes internes personne1_décédée_avant_date_traitement
et personne2_décédée_avant_date_traitement.
Afin de décomposer et de raisonner sur les valeurs d’énumération, on peut
utiliser la construction de filtrage par motif (pattern matching). Par exemple,
le filtrage par motif d’une énumération DateDécès ressemble à ceci :
champ d'application CalculImpôt:
définition âge_individu égal à
selon individu.date_décès sous forme
-- ToujoursVivant: date_courante - individu.date_naissance
-- Décédé contenu date_décès: date_décès - individu.date_naissance
Nota bene : toutes les différentes branches d’un filtrage par motif doivent contenir des expressions du même type de données.
Solution de la Question 4
Solution de la Question 4
champ d'application CalculImpôt:
définition personne1_décédée_avant_date_traitement égal à
selon couple.personne_1.date_décès sous forme
-- ToujoursVivant: faux
-- Décédé contenu d: d < date_traitement
définition personne2_décédée_avant_date_traitement égal à
# Une autre syntaxe possible pour tester les motifs
couple.personne_2.date_décès sous forme
Décédé contenu d et d < date_traitement
Question 5
Enfin, nous devons définir un test pour notre calcul. En vous basant sur les
tests définis précédemment, écrivez une définition du champ d’application de test
TestCalculImpôt. Ensuite, ajustez les valeurs de test données pour vous assurer
que votre implémentation est correcte.
déclaration champ d'application TestCalculImpôt:
résultat test_calcul_impôt contenu CalculImpôt
Comme précédemment, vous pouvez utiliser une commande similaire pour exécuter votre test :
$ clerk run exercice-2-1-1.catala_fr --scope TestCalculImpôt
Solution de la Question 5
Solution de la Question 5
champ d'application TestCalculImpôt:
définition test_calcul_impôt égal à
résultat de CalculImpôt avec {
# La date de traitement est une semaine avant le décès de test_personne1 :
-- date_traitement: |2012-05-01|
# La date de traitement est une semaine avant le décès de test_personne2 :
# -- date_traitement: |2012-05-20|
-- couple: test_couple
}
Définitions conditionnelles et exceptions
Dans cette section, le tutoriel présente la fonctionnalité phare de Catala pour coder la loi : les exceptions dans les définitions de variables. À la fin de la section, vous devriez comprendre le comportement des calculs impliquant des exceptions, et être capable de structurer des groupes de définitions de variables selon leur statut exceptionnel et leur priorité relative.
Récapitulatif de la section précédente
Récapitulatif de la section précédente
Cette section du tutoriel s’appuie sur la précédente, et réutilisera le même exemple fil rouge, mais tout le code Catala nécessaire pour exécuter l’exemple est inclus ci-dessous pour référence.
# Corrigé du tutoriel
## Article 1
L'impôt sur le revenu pour un individu est défini comme un pourcentage fixe du
revenu de l'individu sur une année.
```catala
déclaration structure Individu:
donnée revenu contenu argent
donnée nombre_enfants contenu entier
déclaration champ d'application CalculImpôtRevenu:
entrée individu contenu Individu
interne taux_imposition contenu décimal
résultat impôt_revenu contenu argent
champ d'application CalculImpôtRevenu:
définition impôt_revenu égal à
individu.revenu * taux_imposition
```
## Article 2
Le pourcentage fixe mentionné à l'article 1 est égal à 20 %.
```catala
champ d'application CalculImpôtRevenu:
définition taux_imposition égal à 20%
```
## Test
```catala
déclaration champ d'application Test:
résultat calcul contenu CalculImpôtRevenu
champ d'application Test:
définition calcul égal à
résultat de CalculImpôtRevenu avec {
-- individu:
Individu {
-- revenu: 20 000 €
-- nombre_enfants: 0
}
}
```
Définitions conditionnelles et exceptions
Les spécifications provenant de textes juridiques ne divisent pas toujours proprement chaque définition de variable dans son propre article. Parfois, et c’est un schéma très courant, un article ultérieur redéfinit une variable déjà définie précédemment, mais avec une particularité dans une certaine situation exceptionnelle. Par exemple, l’article 3 du CITC :
Si l’individu a la charge de 2 enfants ou plus, alors le pourcentage fixe mentionné à l’article 1 est égal à 15 %.
Cet article donne en fait une autre définition pour le pourcentage fixe, qui
était déjà défini à l’article 2. Cependant, l’article 3 définit le pourcentage
conditionnellement au fait que l’individu ait plus de 2 enfants. Comment redéfinir
taux_imposition ? Catala vous permet précisément de redéfinir une variable sous une
condition avec la syntaxe sous condition ... conséquence entre le nom
de la variable définie et le mot-clé égal à :
champ d'application CalculImpôtRevenu:
définition taux_imposition
sous condition individu.nombre_enfants >= 2
conséquence égal à 15 %
Qu’est-ce que cela signifie ? Si l’individu a plus de deux enfants, alors
taux_imposition sera de 15 %. Les définitions conditionnelles vous permettent de définir
vos variables par morceaux, un cas à la fois ; le compilateur Catala assemble
le tout pour l’exécution. Plus précisément, à l’exécution, nous regardons
les conditions de toutes les définitions par morceaux pour une même variable, et choisissons
celle qui est valide.
Pour tester ce qui se passe lorsque les règles des articles 2 et 3 sont en jeu,
vous pouvez tester le programme lorsqu’il y a trois enfants, en modifiant le
champ d’application Test comme suit :
champ d'application Test:
définition calcul égal à
résultat de CalculImpôtRevenu avec {
-- individu:
Individu {
-- revenu: 20 000 €
-- nombre_enfants: 3
}
}
Pendant le test ci-dessus, deux définitions pour taux_imposition sont valides en
même temps. Que se passe-t-il alors ? Dans ces cas, Catala interrompra
l’exécution et renverra un message d’erreur comme celui ci-dessous :
$ clerk run tutoriel.catala_fr --scope=Test
┌─[ERROR]─
│
│ During evaluation: conflict between multiple valid consequences for assigning the same variable.
│
├─➤ tutoriel_fr.catala_fr
│ │
│ │ définition taux_imposition égal à 20 %
│ │ ‾‾‾‾‾‾
├─ Article 2
│
├─➤ tutoriel_fr.catala_fr
│ │
│ │ conséquence égal à 15 %
│ │ ‾‾‾‾
└─ Article 3
Si la spécification est correctement rédigée, alors ces situations d’erreur ne devraient
pas se produire, car une et une seule définition conditionnelle devrait être valide à tout
moment. Ici, cependant, notre définition de taux_imposition entre en conflit avec la
définition plus générale que nous avons donnée ci-dessus. Pour modéliser correctement des situations comme
celle-ci, Catala nous permet de définir la priorité d’une définition conditionnelle
sur une autre. C’est aussi simple que d’ajouter exception avant la définition.
Par exemple, voici une version plus correcte du code pour l’article 3 :
Si l’individu a la charge de 2 enfants ou plus, alors le pourcentage fixe mentionné à l’article 1 est égal à 15 %.
champ d'application CalculImpôtRevenu:
exception définition taux_imposition
sous condition individu.nombre_enfants >= 2
conséquence égal à 15 %
Avec exception, la définition conditionnelle de l’article 3 sera choisie par rapport
au cas de base de l’article 1 lorsque l’individu a deux enfants ou plus. Ce
mécanisme d’exception est calqué sur la logique de la rédaction juridique : c’est le mécanisme clé
qui nous permet de diviser notre définition de variables pour correspondre à la structure de
la spécification. Sans exception, il n’est pas possible d’utiliser le style de
programmation littéraire. C’est précisément pourquoi écrire et maintenir des programmes
informatiques pour les impôts ou les prestations sociales est très difficile avec les langages de programmation
grand public. Alors, allez-y et utilisez exception autant que possible,
car c’est un concept Catala très idiomatique.
Lors de la définition d’exceptions dans votre code Catala, il est important de comprendre précisément leur sémantique sous-jacente, c’est-à-dire quel sera le résultat final du calcul. La sémantique de Catala est formellement définie et basée sur la logique par défaut priorisée, ce qui se traduit intuitivement par l’algorithme suivant décrivant comment calculer les exceptions :
- Rassembler toutes les définitions (conditionnelles ou non) pour une variable donnée ;
- Parmi ces définitions, vérifier toutes celles qui s’appliquent (dont les conditions s’évaluent à
vrai) :- Si aucune définition ne s’applique, le programme plante avec une erreur (“aucune définition ne s’applique”) ;
- Si une seule définition s’applique, alors la choisir et continuer l’exécution du programme ;
- Si plusieurs définitions s’appliquent, alors vérifier leurs priorités :
- S’il existe une définition qui est une exception à toutes les autres qui s’appliquent, la choisir et continuer l’exécution du programme ;
- Sinon, le programme plante avec une erreur (“définitions conflictuelles”).
Comme décrit ci-dessus, mettre exception dans un programme Catala modifie le comportement
du programme, en fournissant une priorité entre les définitions conditionnelles d’une
variable que Catala peut utiliser au moment de l’exécution lorsqu’il hésite entre plusieurs
définitions qui s’appliquent en même temps. Jusqu’à présent, nous avons vu une situation très simple
avec une définition de base (à l’article 2) et une seule exception (à
l’article 3). Mais le mécanisme d’exception peut être beaucoup plus large et aider à définir
différentes lignes de priorité parmi des dizaines de définitions conditionnelles différentes pour une
même variable. Explorons ce mécanisme sur un exemple plus complexe.
Gérer plusieurs exceptions
Il est fréquent dans les textes juridiques qu’un article établissant une règle générale soit suivi de plusieurs articles définissant des exceptions à la règle de base. En plus de l’article 3 et du taux d’imposition réduit pour les familles nombreuses, le CITC (Code des Impôts du Tutoriel Catala) définit en effet une exonération fiscale pour les individus à faible revenu, que nous pouvons coder comme une autre exception à la définition du taux d’imposition de l’article 2 :
Les individus gagnant moins de 10 000 € sont exonérés de l’impôt sur le revenu mentionné à l’article 1.
champ d'application CalculImpôtRevenu:
exception définition taux_imposition
sous condition individu.revenu <= 10 000 €
# Notez le <= au-dessus, la formulation de l'article 4 suggère d'utiliser <
# mais nous prenons toujours l'interprétation la plus favorable au
# contribuable !
conséquence égal à 0 %
Mais alors, que se passe-t-il lors du test du code avec un individu qui gagne moins de 10 000 € et a plus de 2 enfants ?
Pour tester ce qui se passe lorsque les règles des articles 3 et 4 sont en jeu,
vous pouvez tester le programme lorsqu’il y a trois enfants, et un revenu
inférieur à 10 000 € en modifiant le champ d’application Test comme suit :
champ d'application Test:
définition calcul égal à
résultat de CalculImpôtRevenu avec {
-- individu:
Individu {
-- revenu: 5 000 €
-- nombre_enfants: 3
}
}
L’exécution du programme produit l’erreur suivante à l’exécution :
$ clerk run tutoriel.catala_fr --scope=Test
┌─[ERROR]─
│
│ During evaluation: conflict between multiple valid consequences for assigning the same variable.
│
├─➤ tutoriel_fr.catala_fr
│ │
│ │ conséquence égal à 15 %
│ │ ‾‾‾‾
├─ Article 3
│
├─➤ tutoriel_fr.catala_fr
│ │
│ │ conséquence égal à 0 %
│ │ ‾‾‾
└─ Article 4
Dans cette situation, les deux définitions conditionnelles de l’article 3 et de l’article 4 s’appliquent, mais aussi la définition de base de l’article 2. Nous savons que l’article 3 et l’article 4 sont des exceptions à l’article 2, donc ils ont tous deux priorité sur lui. Mais nous ne savons pas quelle définition a la priorité entre l’article 3 et l’article 4, d’où le message d’erreur ci-dessus !
Dans cette situation, nous devons prioriser les exceptions entre elles.
La priorisation des exceptions nécessite une expertise et une recherche juridiques, car il n’est pas toujours évident de savoir quelle exception doit prévaloir dans une situation donnée. Ainsi, les messages d’erreur Catala indiquant un conflit lors de l’évaluation sont une invitation à appeler le juriste de votre équipe et à lui faire interpréter la spécification, plutôt que de résoudre le conflit vous-même.
Ici, parce que l’article 4 suit l’article 3, et parce qu’il est plus favorable au
contribuable de payer 0 € d’impôt plutôt que 15 % de son revenu, nous pouvons prendre la
décision juridique de prioriser l’exception de l’article 4 sur l’exception de
l’article 3. Maintenant, voyons comment écrire cela avec Catala. Parce que l’article 2 est
le cas de base pour l’exception de l’article 3, et l’article 3 est le cas de base pour
l’exception de l’article 4, nous devons donner aux définitions de taux_imposition aux
articles 2 et 3 une étiquette explicite afin que les mots-clés exception dans les articles
3 et 4 puissent faire référence à ces étiquettes :
Article 2
Le pourcentage fixe mentionné à l’article 1 est égal à 20 %.
champ d'application CalculImpôtRevenu:
# Le mot-clé "étiquette" introduit le nom de l'étiquette elle-même, ici
# "article_2".
étiquette article_2 définition taux_imposition égal à 20 %
Article 3
Si l’individu a la charge de 2 enfants ou plus, alors le pourcentage fixe mentionné à l’article 1 est égal à 15 %.
champ d'application CalculImpôtRevenu:
# Cette définition est précédée de deux indications :
# * elle a sa propre étiquette, "article_3" ;
# * cette définition est une exception à la définition étiquetée "article_2".
étiquette article_3 exception article_2
définition taux_imposition
sous condition individu.nombre_enfants >= 2
conséquence égal à 15 %
Article 4
Les individus gagnant moins de 10 000 € sont exonérés de l’impôt sur le revenu mentionné à l’article 1.
champ d'application CalculImpôtRevenu:
étiquette article_4 exception article_3
définition taux_imposition
sous condition individu.revenu < 10 000 €
conséquence égal à 0 %
Grâce aux étiquettes, nous pouvons définir des chaînes d’exceptions, où chaque définition est l’exception à la précédente, et le cas de base pour la suivante. Ce modèle est le plus courant dans les textes juridiques, et son comportement est simple : lorsque plusieurs définitions s’appliquent, choisir celle avec la priorité la plus élevée dans la chaîne. Voici une représentation de la chaîne d’exceptions dans notre exemple jusqu’à présent :
graph LR; A2["`article_2`"] A3["`article_3`"] A4["`article_4`"] A2-->A3 A3-->A4
Mais parfois, il n’est pas possible d’organiser les exceptions en chaîne, car l’interprétation juridique conduit à différentes branches d’exceptions.
Branches d’exceptions
Il peut être difficile de voir pourquoi certaines situations juridiques peuvent conduire à différentes branches d’exceptions. Donnons un exemple avec un nouvel article du CITC :
Les individus gagnant plus de 100 000 € sont soumis à un taux d’imposition de 30%, quel que soit leur nombre d’enfants.
champ d'application CalculImpôtRevenu:
étiquette article_5 exception article_3
définition taux_imposition
sous condition individu.revenu > 100 000 €
conséquence égal à 30 %
Maintenant, l’article 3 a deux exceptions : l’article 4 et l’article 5. Ces deux exceptions ont les conditions suivantes :
- Article 4 : revenu inférieur à 10 000 € ;
- Article 5 : revenu supérieur à 100 000 €.
Afficher les branches d’exception
Afficher les branches d’exception
À mesure que la base de code grandit, il devient de plus en plus difficile de visualiser toutes les définitions conditionnelles d’une variable ainsi que la priorisation entre elles. Pour aider, le compilateur Catala peut afficher les branches d’exception avec la commande suivante :
$ catala exceptions tutoriel.catala_fr --scope=CalculImpôtRevenu --variable=taux_imposition
┌[RESULT]─
│ Printing the tree of exceptions for the definitions of variable "taux_imposition" of scope "CalculImpôtRevenu".
└─
┌─[RESULT]─
│ Definitions with label "article_2":
│
├─➤ tutoriel.catala_fr
│ │
│ │ étiquette article_2 définition taux_imposition égal à 20 %
│ │ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
└─ Title
└─ Article 2
┌─[RESULT]─
│ Definitions with label "article_3":
│
├─➤ tutoriel.catala_fr
│ │
│ │ étiquette article_3 exception article_2 définition taux_imposition sous condition
│ │ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
└─ Title
└─ Article 3
┌─[RESULT]─
│ Definitions with label "article_4":
│
├─➤ tutoriel.catala_fr
│ │
│ │ étiquette article_4 exception article_3 définition taux_imposition sous condition
│ │ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
└─ Title
└─ Article 4
┌─[RESULT]─
│ Definitions with label "article_5":
│
├─➤ tutoriel.catala_fr
│ │
│ │ étiquette article_5 exception article_3 définition taux_imposition sous condition
│ │ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
└─ Title
└─ Article 5
┌─[RESULT]─
│ The exception tree structure is as follows:
│
│ "article_2"───"article_3"──┬──"article_5"
│ │
│ └──"article_4"
└─
Théoriquement, puisque les exceptions de l’article 4 et de l’article 5 ne sont pas priorisées l’une par rapport à l’autre, elles pourraient toutes deux s’appliquer en même temps et entrer en conflit. Cependant, puisque le revenu ne peut pas être à la fois inférieur à 10 000 € et supérieur à 100 000 €, le conflit ne peut pas se produire en pratique. Par conséquent, il n’est pas nécessaire de prioriser les deux exceptions, car elles vivent dans des branches conditionnelles mutuellement exclusives.
Pour tester ce qui se passe lorsque la règle de l’article 5 est en jeu,
vous pouvez tester le programme lorsqu’il y a 3 enfants, et un revenu
supérieur à 100 000 € en modifiant le champ d’application Test comme suit :
champ d'application Test:
définition calcul égal à
résultat de CalculImpôtRevenu avec {
-- individu:
Individu {
-- revenu: 200 000 €
-- nombre_enfants: 3
}
}
Le résultat de l’exécution est alors :
$ clerk run tutoriel.catala_fr --scope=Test
┌─[RESULT]─
│ calcul = CalculImpôtRevenu { -- impôt_revenu: 60 000,00 € }
└─
Le taux de 30% est respecté, puisque 200 000 € x 30% = 60 000 €.
Il est alors possible d’étendre ces branches séparément, par exemple avec un nouvel article du CITC :
Dans les territoires d’outre-mer, le taux d’imposition pour les individus gagnant plus de 100 000 € spécifié à l’article 5 est réduit à 25 %.
Cet article introduit une nouvelle information sur le calcul de l’impôt :
sommes-nous dans un territoire d’outre-mer ou non ? Nous pouvons le modéliser avec une nouvelle entrée dans le
champ d’application CalculImpôtRevenu, conduisant à une déclaration de champ d’application révisée :
déclaration champ d'application CalculImpôtRevenu:
entrée individu contenu Individu
entrée territoires_outre_mer contenu booléen
interne taux_imposition contenu décimal
résultat impôt_revenu contenu argent
Avec cette nouvelle variable d’entrée, le code pour l’article 6 est le suivant :
champ d'application CalculImpôtRevenu:
étiquette article_6 exception article_5
définition taux_imposition
sous condition individu.revenu > 100 000 € et territoires_outre_mer
conséquence égal à 25 %
Notez que dans la condition pour définir taux_imposition à l’article 6, nous
avons répété la condition individu.revenu > 100 000 € en conjonction
avec la nouvelle clause territoires_outre_mer. Dans notre CITC fictif, le texte
de l’article 6 est gentiment formulé et nous rappelle explicitement que cette exception
à l’article 5 ne s’applique que dans les situations qui déclenchent également l’article 5 (où
le revenu est supérieur à 100 000 €).
Cependant, le texte juridique peut parfois omettre cette information clé, ou la rendre implicite, créant un danger de mettre la mauvaise conditionnelle dans le code Catala en présence de branches d’exception. Supposons que nous ayons omis la condition de revenu dans le code pour l’article 6 :
champ d'application CalculImpôtRevenu:
étiquette article_6 exception article_5
définition taux_imposition
sous condition territoires_outre_mer
conséquence égal à 25 %
Avec ce code, la définition de l’article 6 se serait appliquée dans les territoires d’outre-mer pour tous les individus, y compris ceux gagnant moins de 100 000 € ! En effet, les définitions exceptionnelles en Catala n’héritent pas des conditions de leur cas de base : la condition de l’article 6 n’hérite pas de la condition de l’article 5, nous devons la répéter à l’article 6 si nous voulons avoir le bon schéma d’activation.
Enfin, nous pouvons récapituler la collection de branches d’exception sous forme d’arbre d’exceptions pour notre exemple :
graph LR; A2["`article_2`"] A3["`article_3`"] A4["`article_4`"] A5["`article_5`"] A6["`article_6`"] A2-->A3 A3-->A4 A3-->A5 A5-->A6
Regrouper les définitions conditionnelles pour les exceptions
Jusqu’à présent, nous avons vu comment définir des chaînes d’exceptions et des branches d’exceptions mutuellement exclusives. Mais il existe un schéma très courant qui introduit encore une autre manigance exceptionnelle. Supposons qu’en l’an 2000, une grande réforme fiscale modifie le taux d’imposition de base de l’article 2 avec une légère augmentation :
Le pourcentage fixe mentionné à l’article 1 est égal à 21 %.
Maintenant, il existe plusieurs stratégies pour gérer les mises à jour juridiques dans Catala, qui
sont résumées dans la section guide de ce livre. Mais ici,
nous supposerons que nous voulons que les deux versions de la loi (avant et après 2000)
coexistent dans le même programme Catala. Ce choix nous amène à introduire la
date courante comme une nouvelle entrée du champ d’application CalculImpôtRevenu :
déclaration champ d'application CalculImpôtRevenu:
entrée date_courante contenu date
entrée individu contenu Individu
entrée territoires_outre_mer contenu booléen
interne taux_imposition contenu décimal
résultat impôt_revenu contenu argent
Cette variable date_courante nous permettra d’introduire des définitions
conditionnelles mutuellement exclusives pour les deux versions différentes de l’article 2, chacune
s’activant uniquement avant ou après l’an 2000. Notez que si les deux définitions
de l’article 2 n’étaient pas mutuellement exclusives, elles pourraient entrer en conflit l’une avec l’autre,
vous obligeant à prioriser entre elles et à modifier la forme de l’arbre d’exceptions
global en introduisant une autre couche d’exception. Cependant, nous voulons dans
ce cas que ces deux définitions de base de l’article 2 soient collectivement le cas de base
pour toutes les exceptions ultérieures dans l’arbre d’exceptions de taux_imposition ! En
résumé, nous voulons l’arbre d’exceptions suivant :
graph LR; subgraph article_2 direction TB A2["`article_2 (avant 2000)`"] A2bis["`article_2 (après 2000)`"] end A3["`article_3`"] A4["`article_4`"] A5["`article_5`"] A6["`article_6`"] A2~~~A2bis article_2-->A3 A3-->A4 A3-->A5 A5-->A6
Catala est capable de représenter cet arbre d’exceptions, en regroupant
les deux définitions conditionnelles de l’article 2. En effet, puisque l’article 3
est une exception à l’étiquette article_2, il suffit de donner la même étiquette
article_2 aux deux définitions conditionnelles des deux versions de article_2 :
Article 2 (ancienne version avant 2000)
Le pourcentage fixe mentionné à l’article 1 est égal à 20 %.
champ d'application CalculImpôtRevenu:
étiquette article_2 définition taux_imposition
sous condition date_courante < |2000-01-01|
conséquence égal à 20 %
Article 2 (nouvelle version après 2000)
Le pourcentage fixe mentionné à l’article 1 est égal à 21 %.
champ d'application CalculImpôtRevenu:
# Utilisez simplement la même étiquette "article_2" que la définition précédente pour les regrouper
étiquette article_2 définition taux_imposition
sous condition date_courante >= |2000-01-01|
conséquence égal à 21 %
En utilisant le mécanisme de regroupement de définitions avec les branches d’exception, Catala est capable d’exprimer un large éventail de logique de texte juridique, et aide à garder le code aux côtés de sa spécification.
Récapitulons
Ceci conclut la deuxième section du tutoriel. En Catala, les variables peuvent être
définies par morceaux, chaque morceau de définition étant activé par une condition. Lorsque
plusieurs conditions s’appliquent, on peut prioriser les définitions conditionnelles en utilisant
les mots-clés exception et étiquette pour former des arbres d’exceptions capables de capturer la
logique complexe derrière les textes juridiques tout en correspondant à leur structure.
Récapitulatif de la section actuelle
Récapitulatif de la section actuelle
Pour référence, voici la version finale du code Catala consolidé à la fin de cette section du tutoriel.
# Corrigé du tutoriel
## Article 1
L'impôt sur le revenu pour un individu est défini comme un pourcentage fixe du
revenu de l'individu sur une année.
```catala
déclaration structure Individu:
donnée revenu contenu argent
donnée nombre_enfants contenu entier
déclaration champ d'application CalculImpôtRevenu:
entrée date_courante contenu date
entrée individu contenu Individu
entrée territoires_outre_mer contenu booléen
interne taux_imposition contenu décimal
résultat impôt_revenu contenu argent
champ d'application CalculImpôtRevenu:
définition impôt_revenu égal à
individu.revenu * taux_imposition
```
## Article 2
Le pourcentage fixe mentionné à l'article 1 est égal à 20 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_2
définition taux_imposition
sous condition date_courante < |2000-01-01|
conséquence égal à
20%
étiquette article_2
définition taux_imposition
sous condition date_courante >= |2000-01-01|
conséquence égal à
21%
```
## Article 3
Si l'individu a la charge de 2 enfants ou plus, alors le pourcentage fixe
mentionné à l'article 1 est égal à 15 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_3 exception article_2
définition taux_imposition
sous condition individu.nombre_enfants >= 2
conséquence égal à
15%
```
## Article 4
Les individus gagnant moins de 10 000 € sont exonérés de l'impôt sur le revenu mentionné
à l'article 1.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_4 exception article_3
définition taux_imposition
sous condition individu.revenu <= 10 000 €
conséquence égal à
0%
```
## Article 5
Les individus gagnant plus de 100 000 € sont soumis à un taux d'imposition de
30%, quel que soit leur nombre d'enfants.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_5 exception article_3
définition taux_imposition
sous condition individu.revenu > 100 000 €
conséquence égal à
30%
```
## Article 6
Dans les territoires d'outre-mer, le taux d'imposition pour les individus gagnant
plus de 100 000 € spécifié à l'article 5 est réduit à 25 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_6 exception article_5
définition taux_imposition
sous condition individu.revenu > 100 000 € et territoires_outre_mer
conséquence égal à
25%
```
## Test
```catala
déclaration champ d'application Test:
résultat calcul contenu CalculImpôtRevenu
champ d'application Test:
définition calcul égal à
résultat de CalculImpôtRevenu avec {
-- individu:
Individu {
-- revenu: 200 000 €
-- nombre_enfants: 3
}
-- territoires_outre_mer: faux
-- date_courante: |1999-01-01|
}
```
Listes et champs d’application
Dans cette section, le tutoriel aborde un schéma courant qui augmente considérablement la complexité d’une base de code : la nécessité de gérer des listes et des règles s’appliquant à chaque élément de la liste. Ici, nous apprenons comment effectuer des opérations avec des listes et déclarer un nouveau champ d’application pour traiter les règles s’appliquant à chaque élément de la liste.
Les deux dernières sections du tutoriel sont assez difficiles et impliquent une certaine complexité dans l’exemple. Cette complexité est nécessaire pour illustrer comment les fonctionnalités de Catala s’adaptent aux textes juridiques du monde réel qui impliquent des fonctionnalités complexes. Nous encourageons le lecteur à persévérer dans son étude de ces sections, et à poser toute question sur le chat en ligne de la communauté Catala.
Récapitulatif de la section précédente
Récapitulatif de la section précédente
Cette section du tutoriel s’appuie sur la précédente, et réutilisera le même exemple fil rouge, mais tout le code Catala nécessaire pour exécuter l’exemple est inclus ci-dessous pour référence.
# Corrigé du tutoriel
## Article 1
L'impôt sur le revenu pour un individu est défini comme un pourcentage fixe du
revenu de l'individu sur une année.
```catala
déclaration structure Individu:
donnée revenu contenu argent
donnée nombre_enfants contenu entier
déclaration champ d'application CalculImpôtRevenu:
entrée date_courante contenu date
entrée individu contenu Individu
entrée territoires_outre_mer contenu booléen
interne taux_imposition contenu décimal
résultat impôt_revenu contenu argent
champ d'application CalculImpôtRevenu:
définition impôt_revenu égal à
individu.revenu * taux_imposition
```
## Article 2
Le pourcentage fixe mentionné à l'article 1 est égal à 20 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_2
définition taux_imposition
sous condition date_courante < |2000-01-01|
conséquence égal à
20%
étiquette article_2
définition taux_imposition
sous condition date_courante >= |2000-01-01|
conséquence égal à
21%
```
## Article 3
Si l'individu a la charge de 2 enfants ou plus, alors le pourcentage fixe
mentionné à l'article 1 est égal à 15 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_3 exception article_2
définition taux_imposition
sous condition individu.nombre_enfants >= 2
conséquence égal à
15%
```
## Article 4
Les individus gagnant moins de 10 000 € sont exonérés de l'impôt sur le revenu mentionné
à l'article 1.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_4 exception article_3
définition taux_imposition
sous condition individu.revenu <= 10 000 €
conséquence égal à
0%
```
## Article 5
Les individus gagnant plus de 100 000 € sont soumis à un taux d'imposition de
30%, quel que soit leur nombre d'enfants.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_5 exception article_3
définition taux_imposition
sous condition individu.revenu > 100 000 €
conséquence égal à
30%
```
## Article 6
Dans les territoires d'outre-mer, le taux d'imposition pour les individus gagnant
plus de 100 000 € spécifié à l'article 5 est réduit à 25 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_6 exception article_5
définition taux_imposition
sous condition individu.revenu > 100 000 € et territoires_outre_mer
conséquence égal à
25%
```
## Test
```catala
déclaration champ d'application Test:
résultat calcul contenu CalculImpôtRevenu
champ d'application Test:
définition calcul égal à
résultat de CalculImpôtRevenu avec {
-- individu:
Individu {
-- revenu: 200 000 €
-- nombre_enfants: 3
}
-- territoires_outre_mer: faux
-- date_courante: |1999-01-01|
}
```
Créer un foyer à partir d’une liste d’individus
Précédemment, le Code des Impôts du Tutoriel Catala (CITC) a défini un impôt sur le revenu pour chaque individu et ses enfants. Mais maintenant, le CITC devient plus gourmand car une nouvelle taxe distincte similaire à la tristement célèbre poll tax de Thatcher est introduite. À sa création, l’impôt sur le foyer est tel que chaque individu d’un foyer est taxé d’une somme fixe, avec un taux réduit pour les enfants :
Lorsque plusieurs individus vivent ensemble, ils sont collectivement soumis à l’impôt sur le foyer. L’impôt sur le foyer dû est de 10 000 € par individu du foyer, et la moitié de ce montant par enfant.
Maintenant, implémenter cela en Catala nécessite d’aller au-delà du
champ d’application CalculImpôtRevenu que nous avons utilisé plus tôt. En effet, cette nouvelle taxe nécessite
un nouveau champ d’application, CalculImpôtFoyer ! Bien qu’il soit assez évident que le
résultat de ce nouveau champ d’application devrait être impôt_foyer, son entrée est la
collection d’individus qui composent le foyer.
Heureusement, Catala possède un type intégré pour les collections de choses, appelé liste,
même s’il se comporte plus comme un tableau dans le jargon informatique traditionnel.
déclaration champ d'application CalculImpôtFoyer:
# La syntaxe "liste de <X>" désigne le type dont les valeurs sont des listes d'
# éléments de type <X>.
entrée individus contenu liste de Individu
résultat impôt_foyer contenu argent
Pour définir impôt_foyer, nous devons maintenant :
- compter le nombre d’individus dans
individus; - compter le nombre d’enfants de chaque individu et additionner ces comptes ;
- multiplier ces comptes par le bon montant d’impôt.
Nous effectuerons chacune de ces étapes dans le corps de la définition de
impôt_foyer, dans le champ d’application CalculImpôtFoyer, en utilisant des variables
locales.
Lorsqu’une définition de variable devient complexe comme ci-dessus, il est souvent utile de séparer chaque étape en définissant des variables intermédiaires. Il y a deux façons de faire cela.
Premièrement, vous pouvez déclarer à l’intérieur de la déclaration du champ d’application une variable de champ d’application supplémentaire
avec l’étiquette interne au lieu de entrée ou résultat, comme vu dans
la première section du tutoriel.
Deuxièmement, si vous êtes sûr que vous n’aurez besoin de la variable intermédiaire que dans le contexte étroit d’une seule définition de variable de champ d’application, vous pouvez utiliser une variable locale à l’intérieur de la définition de la variable de champ d’application. Ces variables locales sont introduites et utilisées avec la syntaxe suivante :
# La ligne suivante définit la variable locale "x" comme étant égale à 4 * 5
soit x égal à 4 * 5 dans
# Nous pouvons ensuite utiliser "x" après le mot-clé "dans" dans le reste du code
x + 2
Pour l’étape 1, nous avons simplement besoin d’obtenir la longueur de la liste individus, ce qui
peut être fait via la syntaxe nombre de individus (la syntaxe pour toutes les opérations
sur les listes peut être trouvée dans l’aide-mémoire de la syntaxe
ou dans le guide de référence). Pour l’étape 2, nous
devons agréger le nombre d’enfants pour tous les individus, ce qui peut être fait
via la syntaxe somme entier de transforme chaque individu parmi individus en individu.nombre_enfants. Notez l’indication de type (entier) pour la somme, qui
indique que si la liste des individus est vide, alors l’entier 0 doit
être renvoyé. Enfin, nous pouvons assembler les étapes 1 et 2 pour l’étape 3 qui calcule
le montant de l’impôt :
champ d'application CalculImpôtFoyer:
définition impôt_foyer égal à
soit nombre_individus égal à nombre de individus dans
soit nombre_enfants égal à
somme entier de
transforme chaque individu parmi individus en individu.nombre_enfants
dans
10 000 €
* (
# "nombre_individus" est un entier, mais le résultat de la division
# est un décimal, et nous ne pouvons pas ajouter un entier et un décimal sans d'abord
# convertir l'un d'eux
décimal de nombre_individus + nombre_enfants / 2
)
Cette implémentation de l’article 7 est assez directe et concise. Elle fonctionne, mais notez un décalage subtil entre le texte de l’article 7 et son implémentation Catala : plutôt que d’agréger séparément la contribution de chaque individu et de ses enfants à l’impôt sur le foyer, nous comptons tous les individus d’un côté, et tous les enfants de l’autre. L’addition est commutative et associative donc ce décalage donne le même résultat. Cependant, ne pas suivre l’esprit de la loi dans l’implémentation pourrait ne pas être pérenne, comme nous le verrons juste en dessous…
Pour tester ce qui se passe lorsque la règle de l’article 7 est en jeu, vous pouvez tester le programme avec un foyer :
déclaration champ d'application TestFoyer:
résultat calcul contenu CalculImpôtFoyer
champ d'application TestFoyer:
définition calcul égal à
résultat de CalculImpôtFoyer avec {
-- individus:
# Le champ d'application attend une liste d'individus. En Catala, une liste est construite
# avec la syntaxe "[<élément 1>; <élément 2>; ...]".
[ Individu {
-- revenu: 15 000 €
-- nombre_enfants: 0
} ;
Individu {
-- revenu: 80 000 €
-- nombre_enfants: 2
} ]
}
Le résultat de l’exécution est alors :
$ clerk run tutoriel.catala_fr --scope=TestFoyer
┌─[RESULT]─
│ calcul = CalculImpôtFoyer { -- impôt_foyer: 30 000,00 € }
└─
Nous avons deux individus et deux enfants donc 2 x 10 000 € + 2 x 5 000 € = 30 000 €.
Refactoriser pour tenir compte de l’évolution des exigences
Traduire des textes juridiques en code exécutable est souvent des montagnes russes émotionnelles, car de nouvelles exigences dans des articles ultérieurs peuvent complètement briser les invariants et la structure de l’implémentation que vous avez utilisée dans les articles précédents. Aujourd’hui, le Code des Impôts du Tutoriel Catala (CITC) sera dur avec nous, avec l’article fatidique suivant :
Le montant de l’impôt sur le revenu payé par chaque individu peut être déduit de la part de l’impôt sur le foyer due par cet individu.
Maintenant, il existe plusieurs stratégies pour implémenter l’article 8, mais toutes ne sont pas juridiquement correctes. Une stratégie pourrait être de calculer le montant total de l’impôt sur le revenu dû par tous les individus du foyer, et de soustraire ce montant total d’ impôt sur le revenu du montant total de l’impôt sur le foyer pour effectuer la déduction. Cependant, cette stratégie est incorrecte, car la déduction de l’impôt sur le foyer pour un individu est implicitement plafonnée par le montant de l’impôt sur le foyer dû pour cet individu ! Ce plafonnement introduit une non-linéarité dans la formule qui empêche de réorganiser les additions et les soustractions tout en gardant les mêmes résultats dans toutes les configurations.
Supposons que vous ayez deux individus, A et B, sans enfants, dans un foyer
qui n’est pas situé dans un territoire d’outre-mer, avant l’an 2000.
Supposons aussi que le revenu de A est de 20 000 €, tandis que le revenu de B est de 200 000 €.
Selon les articles 1 à 6, A paiera 20 000 € x 20% = 4 000 € d’impôt sur le
revenu, tandis que B paiera 200 000 € x 30% = 60 000 €. Donc, au total, le foyer
paie 64 000 € d’impôt sur le revenu.
D’autre part, l’impôt sur le foyer dû par ce foyer est de 10 000 € + 10 000 € = 20 000 €. Comment appliquer l’article 8 dans cette situation ? Soustraire naïvement l’impôt sur le revenu total (64 000 €) de l’impôt sur le foyer total (20 000 €) donne un impôt sur le foyer révisé de 0 €, mais ce n’est pas le montant légal. En effet, la déduction ne peut avoir lieu qu’au niveau individuel.
A peut déduire 4 000 € de sa part de 10 000 € d’impôt sur le foyer, donc il doit
6 000 € d’impôt sur le foyer. Mais B ne peut déduire que 10 000 € de sa part de 10 000 €
d’impôt sur le foyer, le laissant avec 0 € d’impôt sur le foyer à payer, mais
pas -50 000 € ! C’est la non-linéarité en action.
Donc au total, le montant total correct d’impôt sur le foyer à payer ici est de 6 000 € et non 0 € comme la méthode de soustraction globale l’a calculé.
Nous sommes donc contraints de décomposer explicitement le calcul de l’impôt sur le foyer en
deux étapes : d’abord, calculer la part de l’impôt sur le foyer due par chaque individu,
puis agréger le résultat de la première étape pour tous les individus du
foyer. Naturellement, le champ d’application existant CalculImpôtFoyer est l’endroit où la
deuxième étape aura lieu. Mais où mettre la première étape ? Une refactorisation est nécessaire !
Oui !
Théoriquement, comme Catala vous permet de structurer le code en faisant correspondre la structure du texte juridique, l’ajout de nouveaux articles ne devrait pas nécessiter de changements dans les blocs de code antérieurs. C’est le cas par exemple lorsqu’un nouvel article définit une exception au cas de base d’une variable, comme nous l’avons expérimenté dans la deuxième section du tutoriel.
Mais l’ajout d’exceptions n’est pas la seule chose que les nouveaux articles peuvent introduire. Dans ce cas, nous voyons que l’article 8 rend explicite une étape de calcul qui était implicite ou cachée dans l’article 7 (à savoir, le calcul de la part de l’impôt sur le foyer pour chaque individu). Rendre cette étape de calcul explicite implique de lui donner un statut de premier ordre avec un concept Catala (une variable, un champ d’application, etc.), ce qui n’était peut-être pas le cas dans le code Catala écrit auparavant. Par conséquent, il est normal de refactoriser le code antérieur pour coder le nouvel article 8.
Cependant, le but de la refactorisation est toujours de faire correspondre aussi précisément que possible les étapes de calcul et les articles sur lesquels elles sont basées.
Comme cela s’est déjà produit pour l’article 8, les articles suivants sont susceptibles d’introduire des raffinements et des exceptions pour cette part de l’impôt sur le foyer. Dans ce cas, il est préférable d’utiliser un champ d’application à part entière pour représenter cette étape de calcul supplémentaire. Le champ d’application est lisible par les juristes et possède des fonctionnalités pratiques pour ajouter des paramètres d’entrée et de sortie, définir des exceptions pour ses variables locales, etc.
Par conséquent, nous opterons pour la création d’un tout nouveau champ d’application pour calculer la part de
l’impôt sur le foyer due par un individu, CalculImpôtFoyerIndividuel.
Le champ d’application manquant : calcul de l’impôt sur le foyer pour l’individu
Le nouveau champ d’application, CalculImpôtFoyerIndividuel, aura comme entrée un
individu et renverra comme résultat le montant de l’impôt sur le foyer détenu. Cependant,
à cause de l’article 8, le champ d’application devra également calculer le montant de l’impôt sur le
revenu dû par l’individu, pour le déduire de l’impôt sur le foyer. Le graphe d’appel
entre les champs d’application sera alors le suivant :
%%{init: {"flowchart": {"htmlLabels": true}} }%%
graph TD
A["`CalculImpôtFoyer`"]
B["`CalculImpôtFoyerIndividuel`"]
C["`CalculImpôtRevenu`"]
A-- appelle plusieurs fois -->B
B-- appelle une fois -->C
Par conséquent, nous aurons également besoin comme entrée de CalculImpôtFoyerIndividuel des
entrées nécessaires pour le champ d’application CalculImpôtRevenu de la section précédente
du tutoriel : territoires_outre_mer et
date_courante. Cela donne la déclaration de champ d’application suivante :
déclaration champ d'application CalculImpôtFoyerIndividuel:
entrée individu contenu Individu
entrée territoires_outre_mer contenu booléen
entrée date_courante contenu date
résultat impôt_foyer contenu argent
Maintenant, nous savons que nous devrons appeler CalculImpôtRevenu exactement une fois pour
calculer la déduction pour impôt_foyer. Il existe une méthode sur mesure conçue
pour la lisibilité par les juristes pour faire exactement cela en Catala !
Déclarer un appel de sous-champ d’application statique et définir les entrées de l’appel de sous-champ d’application
# L'appel unique et statique au sous-champ d'application "CalculImpôtRevenu" doit être
# déclaré dans "CalculImpôtFoyerIndividuel", nous répétons donc la
# déclaration du champ d'application ici avec une nouvelle ligne.
déclaration champ d'application CalculImpôtFoyerIndividuel:
entrée individu contenu Individu
entrée territoires_outre_mer contenu booléen
entrée date_courante contenu date
# La ligne suivante déclare un appel statique et unique au sous-champ d'application
# "CalculImpôtRevenu" avec le nom "calcul_impôt_revenu".
calcul_impôt_revenu champ d'application CalculImpôtRevenu
résultat impôt_foyer contenu argent
champ d'application CalculImpôtFoyerIndividuel:
# À l'intérieur d'un bloc "champ d'application", nous devons définir les arguments de l'appel au sous-champ d'application
# "calcul_impôt_revenu" : "individu", "territoires_outre_mer" et
# "territoires_outre_mer".
définition calcul_impôt_revenu.individu égal à
individu
# L'"individu" donné comme argument à "calcul_impôt_revenu",
# qui est l'appel à "CalculImpôtRevenu", est le même "individu"
# qui est l'entrée de "CalculImpôtFoyerIndividuel".
définition calcul_impôt_revenu.territoires_outre_mer égal à
territoires_outre_mer
# Ces lignes peuvent sembler tautologiques mais elles sont essentielles pour connecter
# les champs d'application aux sous-champs d'application de manière non ambiguë. Il est implicite que nous évaluons
# l'impôt sur le revenu pour la déduction à la même date que nous évaluons le montant de
# l'impôt sur le foyer, mais cette ligne le rend explicite. Parfois, vous pourriez vouloir
# appeler le calcul de l'impôt sur le revenu à une date antérieure (comme "date_courante
# - 5 an") en raison d'une exigence légale, et c'est là que vous spécifiez
# cela !
définition calcul_impôt_revenu.date_courante égal à
date_courante
C’est tout, l’appel au sous-champ d’application a été complètement configuré ! Le résultat
est maintenant accessible à calcul_impôt_revenu.impôt_revenu, puisque
“impôt_revenu” est la variable de résultat du sous-champ d’application CalculImpôtRevenu.
À ce stade, il est facile de définir impôt_foyer en un seul passage
à l’intérieur de CalculImpôtFoyerIndividuel :
champ d'application CalculImpôtFoyerIndividuel:
définition impôt_foyer égal à
soit impôt égal à
10 000 € * (1,0 + individu.nombre_enfants / 2)
dans
soit déduction égal à calcul_impôt_revenu.impôt_revenu dans
# N'oubliez pas de plafonner la déduction !
si déduction > impôt alors 0 € sinon impôt - déduction
Pour tester ce qui se passe lorsque la règle des articles 7 et 8 est en jeu vous pouvez tester le programme avec un individu :
déclaration champ d'application TestFoyerIndividuel:
résultat calcul contenu CalculImpôtFoyerIndividuel
champ d'application TestFoyerIndividuel:
définition calcul égal à
résultat de CalculImpôtFoyerIndividuel avec {
-- individu:
Individu {
-- revenu: 15 000 €
-- nombre_enfants: 0
}
-- date_courante: |1999-01-01|
-- territoires_outre_mer: faux
}
Le résultat de l’exécution est alors :
$ clerk run tutoriel.catala_fr --scope=TestFoyerIndividuel
┌─[RESULT]─
│ calcul = CalculImpôtFoyerIndividuel { -- impôt_foyer: 7 000,00 € }
└─
L’impôt sur le foyer dû par un individu sans enfants est de 10 000 €, mais nous devons déduire son impôt sur le revenu. Avec un revenu de 15 000 €, avant 2000 et pas dans un territoire d’outre-mer, le taux d’impôt sur le revenu est de 20% selon l’article 2, donc 3 000 € d’impôt sur le revenu. Par conséquent, l’impôt sur le foyer correct dû avec déduction est de 7 000 €.
Conclusion
Dans cette section du tutoriel, nous avons vu qu’en Catala, les listes d’éléments
sont représentées comme des valeurs avec leur propre type comme liste de argent ou
liste de Individu. Vous pouvez manipuler des listes avec des opérateurs
comme longueur, comptage, agrégation, mais aussi transformer, filtrer, etc. Veuillez vous référer
au guide de référence pour des informations sur tous les
opérateurs de liste disponibles en Catala. De plus, nous avons également vu dans
cette section du tutoriel que plutôt que d’programmer toutes les règles pour traiter
les éléments dans les listes à l’intérieur des opérateurs de liste, il est préférable de créer
un nouveau champ d’application pour écrire toutes les règles qui s’appliquent aux éléments de la liste. Cela nous a
permis de voir comment les champs d’application peuvent s’appeler les uns les autres et permettre une base de code
modulaire qui peut être refactorisée pour tenir compte de l’évolution des exigences légales.
Cependant, pour l’instant, notre implémentation des articles 7 et 8 n’est pas complète, car
il nous manque l’étape où CalculImpôtFoyer appelle
CalculImpôtFoyerIndividuel sur chaque individu du foyer pour
compléter le calcul de l’impôt sur le foyer avec la déduction correcte. Ce sera
le sujet de la prochaine et dernière section du
tutoriel.
Récapitulatif de la section actuelle
Récapitulatif de la section actuelle
Pour référence, voici la version finale du code Catala consolidé à la fin de cette section du tutoriel.
# Corrigé du tutoriel
## Article 1
L'impôt sur le revenu pour un individu est défini comme un pourcentage fixe du
revenu de l'individu sur une année.
```catala
déclaration structure Individu:
donnée revenu contenu argent
donnée nombre_enfants contenu entier
déclaration champ d'application CalculImpôtRevenu:
entrée date_courante contenu date
entrée individu contenu Individu
entrée territoires_outre_mer contenu booléen
interne taux_imposition contenu décimal
résultat impôt_revenu contenu argent
champ d'application CalculImpôtRevenu:
définition impôt_revenu égal à
individu.revenu * taux_imposition
```
## Article 2
Le pourcentage fixe mentionné à l'article 1 est égal à 20 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_2
définition taux_imposition
sous condition date_courante < |2000-01-01|
conséquence égal à
20%
étiquette article_2
définition taux_imposition
sous condition date_courante >= |2000-01-01|
conséquence égal à
21%
```
## Article 3
Si l'individu a la charge de 2 enfants ou plus, alors le pourcentage fixe
mentionné à l'article 1 est égal à 15 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_3 exception article_2
définition taux_imposition
sous condition individu.nombre_enfants >= 2
conséquence égal à
15%
```
## Article 4
Les individus gagnant moins de 10 000 € sont exonérés de l'impôt sur le revenu mentionné
à l'article 1.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_4 exception article_3
définition taux_imposition
sous condition individu.revenu <= 10 000 €
conséquence égal à
0%
```
## Article 5
Les individus gagnant plus de 100 000 € sont soumis à un taux d'imposition de
30%, quel que soit leur nombre d'enfants.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_5 exception article_3
définition taux_imposition
sous condition individu.revenu > 100 000 €
conséquence égal à
30%
```
## Article 6
Dans les territoires d'outre-mer, le taux d'imposition pour les individus gagnant
plus de 100 000 € spécifié à l'article 5 est réduit à 25 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_6 exception article_5
définition taux_imposition
sous condition individu.revenu > 100 000 € et territoires_outre_mer
conséquence égal à
25%
```
## Article 7
Lorsque plusieurs individus vivent ensemble, ils sont collectivement soumis à
l'impôt sur le foyer. L'impôt sur le foyer dû est de 10 000 € par individu du foyer,
et la moitié de ce montant par enfant.
```catala
déclaration champ d'application CalculImpôtFoyer:
entrée individus contenu liste de Individu
entrée territoires_outre_mer contenu booléen
entrée date_courante contenu date
résultat impôt_foyer contenu argent
champ d'application CalculImpôtFoyer:
définition impôt_foyer égal à
somme argent de
transforme chaque individu parmi individus en
(
résultat de CalculImpôtFoyerIndividuel avec {
-- individu: individu
-- territoires_outre_mer: territoires_outre_mer
-- date_courante: date_courante
}
).impôt_foyer
```
## Article 8
Le montant de l'impôt sur le revenu payé par chaque individu peut être déduit de la
part de l'impôt sur le foyer due par cet individu.
```catala
déclaration champ d'application CalculImpôtFoyerIndividuel:
entrée individu contenu Individu
entrée territoires_outre_mer contenu booléen
entrée date_courante contenu date
calcul_impôt_revenu champ d'application CalculImpôtRevenu
résultat impôt_foyer contenu argent
champ d'application CalculImpôtFoyerIndividuel:
définition calcul_impôt_revenu.individu égal à
individu
définition calcul_impôt_revenu.territoires_outre_mer égal à
territoires_outre_mer
définition calcul_impôt_revenu.date_courante égal à
date_courante
définition impôt_foyer égal à
soit impôt égal à
10 000 € * (1,0 + individu.nombre_enfants / 2)
dans
soit déduction égal à calcul_impôt_revenu.impôt_revenu dans
si déduction > impôt alors 0 € sinon impôt - déduction
```
## Test
```catala
déclaration champ d'application TestFoyerIndividuel:
résultat calcul contenu CalculImpôtFoyerIndividuel
champ d'application TestFoyerIndividuel:
définition calcul égal à
résultat de CalculImpôtFoyerIndividuel avec {
-- individu:
Individu {
-- revenu: 15 000 €
-- nombre_enfants: 0
}
-- date_courante: |1999-01-01|
-- territoires_outre_mer: faux
}
```
États de variables et appels dynamiques de champs d’application
Introduction
Dans cette section, le tutoriel reprend là où la section précédente s’est arrêtée, c’est-à-dire l’implémentation du calcul de l’impôt sur le foyer pour un individu. Maintenant, nous devons encore agréger les calculs pour les individus au niveau du foyer pour générer l’impôt total sur le foyer.
Faire cette agrégation nécessitera d’appeler le champ d’application CalculImpôtFoyerIndividuel
plusieurs fois pour une agrégation de liste à l’intérieur de CalculImpôtFoyer. Nous
couvrirons ce sujet, mais d’abord nous devons régler les affaires inachevées
de la dernière section du tutoriel !
Récapitulatif de la section précédente
Récapitulatif de la section précédente
Cette section du tutoriel s’appuie sur la précédente, et réutilisera le même exemple fil rouge, mais tout le code Catala nécessaire pour exécuter l’exemple est inclus ci-dessous pour référence.
# Corrigé du tutoriel
## Article 1
L'impôt sur le revenu pour un individu est défini comme un pourcentage fixe du
revenu de l'individu sur une année.
```catala
déclaration structure Individu:
donnée revenu contenu argent
donnée nombre_enfants contenu entier
déclaration champ d'application CalculImpôtRevenu:
entrée date_courante contenu date
entrée individu contenu Individu
entrée territoires_outre_mer contenu booléen
interne taux_imposition contenu décimal
résultat impôt_revenu contenu argent
champ d'application CalculImpôtRevenu:
définition impôt_revenu égal à
individu.revenu * taux_imposition
```
## Article 2
Le pourcentage fixe mentionné à l'article 1 est égal à 20 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_2
définition taux_imposition
sous condition date_courante < |2000-01-01|
conséquence égal à
20%
étiquette article_2
définition taux_imposition
sous condition date_courante >= |2000-01-01|
conséquence égal à
21%
```
## Article 3
Si l'individu a la charge de 2 enfants ou plus, alors le pourcentage fixe
mentionné à l'article 1 est égal à 15 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_3 exception article_2
définition taux_imposition
sous condition individu.nombre_enfants >= 2
conséquence égal à
15%
```
## Article 4
Les individus gagnant moins de 10 000 € sont exonérés de l'impôt sur le revenu mentionné
à l'article 1.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_4 exception article_3
définition taux_imposition
sous condition individu.revenu <= 10 000 €
conséquence égal à
0%
```
## Article 5
Les individus gagnant plus de 100 000 € sont soumis à un taux d'imposition de
30%, quel que soit leur nombre d'enfants.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_5 exception article_3
définition taux_imposition
sous condition individu.revenu > 100 000 €
conséquence égal à
30%
```
## Article 6
Dans les territoires d'outre-mer, le taux d'imposition pour les individus gagnant
plus de 100 000 € spécifié à l'article 5 est réduit à 25 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_6 exception article_5
définition taux_imposition
sous condition individu.revenu > 100 000 € et territoires_outre_mer
conséquence égal à
25%
```
## Article 7
Lorsque plusieurs individus vivent ensemble, ils sont collectivement soumis à
l'impôt sur le foyer. L'impôt sur le foyer dû est de 10 000 € par individu du foyer,
et la moitié de ce montant par enfant.
```catala
déclaration champ d'application CalculImpôtFoyer:
entrée individus contenu liste de Individu
entrée territoires_outre_mer contenu booléen
entrée date_courante contenu date
résultat impôt_foyer contenu argent
champ d'application CalculImpôtFoyer:
définition impôt_foyer égal à
somme argent de
transforme chaque individu parmi individus en
(
résultat de CalculImpôtFoyerIndividuel avec {
-- individu: individu
-- territoires_outre_mer: territoires_outre_mer
-- date_courante: date_courante
}
).impôt_foyer
```
## Article 8
Le montant de l'impôt sur le revenu payé par chaque individu peut être déduit de la
part de l'impôt sur le foyer due par cet individu.
```catala
déclaration champ d'application CalculImpôtFoyerIndividuel:
entrée individu contenu Individu
entrée territoires_outre_mer contenu booléen
entrée date_courante contenu date
calcul_impôt_revenu champ d'application CalculImpôtRevenu
résultat impôt_foyer contenu argent
champ d'application CalculImpôtFoyerIndividuel:
définition calcul_impôt_revenu.individu égal à
individu
définition calcul_impôt_revenu.territoires_outre_mer égal à
territoires_outre_mer
définition calcul_impôt_revenu.date_courante égal à
date_courante
définition impôt_foyer égal à
soit impôt égal à
10 000 € * (1,0 + individu.nombre_enfants / 2)
dans
soit déduction égal à calcul_impôt_revenu.impôt_revenu dans
si déduction > impôt alors 0 € sinon impôt - déduction
```
## Test
```catala
déclaration champ d'application TestFoyerIndividuel:
résultat calcul contenu CalculImpôtFoyerIndividuel
champ d'application TestFoyerIndividuel:
définition calcul égal à
résultat de CalculImpôtFoyerIndividuel avec {
-- individu:
Individu {
-- revenu: 15 000 €
-- nombre_enfants: 0
}
-- date_courante: |1999-01-01|
-- territoires_outre_mer: faux
}
```
États de variables
Rappelez-vous que nous avons défini impôt_foyer en un seul passage
à l’intérieur de CalculImpôtFoyerIndividuel :
champ d'application CalculImpôtFoyerIndividuel:
définition impôt_foyer égal à
soit impôt égal à
10 000 € * (1,0 + individu.nombre_enfants / 2)
dans
soit déduction égal à calcul_impôt_revenu.impôt_revenu dans
# N'oubliez pas de plafonner la déduction !
si déduction > impôt alors 0 € sinon impôt - déduction
Cependant, faire cela fusionne les spécifications de l’article 7 et de l’article 8,
ce qui va à l’encontre de l’esprit de Catala de diviser le code dans la même structure
que le texte juridique. Donc, au lieu d’utiliser deux variables locales à l’intérieur de la définition
de impôt_foyer, nous voulons diviser la formule en deux définition distinctes.
Intuitivement, cela implique de créer deux variables de champ d’application dans
CalculImpôtFoyerIndividuel, impôt_foyer_base (pour l’article 7) et
impôt_foyer_avec_déduction (article 8). Mais en réalité, cela revient à donner
deux états consécutifs pour la variable impôt_foyer, et les juristes comprennent
mieux le code de cette façon ! Donc Catala a une fonctionnalité pour vous permettre de faire exactement cela :
déclaration champ d'application CalculImpôtFoyerIndividuel:
entrée individu contenu Individu
entrée territoires_outre_mer contenu booléen
entrée date_courante contenu date
calcul_impôt_revenu champ d'application CalculImpôtRevenu
résultat impôt_foyer contenu argent
# Les différents états pour la variable "impôt_foyer" sont déclarés ici,
# dans l'ordre exact où vous vous attendez à ce qu'ils soient calculés !
état base
état avec_déduction
Avec nos deux états base et avec_déduction, nous pouvons coder les articles 7 et
8 :
Article 7
Lorsque plusieurs individus vivent ensemble, ils sont collectivement soumis à l’impôt sur le foyer. L’impôt sur le foyer dû est de 10 000 € par individu du foyer, et la moitié de ce montant par enfant.
champ d'application CalculImpôtFoyerIndividuel:
définition impôt_foyer état base égal à
10 000 € * (1,0 + individu.nombre_enfants / 2)
Article 8
Le montant de l’impôt sur le revenu payé par chaque individu peut être déduit de la part de l’impôt sur le foyer due par cet individu.
champ d'application CalculImpôtFoyerIndividuel:
définition impôt_foyer état avec_déduction égal à
# Ci-dessous, "impôt_foyer" fait référence à la valeur de "impôt_foyer" calculée
# dans l'état précédent, donc ici l'état "base" qui précède immédiatement
# l'état "avec_déduction" dans la déclaration.
si calcul_impôt_revenu.impôt_revenu > impôt_foyer alors 0 €
sinon
impôt_foyer - calcul_impôt_revenu.impôt_revenu
# Il est également possible de faire référence aux états de variables explicitement avec la
# syntaxe "impôt_foyer état base".
Ailleurs dans CalculImpôtFoyerIndividuel, utiliser impôt_foyer fera
implicitement référence au dernier état de la variable (donc ici avec_déduction),
correspondant à la convention implicite habituelle dans les textes juridiques.
Ceci complète notre implémentation de CalculImpôtFoyerIndividuel ! Sa
variable de résultat impôt_foyer contient maintenant la part de l’impôt sur le foyer due par
chaque individu du foyer, avec la déduction correcte de l’impôt sur le revenu.
Nous pouvons maintenant l’utiliser dans le calcul de l’impôt global sur le foyer dans
CalculImpôtFoyer.
Relier les champs d’application ensemble via la transformation de liste
Nous pouvons maintenant finir de coder l’article 7 en additionnant chaque part de l’
impôt sur le foyer détenue par tous les individus du foyer. Nous ferons
cela via l’agrégation de liste, comme précédemment, mais les éléments de la liste à
agréger sont maintenant le résultat de l’appel de CalculImpôtFoyerIndividuel
sur chaque individu. Précédemment, nous avons montré comment appeler un sous-champ d’application
statiquement et exactement une fois. Mais ici, ce n’est pas ce que nous voulons : nous voulons
appeler le sous-champ d’application autant de fois qu’il y a d’individus dans le foyer.
Nous devons alors utiliser une méthode différente pour appeler le sous-champ d’application :
Avec toutes nos refactorisations, la déclaration du champ d’application CalculImpôtFoyer
peut être simplifiée (nous n’avons plus besoin de la variable de fonction part_impôt_foyer) :
déclaration champ d'application CalculImpôtFoyer:
entrée individus contenu liste de Individu
entrée territoires_outre_mer contenu booléen
entrée date_courante contenu date
résultat impôt_foyer contenu argent
Ensuite, la définition de impôt_foyer pourrait être réécrite comme suit à côté
de l’article 7 :
champ d'application CalculImpôtFoyer:
définition impôt_foyer égal à
somme argent de
transforme chaque individu parmi individus en (
# Ci-dessous se trouve la syntaxe pour appeler le sous-champ d'application
# "CalculImpôtFoyerIndividuel" dynamiquement, sur place.
# après "avec" se trouve la liste des entrées du champ d'application.
résultat de CalculImpôtFoyerIndividuel avec {
# Les trois lignes suivantes sont tautologiques dans cet exemple, car
# les noms des paramètres et les noms des variables de champ d'application
# sont identiques, mais les valeurs des paramètres d'appel de champ d'application peuvent être
# arbitrairement complexes !
-- individu: individu # <- ce dernier "individu" est la variable de transformation
-- territoires_outre_mer: territoires_outre_mer
-- date_courante: date_courante
}
# La construction "résultat de <X> avec { ... }" renvoie une structure
# contenant toutes les variables de résultat du champ d'application <X>. Par conséquent, nous accédons
# à la variable de résultat "impôt_foyer" du champ d'application
# "CalculImpôtFoyerIndividuel" avec la syntaxe d'accès au champ
# ".impôt_foyer".
).impôt_foyer
C’est tout ! Nous avons fini d’implémenter l’article 7 et l’article 8 de manière propre, extensible et pérenne en utilisant une série de champs d’application qui s’appellent les uns les autres.
Tester et déboguer le calcul
Nous avons écrit un code assez complexe dans cette section du tutoriel, il est grand temps de le tester et de le déboguer. De la même manière que le test présenté dans la première section du tutoriel, nous pouvons déclarer un nouveau champ d’application de test pour le calcul de l’impôt sur le foyer, et l’exécuter :
déclaration champ d'application TestFoyer:
résultat calcul contenu CalculImpôtFoyer
champ d'application TestFoyer:
définition calcul égal à
résultat de CalculImpôtFoyer avec {
-- individus:
[ Individu {
-- revenu: 15 000 €
-- nombre_enfants: 0
} ;
Individu {
-- revenu: 80 000 €
-- nombre_enfants: 2
} ]
-- territoires_outre_mer: faux
-- date_courante: |1999-01-01|
}
$ clerk run tutoriel.catala_fr --scope=TestFoyer
┌─[RESULT]─
│ calcul = CalculImpôtFoyer { -- impôt_foyer: 21 500,00 € }
└─
Le résultat du test est-il correct ? Voyons cela en déroulant le calcul manuellement :
- L’impôt sur le foyer pour deux individus et deux enfants est
2 * 10 000 € + 2 * 5 000 €, soit 30 000 € ; - Le premier individu gagne plus de 10 000 €, moins de 100 000 €, n’a pas d’enfants et nous sommes avant l’an 2000, donc le taux d’impôt sur le revenu est de 20 % selon l’article 2 et son impôt sur le revenu est de 3 000 € ;
- La part de l’impôt sur le foyer pour le premier individu est de 10 000 €, donc la déduction pour le premier individu est la totalité des 3 000 € ;
- Le deuxième individu gagne plus de 10 000 €, moins de 100 000 €, mais a deux enfants donc le taux d’impôt sur le revenu est de 15 % selon l’article 3 et son impôt sur le revenu est de 12 000 € ;
- La part de l’impôt sur le foyer pour le deuxième individu est de 20 000 €, donc la déduction pour le deuxième individu est la totalité des 12 000 € ;
- La déduction totale est donc de 15 000 €, qui est plafonnée à 8 500 € selon l’article 9 ;
- Appliquer la déduction à l’impôt sur le foyer de base donne 21 500 €.
Jusqu’ici tout va bien, le résultat du test est correct. Mais il aurait pu arriver au
bon résultat en prenant les mauvaises étapes intermédiaires, donc nous voudrons
les inspecter. Heureusement, l’interpréteur Catala peut imprimer la trace complète
du calcul à cette fin. Voici la sortie sur l’interprétation
de TestFoyer :
Trace de TestFoyer
Trace de TestFoyer
$ clerk run tutoriel.catala_fr --scope=TestFoyer -c--trace
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition calcul égal à
│ ‾‾‾‾‾‾
Test
[LOG] → CalculImpôtFoyer.direct
[LOG] ≔ CalculImpôtFoyer.direct.
entrée: CalculImpôtFoyer_in { -- individus_in: [Individu { -- revenu: 15 000,00 € -- nombre_enfants: 0 }; Individu { -- revenu: 80 000,00 € -- nombre_enfants: 2 }] -- territoires_outre_mer_in: faux -- date_courante_in: 1999-01-01 }
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition parts_impôt_foyer égal à
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 7
[LOG] → CalculImpôtFoyerIndividuel.direct
[LOG] ≔ CalculImpôtFoyerIndividuel.direct.
entrée: CalculImpôtFoyerIndividuel_in { -- individu_in: Individu { -- revenu: 15 000,00 € -- nombre_enfants: 0 } -- territoires_outre_mer_in: faux -- date_courante_in: 1999-01-01 }
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition impôt_foyer égal à
│ ‾‾‾‾‾‾‾‾‾‾‾‾
Article 7
[LOG] ≔ CalculImpôtFoyerIndividuel.impôt_foyer: 10 000,00 €
[LOG] → CalculImpôtRevenu.direct
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition calcul_impôt_revenu.date_courante égal à
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition calcul_impôt_revenu.individu égal à
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition calcul_impôt_revenu.territoires_outre_mer égal à
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ≔ CalculImpôtRevenu.direct.
entrée: CalculImpôtRevenu_in { -- date_courante_in: 1999-01-01 -- individu_in: Individu { -- revenu: 15 000,00 € -- nombre_enfants: 0 } -- territoires_outre_mer_in: faux }
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ date_courante < |2000-01-01|
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 2 (ancienne version avant 2000)
[LOG] ≔ CalculImpôtRevenu.taux_imposition: 0,2
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition impôt_revenu égal à
│ ‾‾‾‾‾‾‾‾‾‾‾‾
Article 1
[LOG] ≔ CalculImpôtRevenu.impôt_revenu: 3 000,00 €
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ calcul_impôt_revenu champ d'application CalculImpôtRevenu
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 7
[LOG] ≔ CalculImpôtRevenu.direct.
résultat: CalculImpôtRevenu { -- impôt_revenu: 3 000,00 € }
[LOG] ← CalculImpôtRevenu.direct
[LOG] ≔ CalculImpôtFoyerIndividuel.
calcul_impôt_revenu: CalculImpôtRevenu { -- impôt_revenu: 3 000,00 € }
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition déduction égal à
│ ‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ≔ CalculImpôtFoyerIndividuel.déduction: 3 000,00 €
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ résultat de CalculImpôtFoyerIndividuel avec {
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- individu: individu
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- territoires_outre_mer: territoires_outre_mer
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- date_courante: date_courante
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ }
│ ‾
Article 7
[LOG] ≔ CalculImpôtFoyerIndividuel.direct.
résultat: CalculImpôtFoyerIndividuel { -- impôt_foyer: 10 000,00 € -- déduction: 3 000,00 € }
[LOG] ← CalculImpôtFoyerIndividuel.direct
[LOG] → CalculImpôtFoyerIndividuel.direct
[LOG] ≔ CalculImpôtFoyerIndividuel.direct.
entrée: CalculImpôtFoyerIndividuel_in { -- individu_in: Individu { -- revenu: 80 000,00 € -- nombre_enfants: 2 } -- territoires_outre_mer_in: faux -- date_courante_in: 1999-01-01 }
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition impôt_foyer égal à
│ ‾‾‾‾‾‾‾‾‾‾‾‾
Article 7
[LOG] ≔ CalculImpôtFoyerIndividuel.impôt_foyer: 20 000,00 €
[LOG] → CalculImpôtRevenu.direct
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition calcul_impôt_revenu.date_courante égal à
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition calcul_impôt_revenu.individu égal à
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition calcul_impôt_revenu.territoires_outre_mer égal à
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ≔ CalculImpôtRevenu.direct.
entrée: CalculImpôtRevenu_in { -- date_courante_in: 1999-01-01 -- individu_in: Individu { -- revenu: 80 000,00 € -- nombre_enfants: 2 } -- territoires_outre_mer_in: faux }
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ individu.nombre_enfants >= 2
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 3
[LOG] ≔ CalculImpôtRevenu.taux_imposition: 0,15
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition impôt_revenu égal à
│ ‾‾‾‾‾‾‾‾‾‾‾‾
Article 1
[LOG] ≔ CalculImpôtRevenu.impôt_revenu: 12 000,00 €
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ calcul_impôt_revenu champ d'application CalculImpôtRevenu
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 7
[LOG] ≔ CalculImpôtRevenu.direct.
résultat: CalculImpôtRevenu { -- impôt_revenu: 12 000,00 € }
[LOG] ← CalculImpôtRevenu.direct
[LOG] ≔ CalculImpôtFoyerIndividuel.
calcul_impôt_revenu: CalculImpôtRevenu { -- impôt_revenu: 12 000,00 € }
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition déduction égal à
│ ‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ≔ CalculImpôtFoyerIndividuel.déduction: 12 000,00 €
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ résultat de CalculImpôtFoyerIndividuel avec {
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- individu: individu
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- territoires_outre_mer: territoires_outre_mer
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- date_courante: date_courante
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ }
│ ‾
Article 7
[LOG] ≔ CalculImpôtFoyerIndividuel.direct.
résultat: CalculImpôtFoyerIndividuel { -- impôt_foyer: 20 000,00 € -- déduction: 12 000,00 € }
[LOG] ← CalculImpôtFoyerIndividuel.direct
[LOG] ≔ CalculImpôtFoyer.
parts_impôt_foyer: [CalculImpôtFoyerIndividuel { -- impôt_foyer: 10 000,00 € -- déduction: 3 000,00 € }; CalculImpôtFoyerIndividuel { -- impôt_foyer: 20 000,00 € -- déduction: 12 000,00 € }]
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition impôt_foyer
│ ‾‾‾‾‾‾‾‾‾‾‾‾
Article 7
[LOG] ≔ CalculImpôtFoyer.impôt_foyer#base: 30 000,00 €
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition déduction_totale
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ≔ CalculImpôtFoyer.déduction_totale#base: 15 000,00 €
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition déduction_totale
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 9
[LOG] ≔ CalculImpôtFoyer.déduction_totale#plafonnée: 8 500,00 €
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ définition impôt_foyer
│ ‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ≔ CalculImpôtFoyer.impôt_foyer#déduction: 21 500,00 €
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr
│
│ résultat de CalculImpôtFoyer avec {
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- individus:
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾
│ [ Individu {
│ ‾‾‾‾‾‾‾‾‾‾‾‾
│ -- revenu: 15 000 €
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- nombre_enfants: 0
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ } ;
│ ‾‾‾
│ Individu {
│ ‾‾‾‾‾‾‾‾‾‾
│ -- revenu: 80 000 €
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- nombre_enfants: 2
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ } ]
│ ‾‾‾
│ -- territoires_outre_mer: faux
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- date_courante: |1999-01-01|
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ }
│ ‾
Test
[LOG] ≔ CalculImpôtFoyer.direct.
résultat: CalculImpôtFoyer { -- impôt_foyer: 21 500,00 € }
[LOG] ← CalculImpôtFoyer.direct
[LOG] ≔ TestFoyer.calcul: CalculImpôtFoyer { -- impôt_foyer: 21 500,00 € }
L’inspection de la trace révèle la structure du calcul qui correspond étroitement au raisonnement juridique que nous avons fait juste au-dessus pour calculer la sortie du test manuellement. Avec cet outil puissant, il est possible de déboguer et de maintenir des programmes Catala à grande échelle.
Conclusion
Félicitations pour avoir terminé le tutoriel Catala ! Les deux dernières sections n’ont pas présenté de fonctionnalités uniques à Catala, contrairement aux exceptions de la deuxième section. Au contraire, en Catala, nous utilisons les techniques classiques de génie logiciel de la programmation fonctionnelle pour diviser le code en plusieurs fonctions qui s’appellent les unes les autres au bon niveau d’ abstraction, dans le but de garder le code proche de là où il est spécifié dans la loi. Il existe différentes façons d’exprimer quelque chose en Catala, mais la proximité entre le code et la spécification juridique devrait être le critère pour ce qui est la manière idiomatique de faire les choses.
Refactoriser le code Catala en continu à mesure que de nouvelles exigences légales sont ajoutées ou mises à jour est la clé pour maintenir la base de code efficacement sur le long terme, et éviter le code spaghetti qui est courant lors de la traduction de la loi en code. Nous espérons que ce tutoriel vous a mis sur la bonne voie pour votre voyage dans Catala et le monde merveilleux de l’automatisation sûre et fidèle des dispositions légales.
Nous vous encourageons à lire les prochains chapitres de ce livre pour continuer à apprendre comment utiliser Catala, car le tutoriel n’est pas situé dans une configuration de projet de développement logiciel réel, et manque de beaucoup de conseils sur le codage en Catala mais aussi l’interaction avec les juristes.
Récapitulatif de la section actuelle
Récapitulatif de la section actuelle
Pour référence, voici la version finale du code Catala consolidé à la fin de cette section du tutoriel.
# Corrigé du tutoriel
## Article 1
L'impôt sur le revenu pour un individu est défini comme un pourcentage fixe du
revenu de l'individu sur une année.
```catala
déclaration structure Individu:
donnée revenu contenu argent
donnée nombre_enfants contenu entier
déclaration champ d'application CalculImpôtRevenu:
entrée date_courante contenu date
entrée individu contenu Individu
entrée territoires_outre_mer contenu booléen
interne taux_imposition contenu décimal
résultat impôt_revenu contenu argent
champ d'application CalculImpôtRevenu:
définition impôt_revenu égal à
individu.revenu * taux_imposition
```
## Article 2
Le pourcentage fixe mentionné à l'article 1 est égal à 20 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_2
définition taux_imposition
sous condition date_courante < |2000-01-01|
conséquence égal à
20%
étiquette article_2
définition taux_imposition
sous condition date_courante >= |2000-01-01|
conséquence égal à
21%
```
## Article 3
Si l'individu a la charge de 2 enfants ou plus, alors le pourcentage fixe
mentionné à l'article 1 est égal à 15 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_3 exception article_2
définition taux_imposition
sous condition individu.nombre_enfants >= 2
conséquence égal à
15%
```
## Article 4
Les individus gagnant moins de 10 000 € sont exonérés de l'impôt sur le revenu mentionné
à l'article 1.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_4 exception article_3
définition taux_imposition
sous condition individu.revenu <= 10 000 €
conséquence égal à
0%
```
## Article 5
Les individus gagnant plus de 100 000 € sont soumis à un taux d'imposition de
30%, quel que soit leur nombre d'enfants.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_5 exception article_3
définition taux_imposition
sous condition individu.revenu > 100 000 €
conséquence égal à
30%
```
## Article 6
Dans les territoires d'outre-mer, le taux d'imposition pour les individus gagnant
plus de 100 000 € spécifié à l'article 5 est réduit à 25 %.
```catala
champ d'application CalculImpôtRevenu:
étiquette article_6 exception article_5
définition taux_imposition
sous condition individu.revenu > 100 000 € et territoires_outre_mer
conséquence égal à
25%
```
## Article 7
Lorsque plusieurs individus vivent ensemble, ils sont collectivement soumis à
l'impôt sur le foyer. L'impôt sur le foyer dû est de 10 000 € par individu du foyer,
et la moitié de ce montant par enfant.
```catala
déclaration champ d'application CalculImpôtFoyer:
entrée individus contenu liste de Individu
entrée territoires_outre_mer contenu booléen
entrée date_courante contenu date
résultat impôt_foyer contenu argent
champ d'application CalculImpôtFoyerIndividuel:
définition calcul_impôt_revenu.individu égal à
individu
définition calcul_impôt_revenu.territoires_outre_mer égal à
territoires_outre_mer
définition calcul_impôt_revenu.date_courante égal à
date_courante
définition impôt_foyer égal à
10 000 € * (1,0 + individu.nombre_enfants / 2)
champ d'application CalculImpôtFoyer:
définition impôt_foyer
égal à
somme argent de
transforme chaque individu parmi individus en
(
résultat de CalculImpôtFoyerIndividuel avec {
-- individu: individu
-- territoires_outre_mer: territoires_outre_mer
-- date_courante: date_courante
}
).impôt_foyer
```
## Article 8
Le montant de l'impôt sur le revenu payé par chaque individu peut être déduit de la
part de l'impôt sur le foyer due par cet individu.
```catala
déclaration champ d'application CalculImpôtFoyerIndividuel:
entrée individu contenu Individu
entrée territoires_outre_mer contenu booléen
entrée date_courante contenu date
calcul_impôt_revenu champ d'application CalculImpôtRevenu
résultat impôt_foyer contenu argent
résultat déduction contenu argent
champ d'application CalculImpôtFoyerIndividuel:
définition déduction égal à
si calcul_impôt_revenu.impôt_revenu > impôt_foyer alors impôt_foyer
sinon calcul_impôt_revenu.impôt_revenu
```
## Test
```catala
déclaration champ d'application TestFoyer:
résultat calcul contenu CalculImpôtFoyer
champ d'application TestFoyer:
définition calcul égal à
résultat de CalculImpôtFoyer avec {
-- individus:
[
Individu {
-- revenu: 15 000 €
-- nombre_enfants: 0
};
Individu {
-- revenu: 80 000 €
-- nombre_enfants: 2
}
]
-- territoires_outre_mer: faux
-- date_courante: |1999-01-01|
}
```
Guide pas à pas : configurer un projet Catala
Bien que le tutoriel Catala vous initie à la programmation en Catala et aux spécificités de la traduction des exigences juridiques dans votre code, il lui manque tous les aspects d’un projet informatique au-delà de la simple écriture du code.
Plus précisément, l’équipe Catala encourage les développeurs Catala à utiliser les standards
modernes de génie logiciel en ce qui concerne l’organisation et la gestion de projet.
Cette section agit comme un guide pas à pas qui montre principalement comment le système de construction Catala, clerk,
facilite les processus de structuration, de test et de déploiement d’un projet informatique utilisant Catala.
Ce guide suppose que le lecteur travaille sur un système de type Unix et a un niveau minimum de pratique de la ligne de commande et des opérations système de base. Les deuxième, troisième et quatrième sections supposent également une connaissance de base des travaux de génie logiciel moderne, et une familiarité avec des plateformes comme Gitlab ou Github.
Ce guide est structuré séquentiellement pour imiter les différentes étapes de la création d’un nouveau projet Catala et de l’organisation de sa base de code ainsi que du travail autour de celle-ci :
- la première section explique comment
organiser les fichiers sources Catala et configurer votre projet pour
clerk, le système de construction de Catala ; - la deuxième section développe la précédente pour ajouter le déploiement automatisé des artefacts générés par Catala en utilisant les différents backends du compilateur ;
- la troisième section concerne la mise en place d’une pile de génie logiciel moderne autour de la base de code, complète avec gestion de versions, tests automatisés et intégration continue (CI) ;
- la quatrième et dernière section est principalement non technique et orthogonale aux sections précédentes, elle concerne l’organisation humaine des projets Catala impliquant des juristes et des programmeurs, ainsi que les choix fondamentaux importants à faire au début du projet.
Bien que les premières sections soient principalement destinées aux programmeurs, l’équipe Catala recommande que tout le monde sur le projet, y compris les juristes, lise la dernière section avant de se lancer dans la production du code Catala.
Bonne lecture !
Structure des répertoires et configuration
La première étape lors de la création de votre projet Catala est de définir la structure des
répertoires et des fichiers qui contiendront votre code source. Dans cette section,
nous décrirons l’organisation et la configuration d’un projet Catala
contenant des fichiers de code source gérés par le système de construction clerk.
Pour commencer, nous vous encourageons vivement à mettre en place un système de gestion de versions tel que git lors de la construction de votre projet ; cette pratique est largement utilisée dans le développement logiciel et aide à suivre et maintenir les contributions à votre code.
Exemple de projet Clerk
Disons que vous avez un projet fictif qui a deux parties principales : une pour calculer les codes fiscaux et une pour les aides au logement. La hiérarchie de fichiers suivante montre un exemple de l’organisation habituelle pour un projet Catala :
mon-projet/
│ clerk.toml
├───src/
│ ├───code_impots/
│ │ │ article_121.catala_fr
│ │ │ article_132.catala_fr
│ │ │ ...
│ │
│ ├───aides_logement/
│ │ │ article_8.catala_fr
│ │ │ ...
│ │
│ └───commun/
│ │ prorata.catala_fr
│ │ foyer.catala_fr
│ │ ...
│
└───tests/
│ test_impot_revenu.catala_fr
│ test_aides_logement.catala_fr
Le projet est composé de plusieurs répertoires et d’un fichier de configuration de projet
clerk.toml :
src/: contient les programmes Catala principaux. Ces programmes peuvent être davantage divisés en sous-répertoires pour séparer correctement votre processus de développement. Le sous-répertoiresrc/commun/, ici, contient certaines structures de données et utilitaires qui sont partagés par les deux autres composants.tests/: contient les tests dédiés de vos programmes Catala. Afin de ne pas encombrer votre logique avec des tests parasites, il est conseillé de créer des fichiers spécifiques et séparés contenant vos tests pour chaque module source.clerk.toml: le fichier de configuration d’un projetclerk.
Chaque fichier source dans votre projet Catala est destiné à être son propre module logique séparé (sauf en cas d’inclusion textuelle). Les modules Catala sont des collections de champs d’application, de constantes de haut niveau et de fonctions qui partagent le même espace de noms. De plus, les modules peuvent être importés dans d’autres modules, vous permettant de modulariser votre base de code et de la garder propre et ordonnée.
Mais, seul et non préparé, un fichier source Catala n’est pas encore un module Catala ! Vous devez déclarer le titre du module à l’intérieur du fichier source et créer l’interface publique d’abord ; voir la documentation dédiée pour déclarer et utiliser des modules.
Avoir un tas de fichiers de code source Catala (correctement déclarés comme modules) stockés
dans les sous-répertoires du projet n’est pas encore suffisant pour que clerk
trouve ses marques et sache quoi faire. Vous devez le guider avec un fichier de configuration
déclaratif, clerk.toml.
Le fichier de configuration clerk.toml
Ce fichier est utilisé pour configurer comment et ce que l’outil clerk est
censé construire et exécuter. Ce fichier de configuration doit être écrit
au format de configuration TOML. Voici un
exemple de ce à quoi il devrait ressembler pour notre projet fictif (les commentaires sont
préfixés par le caractère #) :
[project]
include_dirs = [ "src/commun", # Quels répertoires inclure
"src/code_impots", # lors de la recherche de modules Catala
"src/aides_logement" ] # et de dépendances.
build_dir = "_build" # Définit où sortir les fichiers compilés générés.
target_dir = "_target" # Définit où sortir les fichiers finaux des cibles.
# Chaque section [[target]] décrit une cible de construction pour le projet
[[target]]
name = "code-impots-us" # Le nom de la cible
modules = [ "Article_121", "Article_132", ... ] # Composants modules
tests = [ "tests/test_impot_revenu.catala_fr" ] # Test(s) associé(s)
backends = [ "c", "java" ] # Backends de langage de sortie
[[target]]
name = "aides-logement"
modules = [ "Article_8", ... ]
tests = [ "tests/test_aides_logement.catala_fr" ]
backends = [ "ocaml", "c", "java" ]
Cet exemple de fichier clerk.toml décrit d’abord deux éléments de configuration à l’échelle du projet
(sous la section [project]) :
- dans quels répertoires
clerkdoit chercher les fichiers sources Catala lorsqu’il essaie de trouver des modules et des dépendances ; - où sortir les fichiers compilés générés lors de la construction du projet et de ses cibles.
Les options build_dir et target_dir ont déjà pour valeur par défaut "_build" et
"_target" lorsqu’elles sont absentes, donc, ici elles peuvent être omises en toute sécurité.
Ensuite, le fichier de configuration définit deux composants [[target]]. Une cible dans un
projet Catala est un ensemble de modules Catala qui seront compilés vers un ou
plusieurs langages de programmation cibles, créant des bibliothèques sources prêtes à l’emploi
que vous pouvez ensuite importer et distribuer à d’autres applications dans votre système informatique.
Par exemple, la première cible est nommée "code-impots-us" (vous pouvez la choisir comme
vous le souhaitez) et empaquettera toutes les structures de données déclarées et les champs d’application définis
dans les modules donnés et leurs dépendances. Nous associons également des fichiers (ou répertoires) de
tests spécifiques liés à cette cible afin que nous puissions les exécuter
isolément si nécessaire. Enfin, nous définissons les backends de langage que cette
cible doit générer. Dans notre premier exemple, nous avons configuré la cible pour
traduire notre code en tant que bibliothèque C et en tant que paquet Java.
Le champ de configuration modules d’une section [[target]] nécessite une liste
de noms de modules, tandis que la section tests nécessite une liste de fichiers. Pourquoi cela ?
La raison derrière cette différence est que les modules contiennent la logique du
code Catala pour votre cible ; alors qu’un module Catala est généralement contenu
dans un seul fichier de code, l’inclusion textuelle
est souvent utilisée pour diviser le code d’un seul module sur plusieurs fichiers sources,
correspondant à différentes sources juridiques.
Par conséquent, nous fournissons les noms de modules dans clerk.toml et laissons clerk comprendre
quels fichiers sources appartiennent à quel module pour plus de simplicité.
Les tests, cependant, sont une bête différente. Comme nous le verrons, un test est simplement un
champ d’application avec une marque spéciale #[test] dans un fichier, il n’a pas besoin d’être à l’intérieur d’un module correctement
déclaré. C’est pourquoi le champ tests de la section [[target]]
à l’intérieur de clerk.toml est une liste de fichiers, et non de modules.
Veuillez vous référer à la référence complète de clerk.toml pour
une liste exhaustive des options de configuration et de leurs valeurs possibles.
Maintenant que nous avons nos fichiers sources bien disposés dans leurs répertoires appropriés, ainsi qu’un fichier de configuration de projet, nous détaillerons dans la section suivante comment construire et déployer les livrables pour le projet.
Construction et déploiement du projet
Dans la section précédente, nous avons défini un répertoire contenant un projet Catala avec
un fichier de configuration clerk.toml qui contenait deux cibles principales (code-impots-us
et aides-logement) que nous visons à construire et exporter en tant que bibliothèques sources
dans différents langages.
Récapitulatif de la section précédente : fichier de configuration clerk.toml et hiérarchie du projet
Récapitulatif de la section précédente : fichier de configuration clerk.toml et hiérarchie du projet
Voici le fichier de configuration clerk.toml de notre projet fictif :
[project]
include_dirs = [ "src/commun", # Quels répertoires inclure
"src/code_impots", # lors de la recherche de modules Catala
"src/aides_logement" ] # et de dépendances.
build_dir = "_build" # Définit où sortir les fichiers compilés générés.
target_dir = "_target" # Définit où sortir les fichiers finaux des cibles.
# Chaque section [[target]] décrit une cible de construction pour le projet
[[target]]
name = "code-impots-us" # Le nom de la cible
modules = [ "Article_121", "Article_132", ... ] # Composants modules
tests = [ "tests/test_impot_revenu.catala_fr" ] # Test(s) associé(s)
backends = [ "c", "java" ] # Backends de langage de sortie
[[target]]
name = "aides-logement"
modules = [ "Article_8", ... ]
tests = [ "tests/test_aides_logement.catala_fr" ]
backends = [ "ocaml", "c", "java" ]
Hiérarchie des fichiers du projet :
mon-projet/
│ clerk.toml
├───src/
│ ├───code_impots/
│ │ │ article_121.catala_fr
│ │ │ article_132.catala_fr
│ │ │ ...
│ │
│ ├───aides_logement/
│ │ │ article_8.catala_fr
│ │ │ ...
│ │
│ └───commun/
│ │ prorata.catala_fr
│ │ foyer.catala_fr
│ │ ...
│
└───tests/
│ test_impot_revenu.catala_fr
│ test_aides_logement.catala_fr
Construire le projet
Maintenant que vous avez tout configuré, vous pouvez construire le projet, ce qui signifie
compiler les fichiers de code source Catala dans les différents langages de programmation cibles.
C’est le travail de la commande clerk build :
clerk peut être exécuté de n’importe où dans la hiérarchie de votre projet, mais les
fichiers générés seront toujours placés dans les répertoires de construction et de cible à la
racine du projet.
Vous ne devez pas faire référence à des répertoires frères (../bar) pointant en dehors du
projet : cela causerait des échecs de résolution de chemin dans l’outillage Catala.
$ clerk build
┌─[RESULT]─
│ Build successful. The targets can be found in the following files:
│ [code-impots-us] → _targets/code-impots-us
│ [aides-logement] → _targets/aides-logement
└─
La sortie de la commande vous montre où trouver les résultats. Chaque section [[target]]
produit un sous-répertoire dans le répertoire _targets/, avec les artefacts de compilation
à l’intérieur. Dans notre exemple, cela pourrait ressembler à ceci :
_targets/
├───code-impots-us/
│ ├───c/
│ │ │ Article_121.c
│ │ │ Article_121.h
│ │ │ Article_121.o
│ │ │ ...
│ │
│ ├───java/
│ │ │ Article_121.java
│ │ │ Article_121.class
│ │ │ ...
│ aides-logement/
│ │ ...
Déployer le code généré
Maintenant que tout est correctement construit dans les différents backends, il est temps de les intégrer ! Le but de Catala est de fournir des bibliothèques sources prêtes à l’emploi dans un langage de programmation cible ; Catala ne crée pas une application complète pour vous. Par conséquent, vous prenez généralement ce que Catala construit et l’intégrez dans un autre projet existant.
À partir de ce point, le déploiement nécessite un peu de travail manuel car il dépend des
spécificités de vos cas d’utilisation. Fondamentalement, c’est à vous de copier les
artefacts dans _targets vers votre autre projet, de les compiler et de les lier à
votre base de code existante.
Par exemple, si vous souhaitez intégrer le programme Catala dans le cadre d’une application
Java, vous devrez copier les fichiers sources Java générés depuis le
répertoire _target/<nom_cible>/java/ vers un sous-répertoire de votre projet Java,
et mettre à jour votre configuration Maven pom.xml en conséquence afin que Maven puisse
construire les fichiers sources générés par Catala.
L’équipe Catala ne recommande pas de modifier les fichiers générés par le compilateur Catala, pour deux raisons :
- chaque fois que vous mettrez à jour les fichiers sources Catala, le compilateur re-générera un nouveau fichier dans votre langage de programmation cible que vous devrez re-modifier à la main ;
- même si vous automatisez la modification, chaque modification du fichier généré pourrait introduire une différence de comportement avec la façon dont le fichier source Catala original se comporte avec l’interpréteur Catala.
En effet, le compilateur Catala garantit que le fichier généré dans votre langage de programmation cible se comporte exactement comme le fichier source Catala exécuté avec l’interpréteur Catala. Toute modification du fichier généré pourrait briser cette garantie.
Pour adapter les fichiers générés à votre processus de développement, nous recommandons plutôt que vous construisiez du code “glu” dans votre langage de programmation cible au-dessus des fichiers générés. Ce code “glu” est susceptible de contenir des utilitaires pour convertir vos structures de données existantes en structures de données attendues par les fichiers générés, et vice versa.
Appeler les fonctions générées dans les langages de programmation cibles
Illustrons avec un exemple. Considérez ce programme Catala très simple :
> Module ImpotSimple
```catala
déclaration champ d'application CalculImpotRevenu:
entrée revenu contenu argent
résultat impot_revenu contenu argent
champ d'application CalculImpotRevenu:
définition impot_revenu égal à revenu * 10%
```
Avec sa version compilée en Java :
Fichier ‘ImpotSimple.java’ généré
Fichier ‘ImpotSimple.java’ généré
/* This file has been generated by the Catala compiler, do not edit! */
import catala.runtime.*;
import catala.runtime.exception.*;
public class Test {
public static class CalculImpotRevenu implements CatalaValue {
final CatalaMoney impot_revenu;
CalculImpotRevenu (final CatalaMoney revenu_in) {
final CatalaMoney revenu = revenu_in;
final CatalaMoney
impotRevenu = revenu.multiply
(new CatalaDecimal(new CatalaInteger("1"),
new CatalaInteger("5")));
this.impot_revenu = impotRevenu;
}
static class CalculImpotRevenuOut {
final CatalaMoney impot_revenu;
CalculImpotRevenuOut (final CatalaMoney impot_revenu) {
this.impot_revenu = impot_revenu;
}
}
CalculImpotRevenu (CalculImpotRevenuOut result) {
this.impot_revenu = result.impot_revenu;
}
@Override
public CatalaBool equalsTo(CatalaValue other) {
if (other instanceof CalculImpotRevenu v) {
return this.impot_revenu.equalsTo(v.impot_revenu);
} else { return CatalaBool.FALSE; }
}
@Override
public String toString() {
return "impot_revenu = " + this.impot_revenu.toString();
}
}
}
Si vous inspectez le fichier généré, vous remarquerez que les champs d’application Catala seront traduits en classe Java (et en fonctions en C ou Python). Les calculs de champ d’application sont effectués dans le constructeur de la classe. Par conséquent, pour exécuter le champ d’application, nous devons instancier cette classe et récupérer le résultat.
De plus, pour chaque backend, il existe une version dédiée du runtime Catala.
Ce composant est nécessaire pour la compilation et l’exécution des
programmes Catala générés. Les runtimes décriront les types et
structures de données Catala, les erreurs spécifiques ainsi qu’une API pour les manipuler depuis les
langages ciblés. Les fichiers pour le runtime devraient être inclus dans le
_targets/<nom-cible> ; vous pouvez également les copier dans votre projet et
référencer leurs types et fonctions depuis votre application.
En mettant tout cela ensemble, voici par exemple un programme Java simple qui exécute notre champ d’application :
import catala.runtime.CatalaMoney;
class Main {
public static void main(String[] args){
CatalaMoney revenu_entree = CatalaMoney.ofCents(50000*100);
CalculImpotRevenu resultat = new CalculImpotRevenu(revenu_entree);
CatalaMoney impot_resultat = resultat.impot_revenu;
System.out.println("Impôt sur le revenu : " + impot_resultat);
}
}
Comme mentionné, les runtimes Catala offrent une API pour construire les
valeurs spécifiques à Catala, par exemple, la méthode statique java CatalaMoney.ofCents
qui construit une valeur CatalaMoney équivalente à une valeur de type argent.
Seul le ciel est la limite ensuite quant à ce que vous pouvez construire !
Dans cette section, nous avons vu comment construire un projet, l’exporter et l’intégrer dans une application existante. Dans la section suivante, nous plongerons dans les tests de Catala et la mise en place de l’intégration continue.
Processus de test et d’intégration continue
Dans la section précédente, nous avons défini un répertoire contenant un projet Catala avec
un fichier de configuration clerk.toml qui contenait deux cibles principales (code-impots-us
et aides-logement), que nous avons appris à compiler et déployer en C et Java. Maintenant,
assurons-nous que le code déployé est correct et pratiquons un peu de développement piloté par les tests !
Récapitulatif de la section précédente : fichier de configuration clerk.toml et hiérarchie du projet
Récapitulatif de la section précédente : fichier de configuration clerk.toml et hiérarchie du projet
Voici le fichier de configuration clerk.toml de notre projet fictif :
[project]
include_dirs = [ "src/commun", # Quels répertoires inclure
"src/code_impots", # lors de la recherche de modules Catala
"src/aides_logement" ] # et de dépendances.
build_dir = "_build" # Définit où sortir les fichiers compilés générés.
target_dir = "_target" # Définit où sortir les fichiers finaux des cibles.
# Chaque section [[target]] décrit une cible de construction pour le projet
[[target]]
name = "code-impots-us" # Le nom de la cible
modules = [ "Article_121", "Article_132", ... ] # Composants modules
tests = [ "tests/test_impot_revenu.catala_fr" ] # Test(s) associé(s)
backends = [ "c", "java" ] # Backends de langage de sortie
[[target]]
name = "aides-logement"
modules = [ "Article_8", ... ]
tests = [ "tests/test_aides_logement.catala_fr" ]
backends = [ "ocaml", "c", "java" ]
Hiérarchie des fichiers du projet :
mon-projet/
│ clerk.toml
├───src/
│ ├───code_impots/
│ │ │ article_121.catala_fr
│ │ │ article_132.catala_fr
│ │ │ ...
│ │
│ ├───aides_logement/
│ │ │ article_8.catala_fr
│ │ │ ...
│ │
│ └───commun/
│ │ prorata.catala_fr
│ │ foyer.catala_fr
│ │ ...
│
└───tests/
│ test_impot_revenu.catala_fr
│ test_aides_logement.catala_fr
Mise en place des tests
Nous encourageons les développeurs Catala à écrire beaucoup de tests dans leurs projets !
Bien que vous puissiez écrire des tests n’importe où dans vos fichiers de code source Catala, nous
vous conseillons de les regrouper dans un dossier tests à la racine de
votre projet, avec des fichiers spécifiques remplis de tests pour un module spécifique dans src,
par exemple.
Qu’est-ce qu’un test ?
En Catala, un test est un champ d’application sans variables d’entrée,
qui appelle le champ d’application ou la fonction que vous voulez tester avec des entrées codées en dur.
Par exemple, imaginez que l’un de vos fichiers src,
src/impot_revenu.catala_fr, contient la déclaration de champ d’application suivante (adaptée
et étendue de plus tôt)
> Module Impot_revenu
```catala
déclaration énumération Declaration:
-- Individuelle
-- Conjointe contenu date
déclaration champ d'application CalculImpotRevenu:
entrée revenu contenu argent
entrée nombre_enfants contenu entier
entrée declaration contenu Declaration
résultat impot_revenu contenu argent
```
Ensuite, dans le fichier tests/tests_impot_revenu.catala_fr, vous pouvez écrire un test
pour le champ d’application CalculImpotRevenu. Bien qu’il n’y ait pas de format contraint
pour les tests en Catala, nous recommandons que vous suiviez ce modèle :
> Usage de Impot_revenu
```catala
# D'abord, déclarez votre test
déclaration champ d'application TestImpotRevenu1: # Vous pouvez choisir n'importe quel nom pour votre test
résultat calcul contenu Impot_revenu.CalculImpotRevenu # Mettez ici le champ d'application que vous voulez tester
# Ensuite, remplissez les entrées de votre test
champ d'application TestImpotRevenu1:
définition calcul égal à
résultat de Impot_revenu.CalculImpotRevenu avec {
# Les entrées de ce test pour CalculImpotRevenu sont ci-dessous :
-- revenu: 20 000 €
-- nombre_enfants: 2
-- declaration: Conjointe contenu |1998-04-03|
}
```
Ce test est maintenant un programme valide. Vous pouvez l’exécuter avec :
$ clerk run tests/tests_impot_revenu.catala_fr --scope=TestImpotRevenu1
┌─[RESULT]─ TestImpotRevenu1 ─
│ calcul =
│ Impot_revenu.CalculImpotRevenu {
│ -- impot_revenu: 5 000,00 €
│ }
└─
Maintenant, ce test devrait indiquer quelles sont les sorties attendues, à comparer
avec les sorties calculées. Il y a des façons de le faire avec Catala, selon
ce qui convient le mieux à votre cas d’utilisation. Le point de départ pour les deux méthodes est
exactement ce que nous avons décrit juste au-dessus. Les deux méthodes sont supportées par le
système de test de Catala, accessible via la commande clerk test.
Test par assertion
Une façon de vérifier le résultat calculé est d’affirmer qu’il doit être égal à
une valeur attendue, en utilisant les assertions de Catala.
Pour enregistrer un test par assertion dans clerk test, mettez simplement l’attribut #[test] à la déclaration du champ d’application de test. Par exemple, voici
notre exemple de test configuré comme un test par assertion :
#[test]
déclaration champ d'application TestImpotRevenu1:
résultat calcul contenu CalculImpotRevenu
champ d'application TestImpotRevenu1:
définition calcul égal à
résultat de CalculImpotRevenu avec {
# Les entrées de ce test pour CalculImpotRevenu sont ci-dessous :
-- revenu: 20 000 €
-- nombre_enfants: 2
-- declaration: Conjointe contenu |1998-04-03|
}
assertion (calcul.impot_revenu = 5 000 €)
Bien sûr, l’assertion peut être aussi complexe que vous le souhaitez. Vous pouvez vérifier
le résultat de manière exhaustive ou partielle, vérifier si une propriété dépendant
de l’entrée est satisfaite, etc. Au moment du test, clerk test vérifiera seulement
si toutes les assertions que vous avez définies sont valides.
Les tests basés sur des assertions nécessitent des assertions. Si vous mettez juste #[test] sans aucune
assertion dans votre test, il réussira quel que soit le résultat (puisque aucune
assertion n’échoue), ce qui n’est probablement pas ce que vous voulez pour un test.
L’équipe Catala recommande l’utilisation des tests basés sur des assertions comme méthode principale pour tester les projets, pour les tests unitaires ou de bout en bout. Cela aide à prévenir toute régression indésirable future sur votre base de code.
Test Cram
La deuxième façon de vérifier le résultat attendu d’un calcul est simplement de vérifier la sortie textuelle de l’exécution de la commande dans le terminal. C’est appelé test cram. Pour activer les tests cram dans Catala, vous devez spécifier :
- quelle est la commande que vous voulez tester ;
- quelle devrait être la sortie attendue du terminal.
Les tests Cram sont directement intégrés dans les fichiers de code source Catala, sous la forme d’un
bloc de code Markdown ```catala-test-cli. À l’intérieur, vous spécifiez quelle
commande tester après l’invite $ catala .... Par exemple, la commande interpret --scope=TestImpotRevenu1 est équivalente à l’exécution de clerk run --scope=TestImpotRevenu1. Ensuite suit un verbatim du résultat attendu tel qu’il est craché
par l’exécution de la commande sur le terminal.
Par exemple, voici comment créer un test cram à partir de notre exemple CalculImpotRevenu
ci-dessus :
```catala
déclaration champ d'application TestImpotRevenu1:
résultat calcul contenu CalculImpotRevenu
champ d'application TestImpotRevenu1:
définition calcul égal à
résultat de CalculImpotRevenu avec {
# Les entrées de ce test pour CalculImpotRevenu sont ci-dessous :
-- revenu: 20 000 €
-- nombre_enfants: 2
-- declaration: Conjointe contenu |1998-04-03|
}
```
```catala-test-cli
$ catala interpret --scope=TestImpotRevenu1
┌─[RESULT]─ TestImpotRevenu1 ─
│ calcul =
│ Impot_revenu.CalculImpotRevenu {
│ -- impot_revenu: 5 000,00 €
│ }
└─
```
clerk test récupérera les blocs ```catala-test-cli , exécutera la commande
à l’intérieur, et comparera la sortie avec la sortie attendue.
Rappelez-vous qu’au-delà de interpret, vous pouvez mettre n’importe quelle commande Catala
acceptable sur un test cram. Voir la référence
pour plus de détails.
Vérifier la sortie du terminal d’une commande au lieu d’affirmer des valeurs est fragile et peut introduire beaucoup de bruit lors de la vérification des sorties de test. Par exemple, changer le nom d’un type peut casser la sortie du terminal tout en n’ayant aucun effet sur une assertion dans votre test.
Par conséquent, l’équipe Catala recommande d’utiliser des tests basés sur des assertions lorsque c’est possible,
et d’utiliser uniquement les tests cram pour vérifier ce que le compilateur sort avec des
options spécifiques et des commandes différentes de clerk run.
Exécuter les tests et obtenir des rapports
Simple : exécutez simplement clerk test ! Par défaut, il scannera tout votre projet à la recherche
de #[test] ou ```catala-test-cli dans vos fichiers, exécutera le test,
vérifiera la sortie attendue. Si tout est bon, vous obtiendrez dans votre terminal
un rapport comme :
┏━━━━━━━━━━━━━━━━━━━━━━━━━ ALL TESTS PASSED ━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ FAILED PASSED TOTAL ┃
┃ files 0 34 34 ┃
┃ tests 0 245 245 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
Bien sûr, les nombres dépendent du nombre de tests et de fichiers de test qu’il y a dans votre projet (ne vous inquiétez pas s’ils sont plus bas pour vous).
Si un #[test] basé sur une assertion échoue, vous obtiendrez un rapport supplémentaire imprimant pourquoi
l’assertion a échoué, avec la commande exacte que vous pouvez exécuter pour reproduire l’
échec :
■ scope Test TestImpotRevenu1
$ catala interpret -I code_impots --scope=TestImpotRevenu1
tests/tests_impot_revenu.catala_fr:34 Assertion failed: 5 000,00 € = 4 500,00 €
Les tests cram échoués produiront également un rapport détaillé avec un diff entre la sortie attendue et calculée du terminal.
Pipelines CI
Maintenant que vous avez appris à déclarer vos tests, à les exécuter et à lire les rapports, vous êtes prêt pour le développement piloté par les tests en local. Mais le génie logiciel moderne nécessite une vérification par un tiers des tests avant que le code ne soit fusionné dans la base de code principale. C’est l’un des objectifs des configurations d’intégration continue, et nous discuterons ici de la façon de les mettre en place avec Catala.
Images Docker
Une configuration d’intégration continue commence généralement par le déploiement d’une machine virtuelle ou d’un conteneur basé sur le cloud équipé de toutes les dépendances nécessaires pour construire votre code et exécuter vos tests.
Pour vous éviter les tracas de l’installation manuelle de Catala et de la configuration de votre machine virtuelle, l’équipe Catala fournit des images Docker prêtes à l’emploi pour la CI basées sur Alpine Linux.
Vous pouvez les parcourir ici ou les récupérer avec :
$ docker pull registry.gitlab.inria.fr/catala/ci-images:latest
Notez que latest-c est une version de l’image CI complétée par les
dépendances nécessaires pour compiler et exécuter du code C ; de même pour latest-java,
latest-python. Choisissez l’image qui convient à vos besoins, ou construisez par-dessus dans
votre propre Dockerfile.
Workflow d’intégration continue
Maintenant, cette étape dépend de ce que vous utilisez comme forge de développement logiciel. De nos jours, toutes ont un moyen de déclarer des pipelines déclenchés lors de certains événements (commits sur une branche, sur une PR, sur une fusion, etc). Ces pipelines lancent des exécuteurs qui exécutent certaines commandes, généralement pour exécuter le test ou construire un artefact.
Ce guide ne vous apprendra pas comment écrire ces fichiers de pipeline, veuillez vous référer à la documentation de votre forge logicielle. Cependant, pour vous donner une idée rapide, voici un exemple de fichier de workflow GitHub Actions pour les projets hébergés sur Github pour notre exemple de projet Catala :
name: CI
on:
push:
jobs:
tests:
name: Test suite and build
container:
image: registry.gitlab.inria.fr/catala/ci-images:latest-c
options: --user root
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run test suite with the Catala interpreter and backends
run: opam exec -- clerk ci
Le opam exec est important car clerk est installé
via opam et la chaîne d’outils logicielle OCaml dans les images CI.
Conçu pour être mis dans une exécution CI, clerk ci est simplement un raccourci pour :
clerk testsur tout votre projet ;clerk buildpour toutes les cibles déclarées dans votreclerk.tomlclerk test --backend=...pour tous les backends déclarés dans chacune de vos cibles dansclerk.toml.
Cela garantit que tout se construit, et que tous les tests passent avec l’interpréteur et à l’intérieur du code généré dans chaque backend. Difficile d’obtenir plus d’assurance que cela !
Récupérer les artefacts et les déployer
À l’intérieur de votre pipeline CI, après une exécution de clerk ci ou clerk build, vous devriez
trouver le code source généré pour chaque backend de chaque cible à l’intérieur du
dossier _targets (rappelez-vous la section précédente du guide).
À partir de là, vous pouvez personnaliser votre fichier de pipeline pour récupérer l’artefact,
l’empaqueter comme vous le souhaitez, le transférer vers un autre dépôt, construire une application plus grande
qui l’intègre, etc.
Conclusion
Merci d’avoir suivi ce guide ! Nous espérons qu’il vous met sur la voie
d’un projet Catala réussi. Veuillez lire la référence clerk
pour plus d’informations sur ce que notre système de construction peut faire pour vous aider.
Développement agile avec juristes et programmeurs
À ce stade du guide, vous devriez avoir configuré l’environnement technique du projet et être prêt à commencer à développer en Catala. Mais même si ce n’est pas le cas, et avant de commencer à traduire votre premier texte juridique en code, vous devriez également mettre en place une organisation et une méthodologie appropriées pour vous assurer que vos efforts de traduction sont aussi productifs que possible, et conduisent au code avec le moins de bugs possible du premier coup. Cette section fournit des conseils et des justifications pour certaines des décisions de projet les plus importantes que vous aurez à prendre. De plus, elle donne des indications pratiques sur la façon d’ adapter la méthodologie agile de programmation à la tâche de traduire la loi en code, ce qui nécessite une culture de sécurité avant tout et un environnement organisationnel.
Pourquoi vous devriez éviter d’écrire des documents de spécification intermédiaires
Un trope courant des grandes organisations chargées d’automatiser la prise de décision basée sur des spécifications juridiques est de produire divers documents textuels servant de “spécifications” qui se situent entre les textes juridiques sources et le code exécutable automatisant les décisions.
Imaginez que vous êtes dans une agence gouvernementale chargée de distribuer une prestation. La base légale de la prestation est écrite dans une loi ; cette loi est interprétée par le ministère qui sort un décret (ou arrêté), décrivant plus en détail les règles de calcul de la prestation. Au sein de votre agence, le département juridique prend généralement la loi et le décret, et rédige des instructions officielles résumant comment l’agence interprète la loi et le décret. Ensuite, ces instructions sont envoyées au département des opérations qui rédige un ensemble de spécifications détaillant toutes les règles de calcul de la prestation pour automatiser le calcul. Enfin, le département informatique prend les spécifications et écrit le code exécutable, ou délègue la programmation à un prestataire. En tout, voici les différents documents coexistant dans le processus :
- la loi (promulguée par le Parlement) ;
- le décret (promulgué par le ministère) ;
- les instructions (rédigées par le département juridique de l’agence) ;
- les spécifications (rédigées par le département opérationnel de l’agence) ;
- le code exécutable (écrit par le département informatique de l’agence).
Maintenant, chaque document s’appuie sur le précédent en allant plus en détail et en prenant de plus en plus de micro-décisions sur la façon dont la prestation doit être calculée. Ces micro-décisions sont cruciales et elles doivent être prises à chaque niveau avec le bon niveau de responsabilité quant aux effets de ces décisions sur les futurs bénéficiaires de la prestation.
Mais chaque étape de production d’un nouveau document à partir du précédent, et son envoi à une nouvelle équipe de personnes, augmente le risque d’erreurs et de malentendus, dans un véritable jeu de téléphone arabe administratif. Généralement, ces erreurs et malentendus sont constamment résolus par de longs appels téléphoniques entre les différentes équipes de votre agence au sujet des documents qu’ils viennent d’envoyer. Ces appels téléphoniques conduisent à beaucoup de prises de décision qui ne laissent souvent aucune trace écrite. Accumulée au fil du temps, cette méthodologie de travail conduit à des spécifications dont les règles ne peuvent pas être retracées à une base légale dans la loi et le décret, et qui risquent de diverger ou même de contredire les instructions du département juridique de votre propre agence. Cela nuit à l’efficacité interne et rend impossible la satisfaction du cadre juridique pour la prise de décision automatisée qui impose des normes de transparence strictes.
Plus globalement, la complexité des processus requis pour traduire les exigences légales en code exécutable pour la prise de décision automatisée est la cible du mouvement “Rules as Code” promu par l’ OCDE. Le but ultime de “Rules as Code” est de réduire toutes les étapes du processus décrit ci-dessus et de publier directement la loi et les décrets sous forme de code exécutable. Bien que l’on puisse penser que Catala permet directement cet objectif ultime, l’équipe Catala ne pense pas que ce soit souhaitable, étant donné les impacts considérables que cela aurait sur l’État de Droit.
Au lieu de cela, l’équipe Catala recommande d’utiliser Catala pour fusionner les deux dernières étapes du processus et fusionner dans le fichier de code source Catala à la fois le code exécutable, et tous les textes qui fournissent la base légale pour votre agence. Le reste de cette page expliquera plus concrètement comment y parvenir.
Lectures complémentaires
Lectures complémentaires
La description ci-dessus est un résumé d’une ligne de recherche socio-technique menée par l’équipe Catala. Vous pouvez trouver des comptes rendus détaillés avec analyse juridique et études de terrain corroborant les résultats dans les publications suivantes :
- Liane Huttner, Denis Merigoux. Catala: Moving Towards the Future of Legal Expert Systems. Artificial Intelligence and Law, 2022, ⟨10.1007/s10506-022-09328-5⟩. ⟨hal-02936606⟩.
- Denis Merigoux, Marie Alauzen, Lilya Slimani. Rules, Computation and Politics. Scrutinizing Unnoticed Programming Choices in French Housing Benefits. Journal of Cross-disciplinary Research in Computational Law, 2023, 2 (1), pp.23. ⟨hal-03712130v3⟩
- Denis Merigoux, Marie Alauzen, Justine Banuls, Louis Gesbert, Émile Rolley. De la transparence à l’explicabilité automatisée des algorithmes : comprendre les obstacles informatiques, juridiques et organisationnels. RR-9535, INRIA Paris. 2024, pp.68. ⟨hal-04391612⟩
Pourquoi vous devriez choisir les textes juridiques pour la programmation littéraire en Catala
Comme expliqué ci-dessus, généralement les programmeurs ne se réfèrent qu’aux spécifications lorsqu’ils écrivent leur code, et non aux textes juridiques dont les spécifications ont été dérivées. Par conséquent, il serait logique à première vue d’utiliser simplement les spécifications comme point de départ de la programmation littéraire. Bien que ce soit techniquement possible, l’équipe Catala ne le recommanderait pas car cela empêche l’effort de réécriture vers Catala de reconstruire la chaîne de justifications des micro-choix du texte juridique au code exécutable.
En effet, tout l’intérêt de la programmation littéraire est de fusionner dans un seul document à la fois le code et l’explication de pourquoi le code fonctionne de cette façon. Appliqué à Catala, la programmation littéraire signifie que l’explication de pourquoi le code fonctionne de cette façon doit contenir la base légale de son comportement, donc les textes juridiques.
De plus, utiliser les textes juridiques dans la programmation littéraire au lieu des spécifications accélère considérablement la maintenance du code. En effet, chaque fois qu’il y a une mise à jour dans les textes juridiques, il suffit de propager la mise à jour à la copie du texte juridique dans le fichier Catala (un processus qui peut être automatisatisé), et ensuite de mettre à jour (manuellement) les extraits de code qui se trouvent juste en dessous des textes juridiques mis à jour. La connaissance de quel morceau de code correspond à quelle partie du texte juridique est explicite, ce qui le rend plus robuste au changement de personnel ou à la perte de mémoire institutionnelle. Comparez cela avec le processus traditionnel de mise à jour de la spécification, puis de mise à jour du code : le rédacteur de la spécification et le rédacteur du code (généralement deux personnes différentes) doivent se rappeler quelle partie du document qu’ils écrivent correspond aux parties mises à jour du document qu’ils reçoivent.
Globalement, utiliser les textes juridiques pour la programmation littéraire Catala assure la cohérence de la documentation pour les choix interprétatifs, ainsi que la robustesse et l’efficacité des futures mises à jour du code.
Une fois que vous avez choisi de commencer avec les textes juridiques comme base pour votre programmation littéraire en Catala, un problème survient rapidement : où mettre toutes les informations qui documentent la désambiguïsation des textes juridiques dans le code exécutable ?
Ces informations sont généralement en partie stockées dans les instructions officielles publiées par votre agence, ou dispersées dans des mémos internes et des e-mails envoyés le long des chaînes hiérarchiques. Ces instructions, mémos et e-mails paraphrasent souvent les textes juridiques, tout en ajoutant un petit bout d’information supplémentaire qui contient la désambiguïsation que vous recherchez.
Une solution pourrait être de copier-coller le texte des instructions, mémos et e-mails et de les mettre à côté du texte juridique dans le fichier source Catala. Mais faire cela encombrera rapidement votre base de code et vous forcera à un choix maladroit : où mettre les extraits de code Catala ? Sous le morceau de texte juridique ou sous le morceau de mémos qui paraphrase le texte juridique ? Il n’y a pas de bonne réponse à cela ; de plus, copier-coller les instructions et mémos systématiquement dans votre base de code revient à faire de la programmation littéraire avec les spécifications au lieu des textes juridiques, ce qui, nous l’avons montré, n’est pas souhaitable.
Au lieu de cela, l’équipe Catala vous conseille de ne pas copier-coller le texte des instructions, mémos et e-mails à l’intérieur de votre base de code de programmation littéraire aux côtés des textes juridiques, mais plutôt de les référencer à l’intérieur des commentaires de code. En effet, les commentaires de code sont précisément là pour documenter les choix dans le code, ce qui en fait l’endroit parfait pour insérer des justifications pour la désambiguïsation des textes juridiques référençant des instructions, mémos ou e-mails. Par exemple, voici à quoi pourrait ressembler un fichier de code source Catala typique pour un exemple juridique fictif impliquant un calcul de déduction de revenu :
Article 364
La déduction de revenu mentionnée à l’article 234 est plafonnée à 10 000 €.
champ d'application RevenuBrutVersNetIndividuel:
# Une lecture littérale de l'article 364 impliquerait de mettre 10 000 € ici. Mais
# en fait, l'instruction de l'agence N°1045-58 publiée le 28/04/2023 précise
# que le plafond est estimé au niveau du foyer et non au niveau individuel.
# Pour concilier avec la vue individuelle de l'article 364, le plafond ici devrait
# être un prorata du plafond du foyer par rapport au revenu de chaque
# individu. Nous supposons que ce prorata est fourni comme variable d'entrée
# "plafond_deduction_prorata" du champ d'application "RevenuBrutVersNetIndividuel".
définition deduction_revenu état plafonnement égal à
plafond_deduction_prorata
# Maintenant nous devons calculer "plafond_deduction_prorata" du champ d'application
# "RevenuBrutVersNetIndividuel", au niveau du foyer. Mais en fait, un mémo interne
# envoyé par le département juridique au département informatique le 05/09/2023 précise
# en outre que le plafond au niveau du foyer ne devrait être proratisé que si la somme
# des revenus des individus dans le foyer est supérieure au plafond.
# Sinon, chaque individu devrait voir comme son plafond individuel le plafond de
# tout le foyer. C'est équivalent car si la somme des revenus est inférieure au
# plafond, alors l'opération de plafonnement n'aura aucun effet.
champ d'application RevenuBrutVersNetFoyer:
# ...
Comme vous pouvez le voir, en plus du texte juridique, la quantité de commentaires à l’intérieur du code Catala peut être conséquente. Plus le texte juridique est concis et ambigu, plus de commentaires de code et de références à la documentation de l’agence seront nécessaires pour expliquer comment il a été désambiguïsé ! Ces commentaires de code doivent être très soigneusement rédigés avec contexte, informations spécifiques et références afin qu’ils soient suffisants pour qu’un nouveau venu sur le projet reconstruise l’argument expliquant pourquoi le code ressemble à ce qu’il est. Écrire de bons commentaires prend du temps au début mais garder une telle pratique cohérente vous évitera des maux de tête à l’avenir et assurera la maintenabilité à long terme de la base de code.
Recruter une équipe mêlant programmation et connaissances du domaine
Au cours de ses expériences et efforts de recherche, l’équipe Catala a remarqué que la grande majorité du temps (70% à 90%) passé sur les projets pour traduire la loi en code Catala était passé non pas à écrire du code, mais à comprendre ce que la loi signifiait et comment l’interpréter correctement par rapport aux directives de l’agence.
Cela signifie que le goulot d’étranglement pour la progression du projet n’est pas la vitesse de codage mais plutôt la vitesse à laquelle l’équipe est collectivement capable de découvrir ce que la loi signifie et comment l’interpréter. Et les ingénieurs logiciels n’ont pas le meilleur ensemble de compétences pour cette tâche.
Si vous voulez en savoir plus sur l’interaction juriste-programmeur dans la production de logiciels automatisant la loi, veuillez vous référer à :
- Nel Escher, Jeffrey Bilik, Nikola Banovic, and Ben Green. 2024. Code-ifying the Law: How Disciplinary Divides Afflict the Development of Legal Software. Proc. ACM Hum.-Comput. Interact. 8, CSCW2, Article 398 (November 2024), 37 pages. https://doi.org/10.1145/3686937
Pour cette raison, l’équipe Catala conseille d’avoir un mélange équilibré entre des personnes qui peuvent écrire du bon code (généralement des ingénieurs logiciels) et des personnes qui peuvent comprendre ce que la loi signifie et l’interpréter (généralement des juristes mais pas toujours, comme nous le verrons) au sein de votre équipe pluridisciplinaire. Ce mélange égal implique que vous pouvez organiser l’équipe en paires de personnes : un juriste et un programmeur. Vous pouvez augmenter l’effort de développement en augmentant le nombre de paires dans votre équipe qui peuvent être affectées à des parties indépendantes du calcul.
Nous verrons ci-dessous comment les programmeurs et les juristes passent leur temps au cours d’une semaine typique. Mais d’abord, parlons de quel type de “juristes” nous parlons dans nos équipes pluridisciplinaires. Le mot “juristes” fait généralement référence aux avocats, c’est-à-dire aux personnes défendant des individus et des entreprises devant les tribunaux lors d’un procès. Mais les agences administratives emploient très peu de ce type de juristes, et ces personnes ne sont dévouées qu’à la gestion des procès dans lesquels l’agence est impliquée. Au lieu de cela, les agences administratives emploient généralement un bon nombre de personnes formellement formées en Droit à l’université dans leur département juridique central, chargées d’identifier et de surveiller tous les textes juridiques auxquels l’ agence doit se conformer. Ces “juristes” rédigent ensuite généralement les instructions officielles de l’agence qui paraphrasent le texte juridique, tout en prenant en compte les décisions de justice passées et la doctrine officielle de l’agence. Cela correspond à l’étape 3. du processus en V décrit plus haut dans cette page.
Ensuite, l’étape 4. du processus implique encore d’autres interprétations de la loi. Mais cette fois, c’est souvent fait par des personnes qui n’ont pas de formation universitaire formelle en droit, et qui ne s’appellent pas “juristes”. Cependant, ces personnes ont une connaissance étendue et spécialisée du contenu juridique en question, basée sur des décennies d’expérience antérieure dans leurs postes, ou en tant que travailleurs sociaux de terrain au sein de l’agence. Parfois, ces personnes n’ont jamais vu les textes juridiques pour la prestation ou l’impôt dans lequel elles sont spécialisées, elles n’ont jamais lu que les instructions officielles de l’agence rédigées par le département juridique interne. L’équipe Catala appelle ces personnes les “experts du domaine”, et ils sont aussi cruciaux que les “juristes” pour s’assurer que la chaîne entre la base légale et le code exécutable est pleinement justifiée dans un fichier de code source Catala.
Par conséquent, l’équipe Catala recommande d’inclure un mélange d’“experts du domaine” et de “juristes” de type département juridique au sein de votre équipe aux côtés des ingénieurs logiciels. C’est la combinaison du point de vue de la théorie juridique avec l’expérience de la pratique de cette loi dans l’agence qui est la clé pour atteindre la cohérence du logiciel écrit en Catala avec à la fois les systèmes existants qu’il est censé remplacer et la doctrine juridique officielle de l’agence à laquelle il est censé se conformer.
Parce que la rotation du personnel est élevée dans les organisations, en particulier aux postes exigeants, il est souvent difficile de trouver des experts avec une expérience suffisante de la loi en question au sein de l’agence, et le plus souvent ces personnes sont déjà surchargées par la maintenance des projets informatiques actuels de l’agence sans temps à investir dans un effort de modernisation Catala.
Par conséquent, il sera difficile de doter votre équipe d’une liste de stars d’ experts juridiques/du domaine. L’équipe Catala conseille de voir cela comme une opportunité de recruter et de former de nouveaux experts juridiques/du domaine qui développeront une nouvelle expertise au fur et à mesure que le projet utilisant Catala avancera. En effet, l’équipe Catala a empiriquement constaté lors des premières expériences utilisant des juristes fraîchement diplômés que la tâche de produire une implémentation Catala d’un morceau de loi est un moyen très efficace pour les juristes de se spécialiser rapidement et de maîtriser ce morceau de loi.
Cependant, ce processus de formation n’est efficace que si le juriste en formation a accès (une heure chaque semaine ou deux semaines par exemple) à un expert du domaine vraiment expérimenté qui peut vider de sa mémoire toutes les questions restantes que le juriste en formation n’a pas pu résoudre par lui-même.
Emploi du temps hebdomadaire : sessions de programmation en binôme et devoirs
Une fois que vous avez assemblé votre équipe selon les principes énoncés ci-dessus, vous devrez définir des tâches pour chacune de vos paires juriste-programmeur. Pour chaque morceau de travail (que ce soit l’implémentation d’une certaine section d’un statut ou d’un certain champ d’application prédéterminé comme important dans le calcul), le travail doit suivre une boucle itérative composée des étapes suivantes :
- Identifier les sources juridiques et les instructions et mémos de l’agence justifiant le morceau de calcul ;
- Les copier-coller dans un fichier de code source Catala et échafauder la structure de données et les déclarations de champ d’application ;
- Travailler linéairement à travers les sources juridiques, en s’assurant de mettre le code Catala aussi près que possible de la source juridique qui le justifie ;
- Pendant le travail d’implémentation, des tâches de refactorisation de code et des questions de recherche juridique surviendront inévitablement ;
- Les traiter en itérant vers les étapes précédentes de cette boucle ;
- Vous avez terminé quand plus aucune nouvelle tâche de refactorisation ou question de recherche juridique ne survient !
En suivant les boucles précédentes, le programmeur et le juriste de la paire seront occupés à temps plein pendant une semaine typique. L’organisation de la semaine pour les deux devrait être centrée autour d’une session hebdomadaire de programmation en binôme de 2 à 3 heures, avec le schéma suivant :
- avant la session de programmation en binôme, le juriste doit envoyer au programmeur les listes de textes juridiques à prendre en compte pour le morceau de travail actuel ;
- sur cette base, le programmeur doit écrire un échafaudage initial de structure de données et de déclarations de champ d’application à utiliser pendant la session de programmation en binôme ;
- le juriste doit également rechercher préventivement les décisions de justice pertinentes et les documents de doctrine de l’agence avant la session de programmation en binôme ;
- un ordre du jour est communément fixé par le juriste et le programmeur pour la session de programmation en binôme à venir, priorisant les tâches à accomplir ;
- pendant la session de programmation en binôme, le juriste et le programmeur doivent discuter du texte de loi et essayer d’esquisser ou d’implémenter entièrement des extraits de code Catala (généralement des définitions de variables de champ d’application) ;
- ils doivent également noter dûment toute tâche de refactorisation ou question de recherche juridique qui survient pendant la session comme tâches à faire ;
- ces tâches à faire doivent occuper à la fois le juriste et le programmeur après la session de programmation en binôme et avant la suivante ;
- de plus, le juriste doit préparer de manière asynchrone des cas de test que le programmeur (ou le juriste) peut implémenter et ajouter à la base de tests pour l’intégration continue ;
- une fois terminé, le morceau de travail est matérialisé sous forme de proposition de fusion (merge request) complète avec implémentation et tests, à examiner à la fois juridiquement et informatiquement par une autre paire juriste-programmeur.
Si tout cela ne remplit pas la semaine de travail, le programmeur peut travailler en plus sur des tâches de programmation liées au déploiement du projet tandis que le juriste peut travailler à la rédaction de mémos internes documentant l’avancement du projet et comment les décisions qu’ils ont prises en programmant sont conformes à la doctrine de l’ agence.
Suivi des progrès et planification du travail
Globalement, vous savez que vous réimplémenterez un certain calcul d’impôt ou de prestation, mais comment diviser le travail en tâches et les distribuer aux paires ?
Il existe deux stratégies pour diviser le travail d’implémentation en morceaux.
La première qui correspondrait le plus à la méthodologie de programmation littéraire est de rassembler d’abord tous les textes juridiques et références aux instructions et mémos internes, de tous les disposer dans des fichiers texte Catala structurés par source juridique, puis de procéder à l’implémentation de chaque morceau de loi dans l’ordre dans lequel il est écrit dans la source juridique. L’équipe Catala a déjà essayé cette stratégie. Son principal avantage est qu’elle conduit à être très exhaustif par rapport au code, et suivre le chemin du texte juridique aide à le comprendre plus rapidement (s’il est bien écrit !). Cependant, cette stratégie ne garantit pas que des parties du calcul seront prêtes de manière incrémentale à tester et déployer de manière continue pendant le projet, et elle conduit à beaucoup de refactorisation pour prendre en compte les nouvelles dispositions légales au fur et à mesure qu’elles apparaissent dans le texte.
Globalement, l’équipe Catala ne recommanderait cette première stratégie que si la prestation ou l’impôt en question n’a jamais été automatisé auparavant dans l’agence et/ou si le niveau interne d’expertise à ce sujet est faible. Dans tous les autres cas, l’équipe Catala recommande de suivre la deuxième stratégie.
La deuxième stratégie est de diviser le calcul de l’impôt ou de la prestation sociale en utilisant un plan de division déjà connu qui correspond à la division fonctionnelle du contenu informatique. Par exemple, vous savez déjà que la prestation est divisée entre l’éligibilité et le calcul du montant, que le calcul de l’éligibilité est divisé entre les conditions de revenu et d’autres conditions, etc. À partir de ce plan de division connu, vous pouvez déjà écrire dans votre fichier Catala “prologue” les déclarations initiales pour les structures de données, les champs d’application et les modules de votre future implémentation. Ensuite, vous pouvez implémenter de manière incrémentale chaque module ou champ d’application en extrayant toutes les sources juridiques dont vous avez besoin pour justifier le calcul actuel, et en les copiant-collant dans le fichier source Catala à la demande. À la fin, les fichiers sources devraient toujours être organisés grossièrement par source juridique comme dans la première méthode, mais les chemins pour remplir les fichiers seront complètement différents.
Pour la gestion de projet au jour le jour, l’équipe Catala vous conseille de faire plein usage des plateformes de génie logiciel comme Github, Gitlab ou Jira. Non seulement les tâches de codage peuvent être modélisées et suivies comme des tickets et des pull requests sur ces plateformes, mais les tâches juridiques peuvent également être modélisées et suivies comme des tickets. En particulier, l’équipe Catala a noté qu’il est très utile de mettre toutes les tâches à faire collectées à la fin d’une session de programmation en binôme sous la forme de tickets dans la plateforme. Ensuite, l’avancement du projet peut être suivi avec des tableaux de tickets, des listes et une estimation du temps comme un projet logiciel standard.
Vous pouvez également utiliser des étiquettes de tickets pour marquer les questions juridiques qui ont été recherchées ou validées par le département juridique de l’agence. Étiqueter les tickets facilite la planification de l’ordre du jour de la prochaine session de programmation en binôme et le tri entre les tâches qui nécessitent encore des recherches ou une validation supplémentaires, et celles qui peuvent être implémentées en code Catala tout de suite.
FAQ: Comment coder la loi?
Après avoir installé Catala, suivi le tutoriel et configuré votre projet, vous devriez être prêt à lancer votre projet de transformation de la loi en code. Cependant, il se peut que vous ayez encore des questions. Ce chapitre vise à répondre aux questions fréquentes que se posent les utilisateurs, à la fois en général et spécifiques à Catala.
Questions générales
Certaines parties de ces réponses sont adaptées, avec permission, de Lawsky, Coding the Code: Catala and Computationally Accessible Tax Law, 75 SMU L. Rev. 535 (2022).
Qu’est-ce que la programmation en binôme pour coder la loi ?
Comme l’explique l’introduction de ce livre, Catala est conçu pour être utilisé par des équipes possédant à la fois une expertise juridique et informatique. La programmation en binôme fait généralement référence à deux programmeurs travaillant ensemble côte à côte sur le même ordinateur pour écrire du code informatique. La programmation en binôme pour coder la loi n’implique pas deux programmeurs ayant des compétences similaires, mais plutôt un programmeur et un juriste, des personnes ayant des domaines d’expertise différents, travaillant côte à côte pour écrire un code informatique qui capture aussi précisément que possible le sens de la loi. La programmation en binôme avec un juriste et un programmeur permet d’encoder la loi sans qu’une seule personne ait besoin d’être experte à la fois en droit fiscal et en codage. La programmation en binôme améliore également la qualité du code car elle exige que le juriste explique la loi très clairement, si clairement qu’un programmeur puisse la comprendre, et elle exige que le programmeur soit capable d’articuler exactement comment le code informatique correspond à la loi, améliorant ainsi la précision et l’attention aux détails tant pour la loi que pour le code.
Qu’est-ce que la programmation littéraire pour coder la loi ?
La programmation littéraire est l’idée, avancée pour la première fois par Donald Knuth, qu’un programme informatique ne communique pas seulement à un ordinateur ce qu’il doit faire, mais communique aussi aux humains lisant le programme ce que le programme veut que l’ordinateur fasse. Concrètement, les programmes qui suivent l’approche de la programmation littéraire incluent beaucoup de commentaires. La programmation littéraire est particulièrement importante pour coder la loi, car elle augmente la transparence et montre clairement comment le code correspond (ou ne correspond pas) à la loi. Comme expliqué dans la première section du tutoriel, la programmation littéraire pour la loi en Catala signifie que le code Catala inclut non seulement le code qui dit à l’ordinateur quoi faire, mais aussi le langage législatif ou réglementaire réel qui se rapporte au code pour l’ordinateur. Cela facilite la mise à jour du code informatique lorsque la loi change. Les commentaires incluent également le raisonnement derrière les décisions de l’équipe de codage, y compris le support statutaire extra-légal pour les décisions. Et les commentaires peuvent également expliciter les ambiguïtés trouvées par l’équipe de codage, et comment et pourquoi l’équipe a choisi de résoudre ces ambiguïtés.
Pourquoi ne devrais-je pas utiliser n’importe quel langage de programmation que j’aime pour coder la loi ?
La question de savoir quel langage de programmation utiliser pour coder la loi est, bien sûr, une question pragmatique. Le fait que Catala puisse être compilé vers d’autres langages, tels que Python, démontre que l’on pourrait coder la loi et capturer avec précision son sens, dans une certaine mesure, dans de nombreux langages différents. Mais Catala présente plusieurs avantages pour coder la loi, en particulier la loi qui est organisée comme des règles générales suivies d’exceptions, comme le sont de nombreuses lois.
Premièrement, un programme en Catala imite la structure de la pensée juridique en listant d’abord les parties, structures et variables pertinentes. Les éléments sont nommés avant d’être utilisés, ce qui permet aux humains regardant le code informatique d’identifier les concepts pertinents avant de les appliquer. Deuxièmement, Catala permet au programmeur de définir facilement un champ d’application sur lequel diverses règles et définitions s’appliquent, en accord avec l’approche de la loi d’avoir des dispositions qui s’appliquent uniquement à “ce chapitre”, “cette section”, et ainsi de suite.
De plus, et c’est le plus important, comme décrit dans la deuxième section du tutoriel, la sémantique de Catala est basée sur une logique de règles générales et d’exceptions (“logique par défaut priorisée”). La structure des programmes Catala peut donc suivre de près la structure de la loi elle-même, si la loi est organisée comme des règles générales suivies d’exceptions. Cela rend plus facile le codage de la loi ; parce que la logique sous-jacente suit le statut, la traduction en logique par défaut peut simplement suivre le statut lui-même. Et si le statut change, il sera plus facile de changer le code Catala qu’il ne le serait de changer un code qui ne suivrait pas la structure du statut. Parce que Catala permet des exceptions qui l’emportent sur des règles plus générales, la formalisation en Catala permet aux sections et sous-sections distinctes de la loi de rester séparées. La rédaction est plus modulaire et permet aux codeurs d’échanger plus facilement la nouvelle loi et l’ancienne loi.
Enfin, les programmes Catala peuvent être formellement vérifiés car Catala a une sémantique formelle bien définie.
Peut-on utiliser de grands modèles de langage (LLM) pour traduire la loi en code ?
Savoir si les grands modèles de langage (LLM) peuvent être utilisés pour traduire la loi en code est une question de recherche ouverte que diverses personnes ont poursuivie, sans grand succès à ce stade. Le langage juridique est complexe et ne ressemble pas au langage “ordinaire”. Même au-delà de cela, coder la loi est plus que simplement “traduire” directement le langage statutaire en code informatique. La loi englobe plus que le simple langage d’un statut. Coder la loi peut, par exemple, nécessiter de résoudre des ambiguïtés présentes dans le texte statutaire, ou d’incorporer des règlements ou des pratiques en dehors du statut. Traduire la loi en code nécessite également précision, exactitude et responsabilité, qui sont toutes, au moment de cette rédaction, en tension avec l’utilisation des LLM.
Toute loi peut-elle être formalisée ?
Dans un certain sens pas particulièrement intéressant, toute loi peut être formalisée. Mais au moins lorsqu’il s’agit de formaliser la loi avec Catala, toute loi n’est pas utilement formalisée. Les lois où l’effort de formalisation porte ses fruits concrètement ont tendance à être des lois lourdes en règles, complexes, impliquant peut-être de nombreux calculs numériques, et, pour vraiment profiter de tout ce que Catala a à offrir, structurées comme des règles générales suivies d’exceptions.
Formaliser la loi a également un impact sur la façon dont elle est appliquée, et sur l’État de droit en général. Voir les travaux du groupe de recherche COHUBICOL pour plus de détails.
Questions spécifiques à Catala
Quand choisir condition, règle plutôt que des booléens ?
Vous avez peut‑être remarqué les mots‑clés condition et règle dans les champs d’application Catala, par exemple :
déclaration champ d'application Foo:
entrée i contenu entier
résultat x condition
champ d'application Foo:
règle x sous condition i = 42 conséquence rempli
Le programme ci‑dessus est strictement équivalent à celui‑ci :
déclaration champ d'application Foo:
entrée i contenu entier
résultat x contenu booléen
champ d'application Foo:
définition x égal à faux
exception définition x sous condition i = 42 conséquence égal à vrai
Comme l’exemple le montre, condition est un sucre syntaxique pour déclarer une variable booléenne dont la valeur par défaut est faux. Dans le corps du champ d’application, on utilise règle au lieu de définition pour préciser sous quelles conditions la condition doit être rempli (vrai) ou non rempli (faux). Il est possible d’utiliser exception et étiquette sur des règles comme sur des définitions, mais toutes les règle sont implicitement des exceptions d’un cas de base où la condition vaut faux.
Ce comportement pour condition et rule correspond à l’intuition juridique et rend cette syntaxe plus lisible pour des morceaux de programme où l’on construit une valeur booléenne selon des cas.
Pourquoi faut‑il faire des conversions de type ?
Certains langages, comme Javascript, ne distinguent pas entiers et décimaux (type unique Number). D’autres, comme Python, masquent la distinction car l’interpréteur insère des conversions implicites quand un entier est utilisé là où un décimal est attendu. Cette approche facilite le codage, car on se soucie moins de la représentation en mémoire : “ça marche”.
Mais cela a un inconvénient : puisque le langage décide pour vous de la représentation en mémoire, vous perdez le contrôle sur la précision des calculs et sur la manière dont les valeurs sont converties. Par exemple, convertir un décimal en entier fait perdre de la précision (arrondi/troncature) ; il y a plusieurs façons de convertir un nombre de mois en nombre de jours selon la finalité du calcul.
La philosophie de Catala est de vous donner le contrôle, au prix d’exiger des conversions explicites. Ainsi, les types de base de Catala (booléen, entier, décimal, argent, date, durée) sont strictement distincts et nécessitent des conversions explicites entre eux. Utiliser un décimal là où un entier est attendu produira une erreur de typage comme :
┌─[ERROR]─
│
│ I don't know how to apply operator + on types integer and decimal
│
├─➤ example.catala_en:
│ │
│ 13 │ 1 + 2.0
│ │ ‾‾‾‾‾‾‾
│
│ Type integer coming from expression:
├─➤ example.catala_en:
│ │
│ 13 │ 1 + 2.0
│ │ ‾
│
│ Type decimal coming from expression:
├─➤ example.catala_en:
│ │
│ 13 │ 1 + 2.0
│ │ ‾‾‾
└─
On corrige en remplaçant 2.0 par entier de 2.0. Voir la section correspondante de la référence du langage pour les détails.
Pourquoi un type argent distinct ?
Effectuer correctement des calculs financiers est difficile. Les règles de précision/arrondi varient selon les applications et doivent être conciliées avec la performance.
C’est pourquoi Catala sépare strictement argent des entier/décimal. Utiliser argent avec des conversions explicites (cf. ci‑dessus) permet au compilateur d’avertir lorsqu’on mélange argent et non‑argent. L’argent et son unité deviennent comme une unité dimensionnelle dans une formule physique.
Une fois ce type distinct, il faut lui donner un comportement cohérent avec la philosophie du langage. Les valeurs argent en Catala sont un nombre entier de centimes. Multiplier un argent par un décimal peut donner un résultat non entier en centimes ; Catala arrondit alors au centime le plus proche.
Si vous avez besoin de plus de précision pour des montants d’argent, représentez‑les en décimal et convertissez vers/depuis argent quand nécessaire.
Comment arrondir un montant à une précision donnée ?
En Catala, argent est un entier de centimes (voir ci‑dessus). Un calcul avec argent est arrondi au centime à chaque étape. Il faut donc penser l’arrondi à chaque valeur intermédiaire ; cela facilite la relecture par des experts métier.
- Pour arrondir à l’unité monétaire la plus proche, utilisez
arrondi de. - Pour arrondir à un multiple arbitraire du centime, utilisez les fonctions utilitaires de la bibliothèque standard (section 5‑7).
Exemples:
Money.round_by_excess of $4.13donne$5Money.round_by_default of $4.13donne$4Money.round_to_decimal of $123.45, 1 = $123.5(arrondi au 10e de l’unité)Money.round_to_decimal of $123.45, -2 = $100.0(arrondi à la centaine)
Des fonctions analogues existent pour décimal dans le module dédié.
Pourquoi des entiers/décimaux mathématiques plutôt que des entiers/flottants machine ?
Pour la précision. Les entiers machine débordent. Les flottants ne représentent pas tous les intervalles et accumulent des erreurs. En général, c’est acceptable ; pas pour des calculs financiers qui pilotent des décisions administratives.
Catala utilise la bibliothèque GMP pour des entiers et décimaux mathématiquement sains dont la représentation grandit avec la précision requise. Cela a un coût de performance, mais GMP propose des optimisations bas niveau pour limiter cet impact.
En pratique, les décimal de Catala sont des rationnels GMP (fractions irréductibles de deux entiers GMP).
Comment créer des dates et des durées à partir d’entiers ?
Pour obtenir une durée, multipliez l’unité voulue par l’entier/décimal :
# 1 mois * 24 = 24 mois
déclaration durée_de_jours contenu durée
dépend de nombre_de_jours contenu entier
égal à
1 jour * nombre_de_jours
En revanche, on ne construit pas une date YYYY-MM-DD en concaténant des entiers. Utilisez la fonction Date.of_year_month_day.
Pourquoi pas de chaînes de caractères ?
L’absence de chaînes est volontaire. Catala cible des calculs décrits par des textes juridiques. Si vous trouvez un texte légal nécessitant des opérations de chaînes, dites‑le nous !
- Les opérations “texte” des lois sont souvent mieux modélisées par des énumérations (avec contenu) et un filtrage par motif exhaustif.
- Les opérations bas niveau non décrites par la loi mais utilisées dans un programme Catala devraient être faites hors de Catala, en fournissant le résultat comme entrée d’un champ d’application, ou via un module externe (référence).
- Ajouter un runtime de chaînes alourdirait fortement la taille/complexité, avec des regex identiques sur tous les backends supportés. Coût important à la mise en œuvre et en maintenance.
Comment ajouter une exception depuis l’extérieur d’un champ d’application ?
La loi peut être tordue. Par exemple, l’article 1731 bis du CGI spécifie des amendes où l’on doit neutraliser certaines déductions à l’intérieur d’un calcul fiscal existant. En Catala, vous auriez deux champs d’application FinesComputation et IncomeTaxComputation ; le premier doit appeler le second tout en modifiant certaines règles à l’intérieur du second.
Ce motif revient à déclarer une exception à une variable de IncomeTaxComputation, depuis l’extérieur de IncomeTaxComputation. Catala offre une fonctionnalité dédiée : les variables de contexte, qui étendent proprement les exceptions entre champs d’application. Voir la référence.
Dois‑je répéter tous les champs d’une struct si je n’en change qu’un seul ?
Non ! Voir “Mise à jour des structures” dans la référence.
Comment sont gérées les dates et durées ?
Quel est le résultat de 31 janv + 1 mois ? 28 fév, 29 fév, 1er mars ? Ces ambiguïtés ont un impact sur les décisions automatisées. Nous avons conçu une bibliothèque dédiée de calcul de dates permettant de choisir la stratégie d’arrondi des dates ambiguës, motivée dans un article scientifique.
Sinon, les dates sont des dates grégoriennes au jour près. Les durées combinent jours/mois/années. Voir la référence.
Quels langages cibles pour Catala ?
Le compilateur Catala cible nativement :
- C (C89) ;
- Python ;
- Java ;
- OCaml.
Depuis le backend OCaml, Javascript peut être ciblé via js_of_ocaml.
Les runtimes ont deux dépendances hors bibliothèques standard : GMP pour l’arithmétique multi‑précision et la bibliothèque de dates.
Pourquoi ne peut‑on pas découper les déclarations de champ d’application comme les définitions ?
En Catala, on peut avoir plusieurs définition pour la même variable, ce qui permet d’aligner le code sur des fragments de texte légal dispersés. On pourrait vouloir faire pareil avec les déclarations de structures et de champs d’application.
Nous avons choisi de ne pas le permettre. Empiriquement, contrairement aux definitions à justifier par le texte légal, l’agencement des structures et prototypes relève surtout de choix de programmation. Nous recommandons de mettre toutes les déclarations de structures et de champs d’application dans un “prologue” distinct des sources légales, plutôt que de les disperser.
De plus, centraliser ces déclarations facilite la navigation. Des outils avancés (“Aller à la déclaration”) peuvent pallier ce besoin, mais ils ne sont pas toujours disponibles pour des lecteurs externes à l’équipe de développement.
Le langage Catala
Bienvenue dans la référence du langage Catala ! L’objectif de ce chapitre est de présenter, point par point, chaque fonctionnalité du langage, avec des exemples et des conseils.
Dans cette référence, une fonctionnalité de langage correspond souvent à un élément de syntaxe comme dans l’aide‑mémoire de la syntaxe. Mais, contrairement à l’aide‑mémoire, cette référence contextualise les fonctionnalités, motive les choix de conception et donne des exemples d’utilisation.
À l’inverse du tutoriel Catala, ce guide de référence n’a pas vocation à vous apprendre Catala pas à pas : les fonctionnalités sont présentées de façon taxonomique et chaque explication ne s’appuie pas toujours sur les précédentes.
Le but de cette référence est donc de vous permettre de retrouver rapidement une fonctionnalité précise lorsque vous en avez besoin, après une première phase d’apprentissage via le tutoriel.
Si vous avez une question mais ne savez pas à quelle fonctionnalité elle se rattache, consultez la FAQ spécifique à Catala.
Programmation littéraire et syntaxe de base
Les fichiers de code source Catala doivent être interprétables comme des fichiers Markdown. Spécifiquement, la syntaxe Markdown de Catala est alignée sur la syntaxe Markdown de Pandoc.
Programmation littéraire
Tissage et extraction
La programmation littéraire mélange le code source et sa documentation dans le même document. Un fichier source Catala contient donc à la fois du code Catala et le texte de la spécification juridique pour ce code.
Pour exécuter le code ou le compiler vers un langage de programmation cible, le compilateur Catala ignore simplement tout le texte de spécification juridique du fichier source et ne prend que le code source. C’est ce qu’on appelle l’extraction (ou tangling).
Mais vous pourriez aussi vouloir produire un document complet lisible par un humain (ou même par un juriste) sur le programme et sa spécification. Le compilateur Catala et le système de construction vous permettent également de le faire, voir la référence du système de construction pour plus d’informations. C’est ce qu’on appelle le tissage (ou weaving).
Texte libre et paragraphes
Si vous mettez simplement du texte dans votre fichier de code source Catala, il sera interprété comme du texte libre et ignoré en tant que code source Catala. Ce mode texte libre est conçu pour que vous puissiez copier-coller les spécifications juridiques de votre programme Catala. Ces spécifications seront la base de votre programme, car vous les annoterez avec des blocs de code Catala.
Le texte libre suit la syntaxe Markdown classique : vous devez laisser une ligne vide pour introduire un nouveau paragraphe.
Ceci est un premier paragraphe de texte libre.
Cette phrase sera rendue sur la même ligne que la précédente.
Cette phrase commence un nouveau paragraphe après un saut de ligne.
Titres
Un document Markdown est organisé grâce à une hiérarchie de titres, chacun
introduit par #. Plus un titre a de #, plus il est profond dans
le plan du document. Utilisez ces titres pour répliquer la structure hiérarchique
des documents juridiques (sections, articles, etc.) ; chaque paragraphe
de la spécification juridique devrait avoir un titre spécifiant son intitulé.
# Titre du document
## Première section
### Article 1
...
## Deuxième section
### Article 2
...
### Article 3
...
En effet, le compilateur Catala gardera une trace de ces titres pour enrichir les positions du code source utilisées dans les messages d’erreur, le débogage et les interfaces d’explications.
Autres fonctionnalités Markdown
Pour le style (gras, italique), les tableaux, les liens, etc., vous pouvez utiliser toutes les fonctionnalités Markdown supportées par Pandoc.
Extension de fichier Markdown
Il est possible de suffixer les fichiers Catala avec l’extension
.md: foo.catala_en sera équivalent à foo.catala_en.md. En
particulier, cela permet à des outils externes d’interpréter les
fichiers Catala comme des fichiers Markdown. Cela permet, par exemple,
d’afficher proprement les fichiers Catala dans des visualisateurs web.
Principes de syntaxe de base
Blocs de code Catala
Tout le code source Catala doit être contenu à l’intérieur d’un bloc de code Catala. Un bloc de code Catala ressemble à ceci à l’intérieur de votre fichier source :
```catala
<votre code va ici>
```
En général, le code source Catala ne se soucie pas des sauts de ligne, des tabulations et des espaces ; vous pouvez organiser votre code comme vous le souhaitez. Cependant, cette syntaxe pour ouvrir et fermer les blocs de code Catala est très stricte et doit être strictement respectée.
- L’ouverture
```cataladoit être au début d’une nouvelle ligne (pas d’espace avant```catala). - Ne mettez pas d’espace entre
```etcatala. - Mettez toujours une nouvelle ligne après
```catala. - Mettez toujours la fermeture
```seule sur une nouvelle ligne. - Mettez toujours une nouvelle ligne après la fermeture
```. - Vous ne pouvez pas imbriquer les blocs de code, c’est-à-dire qu’une ouverture
```cataladoit toujours être fermée par une fermeture```sans ouvrir de nouveaux blocs de code entre les deux.
Commentaires à l’intérieur des blocs de code Catala
À l’intérieur d’un bloc de code Catala, vous pouvez commenter votre code en préfixant les lignes par
#. Les commentaires seront ignorés par le compilateur à l’exécution mais tissés dans
la documentation comme des spécifications juridiques en mode texte libre.
Mots-clés réservés
Certains mots sont réservés en Catala pour les mots-clés et ne peuvent donc pas être utilisés comme noms de variables. Si vous essayez, vous obtiendrez une erreur de syntaxe confuse car Catala croira que vous avez essayé d’utiliser le mot-clé au lieu du nom de variable.
Les mots-clés réservés en Catala sont :
champ d'application
conséquence
donnée
dépend de
déclaration
contexte
de
liste de
contient
énumération
entier
argent
décimal
date
durée
booléen
somme
rempli
définition
état
étiquette
exception
égal à
selon
n'importe quel
sous forme
sous condition
si
alors
sinon
condition
contenu
structure
assertion
avec
pour
tout
on a
règle
soit
existe
dans
parmi
combine
transforme chaque
en
tel
que
et
ou
ou bien
non
maximum
minimum
est
ou si liste vide alors
mais en remplaçant
initialement
nombre
an
mois
jour
vrai
faux
entrée
résultat
interne
arrondi
Inclusion textuelle
Bien qu’un module Catala doive être contenu dans un seul fichier, parfois les spécifications juridiques sont très volumineuses et il est impossible de les décomposer en modules logiquement indépendants : la loi est une grosse boule de code spaghetti. Dans ce cas, il serait injuste de vous forcer à avoir des fichiers sources Catala gigantesques pour représenter un module.
Par conséquent, Catala dispose d’une fonctionnalité d’inclusion textuelle. Elle fonctionne comme ceci. Si, à l’intérieur de
foo.catala_fr, vous mettez (en mode texte libre, pas à l’intérieur d’un bloc de code Catala) :
> Inclusion: bar.catala_fr
Alors, le tissage et l’extraction fonctionneront comme si vous aviez copié-collé le contenu
de bar.catala_fr à l’intérieur de foo.catala_fr à l’endroit où se trouve le > Inclusion.
Typiquement, vous voulez implémenter toutes les dispositions pour le calcul
d’une taxe. Ces dispositions sont réparties entre une loi, un règlement et
une jurisprudence. Chacun de ces éléments spécifie une partie du calcul, donc ils
doivent être à l’intérieur du même module Catala. Cependant, vous pouvez utiliser le mécanisme d’inclusion
textuelle pour diviser votre implémentation en quatre fichiers différents
reflétant les différentes sources de la spécification : taxe.catala_fr,
loi.catala_fr, reglement.catala_fr et jurisprudence.catala_fr.
taxe.catala_fr sera le fichier maître listant les autres :
# Calcul de la taxe
> Inclusion: loi.catala_fr
> Inclusion: reglement.catala_fr
> Inclusion: jurisprudence.catala_fr
Ensuite, vous pouvez copier-coller la spécification juridique dans loi.catala_fr,
reglement.catala_fr et jurisprudence.catala_fr, en commençant par des titres ##
dans chacun de ces fichiers car taxe.catala_fr comporte déjà le titre de niveau supérieur #.
Types, valeurs et opérations
Chaque valeur manipulée par les programmes Catala a un type bien défini qui est soit intégré, soit déclaré par l’utilisateur. Cette section de la référence du langage résume tous les différents types de types, comment les déclarer et comment construire des valeurs de ce type.
Types de base
Les types et valeurs suivants sont intégrés à Catala, s’appuyant sur des mots-clés du langage.
Booléens
Le type booléen n’a que deux valeurs, vrai et faux.
Opérations booléennes
| Symbole | Type du premier argument | Type du second argument | Type du résultat | Sémantique |
|---|---|---|---|---|
et | booléen | booléen | booléen | Et logique (strict) |
ou | booléen | booléen | booléen | Ou logique (strict) |
ou bien | booléen | booléen | booléen | Ou exclusif logique (strict) |
non | booléen | booléen | Négation logique |
Comparaisons
| Symbole | Type du premier argument | Type du second argument | Type du résultat | Sémantique |
|---|---|---|---|---|
=, != | tout sauf fonctions | même que le premier argument | booléen | (In)égalité structurelle |
>, >=, <, <= | entier, décimal, argent, date | même que le premier argument | booléen | Comparaison usuelle |
>, >=, <, <= | durée | durée | booléen | Comparaison usuelle si même unité, sinon erreur d’exécution |
Opérations sur les entiers
Le type entier représente les entiers mathématiques, comme 564 614 ou -2.
Notez que vous pouvez optionnellement utiliser le séparateur de nombres (espace) pour
rendre les grands entiers plus lisibles.
| Symbole | Type du premier argument | Type du second argument | Type du résultat | Sémantique |
|---|---|---|---|---|
+ | entier | entier | entier | Addition entière |
- | entier | entier | entier | Soustraction entière |
- | entier | entier | Négation entière | |
* | entier | entier | entier | Multiplication entière |
/ | entier | entier | décimal | Division rationnelle |
entier de | décimal, argent | entier | Conversion (arrondi au plus proche) |
Voir aussi le module Entier de la bibliothèque standard pour
plus d’opérations.
Décimaux
Le type décimal représente les nombres décimaux mathématiques (ou rationnels),
comme 0,21 ou -988 453,6842541. Notez que vous pouvez optionnellement utiliser le
séparateur de nombres (espace) pour rendre les grands décimaux plus lisibles. Les nombres
décimaux sont stockés avec une précision infinie en Catala, mais vous pouvez les
arrondir à volonté.
Un pourcentage est juste une valeur décimale, donc en Catala vous aurez 30% = 0,30
et vous pouvez utiliser la notation % si cela rend votre code plus facile à lire.
Opérations sur les décimaux
| Symbole | Type du premier argument | Type du second argument | Type du résultat | Sémantique |
|---|---|---|---|---|
+ | décimal | décimal | décimal | Addition rationnelle |
- | décimal | décimal | décimal | Soustraction rationnelle |
- | décimal | décimal | Négation rationnelle | |
* | décimal | décimal | décimal | Multiplication rationnelle |
/ | décimal | décimal | décimal | Division rationnelle |
arrondi de | décimal | décimal | Arrondi à l’unité la plus proche | |
décimal de | entier, argent | décimal | Conversion |
Voir aussi le module Décimal de la bibliothèque standard pour
plus d’opérations.
Argent
Le type argent représente un montant d’argent, positif ou négatif, dans une
unité monétaire (dans la version française de Catala, le symbole monétaire €
est utilisé), avec une précision au centime et pas en dessous. Les valeurs monétaires sont
notées comme des valeurs décimales avec au maximum deux chiffres fractionnaires et le
symbole monétaire, comme 12,36 € ou -871 84,1 €.
Opérations sur l’argent
| Symbole | Type du premier argument | Type du second argument | Type du résultat | Sémantique |
|---|---|---|---|---|
+ | argent | argent | argent | Addition monétaire |
- | argent | argent | argent | Soustraction monétaire |
- | argent | argent | Négation monétaire | |
/ | argent | argent | décimal | Division rationnelle |
arrondi de | argent | argent | Arrondi à l’unité la plus proche | |
argent de | entier, décimal | argent | Conversion (arrondi au centime le plus proche) |
Voir aussi le module Argent de la bibliothèque standard pour
plus d’opérations.
Dates
Le type date représente des dates dans le calendrier
grégorien. Ce type
ne peut pas représenter de dates invalides comme 2025-02-31. Les valeurs de ce type sont
notées suivant la notation ISO8601 (AAAA-MM-JJ)
entourée de barres verticales, comme |1930-09-11| ou |2012-02-03|.
Sémantique de l’addition de dates
Lors de la conception de Catala, l’équipe Catala a remarqué que la question “Qu’est-ce que le 31 janvier + 1 mois ?” n’avait pas de réponse consensuelle en informatique.
- En Java, ce sera le 28/29 février selon l’année bissextile.
- En Python, il est impossible d’ajouter des mois avec la bibliothèque standard.
- Dans
coreutils, cela donne le 3 mars (!).
Étant donné l’importance des calculs de dates dans les implémentations juridiques, l’équipe Catala a décidé de régler ce désordre et a décrit ses résultats dans un article de recherche :
L’addition de dates en Catala consiste à ajouter une durée à une date, donnant une nouvelle date dans le passé ou dans le futur selon que la durée est négative ou positive. La valeur de cette nouvelle date dépend du contenu de la durée :
- si la durée est un nombre de jours, alors Catala ajoutera ou soustraira simplement ce nombre de jours au jour de la date originale, en passant aux mois précédents ou suivants si nécessaire ;
- si la durée est un nombre d’années (disons,
x), alors Catala se comportera comme si la durée était de12 * x mois; - si la durée est un nombre de mois, Catala ajoutera ou soustraira simplement ce nombre de mois au mois de la date originale, en passant à l’année précédente ou suivante si nécessaire ; mais cette opération pourrait donner une date invalide (comme 31 janv. + 1 mois -> 31 fév.), qui doit être arrondie.
Il existe trois modes d’arrondi en Catala, dont la description est ci-dessous :
| Mode d’arrondi | Sémantique | Exemple |
|---|---|---|
| Pas d’arrondi | Erreur d’exécution si date invalide | 31 janv. + 1 mois échoue avec AmbiguousDateComputation |
| Arrondi supérieur | Premier jour du mois suivant | 31 janv. + 1 mois = 1er mars |
| Arrondi inférieur | Dernier jour du mois précédent | 31 janv. + 1 mois = 28/29 fév. (selon année bissextile) |
Par défaut, Catala est en mode “Pas d’arrondi”. Pour définir le mode d’arrondi vers le haut ou vers le bas, pour toutes les opérations de date à l’intérieur d’un champ d’application entier, voir la section de référence pertinente.
Enfin, si la durée à ajouter est composée de plusieurs unités (comme 2 mois + 21 jour), alors Catala commencera par ajouter
la composante avec la plus grande unité (ici, mois), puis la composante avec la plus petite unité (ici, jour).
Opérations sur les dates
| Symbole | Type du premier argument | Type du second argument | Type du résultat | Sémantique |
|---|---|---|---|---|
+ | date | durée | date | Addition de date (voir ci-dessus) |
- | date | durée | date | Soustraction de date |
- | date | date | durée | Nombre de jours entre les dates |
Date.accès_jour de | date | entier | Jour dans le mois (1..31) | |
Date.accès_mois de | date | entier | Mois dans l’année (1..12) | |
Date.accès_année de | date | entier | Numéro de l’année | |
Date.premier_jour_du_mois de | date | date | Premier jour du mois | |
Date.dernier_jour_du_mois de | date | date | Dernier jour du mois |
Voir aussi le module Date de la bibliothèque standard pour
plus d’opérations.
Durées
Le type durée représente des durées en termes de jours, mois et/ou années,
comme 254 jour, 4 mois ou 1 an. Les durées peuvent être négatives et combiner
un nombre de jours et de mois ensemble, comme 1 mois + 15 jour.
Il est toujours vrai qu’en termes de durées, 1 an = 12 mois. Cependant,
parce que les mois ont un nombre variable de jours, comparer des durées en jours
à des durées en mois est ambigu et nécessite des interprétations juridiques.
Pour cette raison, Catala lèvera une erreur d’exécution lors de la tentative d’effectuer une telle comparaison. De plus, la différence entre deux dates donnera toujours une durée exprimée en jours.
Opérations sur les durées
| Symbole | Type du premier argument | Type du second argument | Type du résultat | Sémantique |
|---|---|---|---|---|
+ | durée | durée | durée | Ajouter le nombre de jours, mois, années |
- | durée | durée | durée | Ajouter la durée opposée |
- | durée | durée | Nier les composantes de la durée | |
* | durée | entier | durée | Multiplier le nombre de jours, mois, années |
Conversion des types numériques de base
La conversion entre les types de base est explicite ; la syntaxe est <nom du type désiré> de <argument>.
| Type de l’argument | Type du résultat | Sémantique |
|---|---|---|
| décimal | entier | Troncature |
| entier | décimal | Valeur conservée |
| décimal | argent | Arrondi au centime le plus proche |
| argent | décimal | Valeur conservée |
| argent | entier | Troncature à l’unité la plus proche |
| entier | argent | Valeur conservée |
| décimal | entier | Troncature |
Types déclarés par l’utilisateur
Les types déclarés par l’utilisateur doivent être déclarés avant d’être utilisés dans le reste du programme. Cependant, la déclaration n’a pas besoin d’être placée avant l’utilisation dans l’ordre textuel.
Structures
Les structures combinent plusieurs éléments de données ensemble dans un seul type. Chaque élément de données est un “champ” de la structure. Si vous avez une structure, vous pouvez accéder à chacun de ses champs, mais vous avez besoin de tous les champs pour construire la valeur de la structure.
Les types de structure sont déclarés par l’utilisateur, et chaque type de structure a un nom
choisi par l’utilisateur. Les noms de structure commencent par une majuscule et doivent suivre
la convention de nommage CamelCase. Un exemple de déclaration de structure est :
déclaration structure Individu:
donnée date_naissance contenu date
donnée revenu contenu argent
donnée nombre_enfants contenu entier
Le type de chaque champ de la structure est obligatoire et introduit par contenu.
Il est possible d’imbriquer des structures (déclarer le type d’un champ d’une structure
comme une autre structure ou énumération), mais pas récursivement.
Les valeurs de structure sont construites avec la syntaxe suivante :
Individu {
-- date_naissance: |1930-09-11|
-- revenu: 100 000 €
-- nombre_enfants: 2
}
Pour accéder au champ d’une structure, utilisez simplement la syntaxe <valeur struct>.<nom champ>,
comme individu.revenu.
Énumérations
Les énumérations représentent une alternative entre différents choix, chacun encapsulant un modèle spécifique de données. En ce sens, les énumérations sont des “types somme” à part entière comme dans les langages de programmation fonctionnelle, et plus puissantes que les énumérations de type C qui listent juste des codes alternatifs qu’une valeur peut avoir. Chaque choix ou alternative de l’énumération est appelé un “cas” ou une “variante”.
Les types d’énumération sont déclarés par l’utilisateur, et chaque type d’énumération a un nom
choisi par l’utilisateur. Les noms d’énumération commencent par une majuscule et doivent suivre
la convention de nommage CamelCase. Un exemple de déclaration d’énumération est :
déclaration énumération PasDeCreditImpot:
-- PasDeCreditImpot
-- CreditImpotPourIndividu contenu Individu
-- CreditImpotApresDate contenu date
Le type de chaque cas de l’énumération est obligatoire et introduit par
contenu. Il est possible d’imbriquer des énumérations (déclarer le type d’un champ d’une
énumération comme une autre énumération ou structure), mais pas récursivement.
Les valeurs d’énumération sont construites avec la syntaxe suivante :
# Premier cas
PasDeCreditImpot,
# Deuxième cas
CreditImpotPourIndividu contenu Individu {
-- date_naissance: |1930-09-11|
-- revenu: 100 000 €
-- nombre_enfants: 2
},
# Troisième cas
CreditImpotApresDate contenu |2000-01-01|
Pour inspecter les valeurs d’énumération, voir dans quel cas vous êtes et utiliser les données associées, utilisez le filtrage par motif.
Listes
Le type liste de <autre type> représente un tableau de taille fixe d’un autre type.
Par exemple, liste de entier représente un tableau de taille fixe d’entiers.
Vous pouvez construire des valeurs de liste en utilisant la syntaxe suivante :
[1; 6; -4; 846645; 0]
Opérations sur les listes
| Syntaxe | Type du résultat | Sémantique |
|---|---|---|
<liste> contient <élément> | booléen | vrai si <liste> contient <élément>, faux sinon |
nombre de <liste> | entier | Longueur de la liste |
existe <var> parmi <liste> tel que <expr> | booléen | vrai si au moins un élément de liste satisfait <expr> |
pour tout <var> parmi <liste> on a <expr> | booléen | vrai si tous les éléments de liste satisfont <expr> |
transforme chaque <var> parmi <liste> en <expr> | liste | Transformation élément par élément, créant une nouvelle liste avec <expr> |
liste de <var> parmi <liste> tel que <expr> | liste | Crée une nouvelle liste avec seulement les éléments satisfaisant <expr> |
transforme chaque <var> parmi <liste> tel que <expr1> en | liste | Combine le filtrage et la transformation (voir deux dernières opérations) |
<liste1> ++ <liste2> | liste | Concaténer deux listes |
somme <type> de <liste> | <type> | Agrège le contenu (argent, entier, décimal) d’une liste |
maximum de <liste> (ou si liste vide alors <expr>) | type des éléments | Renvoie l’élément maximum de la liste (ou un défaut optionnel) |
minimum de <liste> (ou si liste vide alors <expr>) | type des éléments | Renvoie l’élément minimum de la liste (ou un défaut optionnel) |
contenu de <var> parmi <liste> tel que <expr1> est maximum (ou si liste vide alors <expr2>) | type des éléments | Renvoie l’élément arg-maximum de la liste (ou un défaut optionnel) |
contenu de <var> parmi <liste> tel que <expr1> est minimum (ou si liste vide alors <expr2>) | type des éléments | Renvoie l’élément arg-minimum de la liste (ou un défaut optionnel) |
combine tout <var> parmi <liste> dans <acc> initialement <expr1> avec <expr2> | type des éléments | Plie <liste>, commençant avec <expr1> et accumulant avec <expr2> |
Ces opérations sur les listes reflètent le contenu d’une bibliothèque de listes de base pour un langage de
programmation fonctionnelle. Mais dans
une telle bibliothèque de listes, une fonctionnalité clé est la capacité d’itérer sur deux ou plusieurs
listes en même temps pour les combiner élément par élément. En Catala, cela peut être
fait simplement en écrivant un n-uplet de listes au lieu
d’une <liste> à l’intérieur des opérations ; alors vous avez un n-uplet de <var> au lieu
d’un seul pour correspondre aux éléments de chaque liste dans le n-uplet de listes. Par exemple,
transforme chaque (x, y) parmi (lst1, lst2) en x + y
Cela fonctionne aussi avec existe, pour tout, liste de, combine, etc.
Notez que si lst1 et lst2 n’ont pas la même longueur, le programme Catala
s’arrêtera avec une erreur d’exécution.
N-uplets
Le type (<type 1>, <type 2>) représente une paire d’éléments, le premier étant
de type 1, le second étant type 2. Il est possible d’étendre le type paire
en un type triplet, ou même un n-uplet pour un nombre arbitraire n en
répétant les éléments après ,.
Vous pouvez construire des valeurs de n-uplet avec la syntaxe suivante :
(|2024-04-01|, 30 €, 1%) # Cette valeur a le type (date, argent, décimal)
Vous pouvez accéder au n-ième élément d’un n-uplet, commençant à 1, avec la syntaxe <n-uplet>.n.
Valeurs optionnelles
Le type optionnel de <type> peut être utilisé pour contenir une valeur de <type> qui pourrait
être absente. Par exemple, optionnel de entier est équivalent à l’énumération :
déclaration énumération EntierOptionnel:
-- Absent
-- Présent contenu entier
Mais l’avantage de optionnel de entier par rapport à un tel EntierOptionnel est
qu’il n’a pas besoin d’être déclaré pour chaque type avec lequel il est utilisé. Comme une
énumération, les valeurs de type optionnel peuvent être créées en utilisant Présent contenu <expr>, et utilisées dans les formes selon <expr> sous forme et <expr> sous forme <constr>, par ex.
si foo sous forme Présent de bar et bar > 3 alors ...
Fonctions
Les types de fonction représentent des valeurs de fonction, c’est-à-dire des valeurs qui nécessitent d’être appelées avec des arguments pour produire un résultat. Les fonctions sont des valeurs de première classe car Catala est un langage de programmation fonctionnelle.
La syntaxe générale pour décrire un type de fonction est :
<type du résultat> dépend de
<nom de l'argument 1> contenu <type de l'argument 1>,
<nom de l'argument 2> contenu <type de l'argument 2>,
...
Par exemple, argent dépend de revenu contenu argent, nombre_enfants contenu entier peut être le type d’une fonction de calcul d’impôt.
Cependant, contrairement à la plupart des langages de programmation, il n’est pas possible de construire directement une fonction comme une valeur ; les fonctions sont créées et passées avec d’autres mécanismes du langage.
Fonctions polymorphes
Une fonctionnalité clé d’un langage de programmation est sa capacité à éviter la duplication de code ; le programmeur devrait réutiliser autant de code que possible à travers des abstractions, telles que des fonctions et des modules. De plus, une fonctionnalité standard du génie logiciel moderne est le polymorphisme, c’est-à-dire écrire des morceaux de code qui peuvent opérer sur plusieurs types de valeurs à la fois, évitant au programmeur la nécessité de dupliquer le même code plusieurs fois selon le type des arguments.
Le polymorphisme peut être implémenté de plusieurs manières selon le langage de programmation. Java a des classes abstraites et des interfaces, C++ a des templates, Python et Javascript ont une résolution dynamique des méthodes. Catala, étant un langage de programmation fonctionnelle typé statiquement, a une façon de faire du polymorphisme qui est inspirée par les mécanismes de base présents en OCaml.
Concrètement, lors de la définition d’une fonction en Catala, vous pouvez donner le type n'importe quel
à un argument ou au type de retour. Cela signifie que lorsque vous appelez la fonction,
vous pouvez l’appeler avec un argument qui a n’importe lequel des types possibles. Nous pouvons appeler
un tel argument “générique”, ou dire qu’il a un type “joker”. Par exemple, il est
pratique de pouvoir écrire la fonction est_vide une fois pour des listes d’
éléments de n’importe quel type :
déclaration est_vide contenu booléen
dépend de argument contenu liste de n'importe quel
égal à (nombre de argument = 0)
Bien que les arguments génériques soient puissants, ils ont aussi l’inconvénient que
vous ne pouvez pas vraiment “faire” grand-chose avec une valeur qui a le type n'importe quel puisque
vous ne savez pas quel type elle a. En particulier, il est impossible d’utiliser
les opérateurs comme +, *, -, /, <=, etc. puisque ces opérateurs ne
fonctionnent que pour les types de base (entier, décimal, etc.) et pas tous les autres
types qui peuvent être définis par l’utilisateur (structures, énumérations, etc.).
Parfois, il y a plusieurs arguments génériques ou types de retour dans la même
fonction mais vous voulez qu’ils partagent le même type générique. Par exemple, si une
fonction inverse les éléments d’une liste, le type des éléments de la liste sera
le même à l’entrée et à la sortie de la fonction. Les types n'importe quel peuvent être nommés à
cette fin, en utilisant n'importe quel de type <nom>. Par exemple :
déclaration inverser contenu liste de n'importe quel de type type_element
dépend de argument contenu liste de n'importe quel de type type_element
Si vous ne nommez pas le type n'importe quel explicitement, il sera supposé que
chaque n'importe quel est un n'importe quel frais et indépendant des autres n'importe quel.
Cela peut conduire à des messages d’erreur complexes du compilateur, alors rappelez-vous de donner
le même nom aux types n'importe quel que vous savez devoir être liés ensemble.
Les signatures de fonctions polymorphes peuvent être vraiment utiles lors de la définition d’interfaces pour des modules implémentés en externe.
Champs d’application, fonctions et constantes
Puisque Catala est un langage fonctionnel, les déclarations de haut niveau en Catala se comportent comme des fonctions. Bien que Catala possède de véritables fonctions de haut niveau, l’équipe Catala conseille de limiter leur utilisation en pratique et d’utiliser des champs d’application à la place, car seuls les champs d’application bénéficient du mécanisme d’exceptions qui est la fonctionnalité phare de Catala.
Déclarations de champ d’application
Un champ d’application est une fonction dont le prototype (c’est-à-dire la signature) est explicitement déclaré, et dont le corps peut être dispersé en petits morceaux à travers la base de code de programmation littéraire.
Vous devez déclarer (déclaration champ d'application Foo ...) une fois à un endroit dans le
code ; ensuite vous pouvez utiliser ce champ d’application (champ d'application Foo) pour ajouter des définitions
de variables plusieurs fois et n’importe où ailleurs
dans votre base de code.
Les sections de référence suivantes vous indiquent comment écrire la déclaration de champ d’application. Comme
divulgâchage, voici un exemple complet pour un champ d’application minimal nommé Min avec deux
variables, a et b :
déclaration champ d'application Min:
entrée a contenu entier
résultat b contenu décimal
Pour des informations sur le corps du champ d’application, y compris les définitions de variables de champ d’application, consultez les sections de référence sur les définitions et exceptions ou les expressions.
Nom du champ d’application
Les déclarations de champ d’application commencent par la déclaration du nom du champ d’application. Les noms de champ d’application commencent
par une majuscule et suivent la convention CamelCase. Une déclaration de champ d’application est un
élément de haut niveau à l’intérieur d’un bloc de code Catala. Par exemple, si vous voulez nommer votre
champ d’application Foo, alors sa déclaration commence par :
déclaration champ d'application Foo:
...
Le contenu de la déclaration de champ d’application (à l’intérieur des ...) est composé de :
- déclarations de variables de champ d’application ;
- sous-champs d’application appelés dans le champ d’application.
Ces deux éléments sont décrits ci-dessous.
Déclarations de variables de champ d’application
Après le nom du champ d’application viennent les variables de champ d’application dans la déclaration de champ d’application. Les variables de champ d’application sont similaires aux variables locales dans une fonction. Leur déclaration comporte :
- leur statut entrée/résultat ;
- leur nom ;
- leur type ;
- optionnellement, une liste d’états pour la variable.
Les noms de variables commencent par une minuscule et suivent la convention snake_case.
La syntaxe pour la déclaration de la variable bar est :
<statut entrée/résultat> bar contenu <type> [<liste d'états de variable>]
Voici la syntaxe pour déclarer le champ d’application Foo avec les variables locales
baz, fizz et buzz ; baz est une entrée booléenne, fizz est une variable
interne de type date avec deux états avant et après, et buzz est un
décimal qui est à la fois entrée et résultat du champ d’application :
déclaration champ d'application Foo:
entrée baz contenu booléen
interne fizz contenu date
état avant
état après
entrée résultat buzz contenu décimal
dépend de arg contenu date
Les explications pour le statut entrée/résultat, les types de variables et les états de variables suivent ci-dessous.
Qualificateurs d’entrée/résultat
Il existe six types de qualificateurs d’entrée/résultat :
entréerésultatinternecontexteentrée résultatcontexte résultat
Variables d’entrée
Les variables d’entrée nécessitent qu’une valeur soit fournie lorsque le champ d’application est appelé, comme un argument de fonction. Elles ne peuvent pas être redéfinies à l’intérieur du champ d’application.
Variables de résultat
Les variables de résultat sont définies à l’intérieur du champ d’application, et leur valeur est renvoyée comme partie du résultat de l’appel du champ d’application.
Variables internes
Lorsqu’une variable n’est ni une entrée ni un résultat du champ d’application, elle doit être
qualifiée d’interne.
Variables de contexte
Les variables de contexte sont essentiellement des entrées optionnelles au champ d’application avec une valeur
par défaut définie à l’intérieur du champ d’application. Supposons que le champ d’application Foo a une variable de contexte
bar. Si vous fournissez une valeur x pour bar lors de l’appel de Foo, le calcul de Foo
considérera que bar = x. Mais si vous ne fournissez pas de valeur
pour bar lors de l’appel de Foo, Foo prendra comme valeur bar la définition
existante de bar à l’intérieur de Foo.
Ce comportement curieux pour la variable contexte est motivé par des cas d’utilisation où
l’on veut créer une exception pour une variable de champ d’application depuis l’extérieur du champ d’application.
Voir la FAQ Catala
pour plus d’explications.
Variables d’entrée et de résultat
Parfois, on veut que l’entrée d’un champ d’application soit copiée et transmise comme partie du
résultat de l’appel du champ d’application. C’est-à-dire qu’une telle variable serait à la fois entrée et
résultat, c’est pourquoi la syntaxe pour cela est entrée résultat. Dans ce cas, la
variable ne peut pas être définie à l’intérieur du champ d’application (car c’est une entrée). Cela fonctionne aussi
en remplaçant entrée par contexte.
Types de variables et fonctions
Le type de la variable suivant le mot-clé contenu peut être n’importe quel identifiant de type
décrit dans la référence des types, y compris les
types de fonction.
Variables de champ d’application condition
Une syntaxe spéciale existe pour le cas spécifique d’une variable de champ d’application
booléenne dont la valeur par défaut est faux. Ce cas correspond directement à une condition
juridique, il est donc nommé condition en Catala. La syntaxe pour déclarer une
variable de champ d’application condition (ici nommée bar, avec un qualificateur interne) est :
déclaration champ d'application Foo:
interne bar condition
Les variables condition ont également une syntaxe spéciale pour les définitions
(voir la section de référence pertinente).
Déclarations d’états de variable
Comme mentionné dans le tutoriel, il est possible de distinguer plusieurs états pendant le calcul de la valeur finale de la variable. Les états se comportent comme une chaîne de variables, la suivante utilisant la valeur de la précédente pour son calcul.
La liste ordonnée des états que la variable prend pendant le calcul est déclarée
aux côtés de la variable. Par exemple, si la variable entière interne foo a les états
a, b et c dans cet ordre, alors foo doit être déclarée avec :
déclaration champ d'application Foo:
interne foo contenu entier
état a
état b
état c
L’ordre des clauses état dans la déclaration détermine l’ordre de calcul
entre les états pour la variable. Vous pouvez écrire foo état a pour la valeur de
la variable foo pendant l’état a. Avec cet ordre entre a, b et c, foo état b peut dépendre de foo état a et foo état c peut dépendre de foo état b, mais pas l’inverse.
Structure de résultat du champ d’application
Toutes les variables qualifiées de résultat dans un champ d’application feront partie du résultat de
l’appel du champ d’application. Ce résultat est matérialisé comme une structure, avec chaque
variable de résultat correspondant à un champ de la structure. Cette structure de résultat
du champ d’application est utilisable comme n’importe quel autre type de structure déclaré par l’utilisateur.
Par exemple, si j’ai la déclaration de champ d’application :
déclaration champ d'application Foo:
résultat bar contenu entier
résultat baz contenu décimal
interne fizz contenu booléen
entrée buzz contenu date
Alors implicitement, une structure de résultat également nommée Foo sera utilisable comme si elle avait la
déclaration suivante :
déclaration structure Foo:
donnée bar contenu entier
donnée baz contenu décimal
Cette déclaration implicite de structure de résultat est utile pour transporter le résultat d’un appel de champ d’application dans une autre variable et les réutiliser plus tard dans le code.
Déclarations de sous-champs d’application
Les champs d’application sont des fonctions, et en tant que tels, ils peuvent être appelés comme des fonctions. Appeler un champ d’application peut être fait à l’intérieur de n’importe quelle expression en fournissant simplement les variables d’entrée comme arguments.
Mais parfois, on sait que l’on effectuera toujours un seul appel statique de sous-champ d’application depuis un champ d’application appelant. Pour cette situation, Catala a une syntaxe déclarative spéciale rendant plus facile pour les juristes de comprendre ce qui se passe.
Les sous-champs d’application sont déclarés dans la déclaration de champ d’application comme des variables de champ d’application.
Par exemple, si à l’intérieur du champ d’application Foo vous appelez le champ d’application Bar exactement une fois,
alors vous écrirez :
déclaration champ d'application Foo:
appel_bar champ d'application Bar
appel_bar est le nom (de votre choix) qui désignera cette instance spécifique
d’appel de Bar à l’intérieur de Foo. Vous pouvez avoir plusieurs appels statiques au
même champ d’application comme :
déclaration champ d'application Foo:
premier_appel_bar champ d'application Bar
second_appel_bar champ d'application Bar
Vous pouvez également préfixer par résultat la ligne appel_bar champ d'application Bar, assurant
que la structure de résultat de l’appel à Bar sera présente comme champ
appel_bar de la structure de résultat de l’appel à Foo.
Comme avec les structures et les énumérations, il n’est pas possible pour ces appels de champ d’application d’être récursifs.
Voir appel de sous-champ d’application pour savoir comment fournir des arguments à l’appel de sous-champ d’application et récupérer les résultats.
Déclarations et définitions de constantes et fonctions globales
Bien que les champs d’application soient le cheval de bataille des implémentations Catala, il y a parfois
de petites choses qui n’ont pas besoin d’être à l’intérieur d’un champ d’application à part entière. Par exemple,
déclarer de petites fonctions utilitaires comme exces_de qui calcule l’excès du
premier argument par rapport au second, ou déclarer une constante pour le nombre de
secondes dans une journée.
Pour cela, Catala permet la déclaration de constantes et fonctions globales, qui ne sont pas liées à un champ d’application particulier.
Puisque les champs d’application sont similaires aux fonctions, il est possible d’utiliser des déclarations de fonctions régulières pour émuler toutes les fonctionnalités des champs d’application. Pourquoi les champs d’application existent-ils en premier lieu alors ?
Parce que les champs d’application permettent la définition d’exceptions pour leurs variables, ce qui est impossible avec les déclarations de fonctions et de constantes régulières. Les exceptions sont la fonctionnalité phare de Catala, et utiliser Catala sans utiliser les exceptions n’a aucun sens ; utilisez un langage de programmation régulier à la place.
Par conséquent, la déclaration de constantes et de fonctions ne doit être utilisée que pour leur usage prévu. La règle générale est : si une définition de constante ou de variable prend plus de quelques lignes de code, elle devrait être transformée en champ d’application !
Constantes
Pour déclarer et définir globalement une constante foo de type <type> (n’importe quel élément
dans la référence des types) avec la valeur <expr> (n’importe quelle
expression de type <type>), utilisez la syntaxe suivante :
déclaration foo contenu <type> égal à <expr>
Par exemple :
déclaration foo contenu booléen égal à vrai
déclaration bar contenu entier égal à 4643 * 876 - 524
Fonctions
Si le type de la constante que vous déclarez et définissez est un type de fonction, alors la constante devient une fonction globale gratuitement. Par exemple :
déclaration foo contenu est_rond contenu booléen
dépend de x contenu décimal
égal à
arrondi de x = x
déclaration exces_de contenu argent
dépend de
arg contenu argent,
seuil contenu argent
égal à
si arg >= seuil
alors arg - seuil
sinon 0 €
Définitions et exceptions
Alors que la section de référence précédente couvrait la
déclaration des champs d’application (introduite par déclaration champ d'application Foo) qui doit être faite une fois
pour chaque champ d’application dans la base de code, cette section couvre la définition des variables de champ d’application
dispersées à travers la base de code de programmation littéraire (introduite par
champ d'application Foo).
Les définitions de variables de champ d’application et les assertions n’ont de sens qu’à l’intérieur d’un champ d’application donné,
c’est pourquoi tous les exemples ci-dessous montreront la fonctionnalité à l’intérieur d’un bloc champ d'application Foo
qui est supposé avoir déjà été déclaré ailleurs.
La syntaxe complète de ce qui sera couvert dans cette section est :
champ d'application <nom_champ_application>:
[étiquette <nom_étiquette>]
[exception <nom_étiquette>]
définition <nom_variable_champ_application>
[de <paramètres>] [état <nom_état>]
[sous condition <expression> conséquence]
égal à <expression>
assertion <expression>
Définitions de variables de champ d’application
Les définitions de variables de champ d’application sont l’endroit où vivra la majeure partie du code Catala.
Définir une variable bar à l’intérieur du champ d’application Foo comme la valeur 42 est aussi simple que :
champ d'application Foo:
définition bar égal à 42
Bien sûr, vous pouvez remplacer 42 par n’importe quelle expression tant qu’elle a le type correct par rapport à la déclaration de la variable de champ d’application.
Variables de champ d’application qui sont des fonctions
Si la variable de champ d’application que vous définissez est une variable de fonction, par exemple si foo
est une fonction du champ d’application Foo avec les arguments x et y, alors la syntaxe
pour définir la variable est :
champ d'application Foo:
définition foo de x, y égal à x + y
Variables de champ d’application avec plusieurs états
Si la variable de champ d’application a plusieurs états,
par exemple si bar a les états debut, milieu et fin, alors la syntaxe pour définir
l’état milieu de la variable est :
champ d'application Foo:
définition bar état milieu égal à bar * 2
# "bar" ci-dessus fait référence à la valeur de bar dans l'état précédent,
# ici "debut"
Les états de variables et les fonctions se mélangent de cette façon :
champ d'application Foo:
définition foo de x, y état milieu égal à (foo de x, y) * 2 + x + y
Variables de champ d’application qui sont condition
Les variables de champ d’application condition (variables de champ d’application booléennes avec une valeur par défaut
de faux) ont une syntaxe différente, adaptée aux juristes, pour les définitions :
définition est remplacé par règle et égal à <expression> est remplacé
par rempli ou non rempli :
champ d'application Foo:
# Les règles viennent généralement toujours sous la forme de définitions conditionnelles, voir ci-dessous
règle bar sous condition [...] conséquence non rempli
Définitions conditionnelles
La principale fonctionnalité de Catala est de pouvoir décomposer les définitions de variables de champ d’application en plusieurs morceaux. En effet, les textes juridiques définissent souvent les choses par morceaux dans plusieurs articles, chaque article traitant d’une situation spécifique donnant une définition spécifique pour une quantité. Ce modèle est reflété dans Catala sous la forme de définitions conditionnelles.
Comme couvert dans le tutoriel, il est totalement attendu en Catala qu’une variable de champ d’application donnée ait plusieurs définitions dans la base de code. Le code “fonctionnera” car ces multiples définitions sont conditionnelles, et chacune ne se déclenchera que si sa condition est remplie.
Les conditions sont introduites dans les définitions de variables de champ d’application juste avant
le mot-clé égal à avec la syntaxe suivante :
champ d'application Foo:
définition bar sous condition fizz >= 0 conséquence égal à 42
Suivez la section pertinente du tutoriel pour plus d’informations sur la façon d’utiliser les définitions conditionnelles pour encoder des dispositions juridiques.
Parfois, la même condition s’appliquera à plusieurs définitions regroupées dans le
même bloc champ d'application. Cela vient souvent d’une condition temporelle sur laquelle
les définitions s’appliquent, comme :
champ d'application Foo:
définition bar sous condition date_courante >= |2025-01-01|
conséquence égal à [...]
définition baz sous condition date_courante >= |2025-01-01|
conséquence égal à [...]
Pour éviter de dupliquer date_courante >= |2025-01-01|, vous pouvez mettre la
condition directement sur le bloc de champ d’application avec :
champ d'application Foo sous condition date_courante >= |2025-01-01|:
définition bar égal à [...]
définition baz égal à [...]
Exceptions et priorités
Lorsqu’il y a plusieurs définitions conditionnelles ou non conditionnelles pour une variable de champ d’application donnée, il est parfois nécessaire de désambiguïser laquelle va prévaloir lorsque les conditions pour 2 définitions ou plus se déclenchent en même temps. Cela se fait en définissant une structure d’exceptions entre les multiples définitions de la même variable de champ d’application. Cette structure d’exceptions est en fait un arbre, car vous pouvez avoir des exceptions d’exceptions.
Suivez la section pertinente du tutoriel pour plus d’informations sur la façon d’utiliser les exceptions pour encoder des dispositions juridiques.
Déclarer une définition exceptionnelle à une seule définition de cas de base
Commençons par le cas le plus basique. Chaque nœud de l’arbre des exceptions pour une
variable de champ d’application donnée commence par une définition conditionnelle ou non conditionnelle.
Vous pouvez donner une étiquette à ce nœud de l’arbre avec la syntaxe étiquette :
champ d'application Foo:
étiquette cas_base définition bar égal à 42
Ensuite, plus tard dans la base de code, si vous voulez ajouter une exception à ce cas_base
de la définition de bar, vous le ferez avec la syntaxe :
champ d'application Foo:
# La ligne ci-dessous signifie que la définition actuelle est une exception *à*
# l'autre définition avec l'étiquette "cas_base".
exception cas_base
définition bar
sous condition fizz = 0
conséquence égal à
0
Dans cette configuration avec une seule définition de cas de base et une définition exceptionnelle,
il n’y a qu’un seul choix quant à ce à quoi l’exception fait référence (l’autre
définition). Dans ces cas où il est non ambigu à quoi vous définissez
une exception, vous pouvez omettre l’étiquette et simplement écrire :
champ d'application Foo:
définition bar égal à 42
...
champ d'application Foo:
exception définition bar
sous condition fizz = 0
conséquence égal à
0
S’il y a une ambiguïté avec votre configuration utilisant ce format raccourci, le compilateur Catala vous avertira. Quoi qu’il en soit, il est toujours plus clair et de meilleure pratique de donner des étiquettes aux définitions, surtout avec des structures d’exception complexes.
Déclarer plusieurs définitions comme une exception à une seule définition
Dans l’exemple précédent, bar était défini exceptionnellement à 0 si fizz = 0. Et
si vous voulez étendre ce comportement avec deux autres définitions exceptionnelles qui
définissent bar à 1 et -1 respectivement lorsque fizz > 0 et fizz < 0 ? Ces
trois définitions exceptionnelles ne peuvent pas entrer en conflit les unes avec les autres car fizz est
soit positif, négatif ou 0. Donc, ces trois définitions exceptionnelles se comportent
comme une grande exception au cas de base (où bar est défini à 42).
Pour regrouper les trois définitions exceptionnelles ensemble en Catala et les définir comme une
exception au cas de base, il suffit de donner aux trois définitions
exceptionnelles la même étiquette fizz_exn (vous pouvez choisir le nom de l’étiquette) et
l’indication d’exception :
champ d'application Foo:
étiquette cas_base définition bar égal à 42
...
champ d'application Foo:
étiquette fizz_exn
exception cas_base
définition bar
sous condition fizz = 0
conséquence égal à
0
...
champ d'application Foo:
étiquette fizz_exn
exception cas_base
définition bar
sous condition fizz > 0
conséquence égal à
1
...
champ d'application Foo:
étiquette fizz_exn
exception cas_base
définition bar
sous condition fizz < 0
conséquence égal à
-1
Les deux étiquettes cas_base et fizz_exn ci-dessus rendent très clair ce qui se
passe, au prix d’être un peu verbeux. Dans cet exemple, puisqu’il
n’y a qu’une seule définition dans le cas de base, il est non ambigu à quoi
les définitions exceptionnelles sont exception, et si elles sont toutes des exceptions
à la même chose sans aucune étiquette, elles seront regroupées implicitement ensemble.
Donc, vous auriez pu omettre toutes les étiquettes et écrire :
champ d'application Foo:
définition bar égal à 42
...
champ d'application Foo:
exception définition bar
sous condition fizz = 0
conséquence égal à
0
...
champ d'application Foo:
exception définition bar
sous condition fizz > 0
conséquence égal à
1
...
champ d'application Foo:
exception définition bar
sous condition fizz < 0
conséquence égal à
-1
Déclarer plusieurs définitions comme une exception à un groupe de définitions
Pour construire sur notre exemple fil rouge, imaginez maintenant que la valeur de bar dans le
cas de base dérive avec le temps : 42 avant 2025 mais 43 après. Alors, vous avez besoin
de deux définitions conditionnelles dans le cas de base (ces deux définitions regroupées
sont toujours mutuellement exclusives). Cela est réalisé en Catala simplement en donnant
la même étiquette cas_base aux deux définitions de cas de base :
champ d'application Foo:
étiquette cas_base définition bar
sous condition date_courante < |2025-01-01|
conséquence égal à
42
...
champ d'application Foo:
étiquette cas_base définition bar
sous condition date_courante >= |2025-01-01|
conséquence égal à
43
...
champ d'application Foo:
étiquette fizz_exn
exception cas_base
définition bar
sous condition fizz = 0
conséquence égal à
0
...
champ d'application Foo:
étiquette fizz_exn
exception cas_base
définition bar
sous condition fizz > 0
conséquence égal à
1
...
champ d'application Foo:
étiquette fizz_exn
exception cas_base
définition bar
sous condition fizz < 0
conséquence égal à
-1
Non ; ici la situation est déjà assez complexe pour créer une ambiguïté quant à ce dont les règles exceptionnelles sont une exception.
Empiler les exceptions, et plus encore
Les exemples précédents montrent comment utiliser la syntaxe étiquette et exception
pour regrouper des définitions ensemble et déclarer des priorités exceptionnelles. Cette syntaxe
est tout ce dont vous avez besoin ! En particulier, vous pouvez empiler les exceptions en définissant des chaînes
et des branches de relation exception, etc. Cela est couvert de manière extensive
dans la section pertinente du tutoriel.
Appel de sous-champ d’application
Une fois que vous avez déclaré un sous-champ d’application,
vous devrez définir ses arguments en définissant les variables d’entrée du
sous-champ d’application dans le champ d’application. Par exemple, si le champ d’application Foo a un sous-champ d’application bar appelant
le champ d’application Bar qui a des variables d’entrée fizz et buzz, vous devrez définir
bar.fizz et bar.fuzz avec cette syntaxe :
champ d'application Foo:
définition bar.fizz égal à [...]
définition bar.fuzz égal à [...]
Bien sûr, vous pouvez utiliser des exceptions et des définitions conditionnelles pour ces définitions de variables d’entrée de sous-champ d’application.
Une fois que vous avez défini toutes les variables d’entrée du champ d’application
(ainsi que certaines variables de contexte si nécessaire),
vous pouvez simplement faire référence aux variables de résultat du sous-champ d’application
dans des expressions ultérieures. Par exemple, si Bar a une variable de résultat fizzbuzz, alors
vous pouvez faire référence à bar.fizzbuzz comme le résultat de fizzbuzz lors de l’appel de Bar
avec les arguments bar.fizz et bar.fuzz.
Assertions
Les assertions en Catala sont des expressions attachées aux champs d’application (elles peuvent dépendre des variables de champ d’application) qui doivent toujours être vraies. Elles peuvent être utilisées pour :
- la validation des entrées et les préconditions de champ d’application ;
- vérifier des invariants logiques ;
- tester une sortie attendue.
Leur syntaxe est aussi simple que :
champ d'application Foo:
assertion bar + 2 = fizz * 4
Il est possible d’attacher un attribut
#[error.message] à une assertion afin
d’y ajouter un message qui sera affiché en cas d’échec.
Mode d’arrondi des dates
Pour définir le mode d’arrondi du calcul de date vers le haut ou vers le bas (voir la section de référence pertinente), pour toutes les opérations de date à l’intérieur d’un champ d’application entier, utilisez cette syntaxe :
# Supposons que vous vouliez définir le mode d'arrondi pour les opérations de date
# à l'intérieur du champ d'application Foo déclaré ailleurs
champ d'application Foo:
date arrondi supérieur # arrondi à la date suivante
# ou
date arrondi inf # arrondi à la date précédente
Expressions
Les expressions en Catala représentent le cœur des règles de calcul, qui apparaissent dans les définitions de variables de champ d’application ou dans les définitions de constantes globales.
Référence rapide de la grammaire BNF des expressions
Référence rapide de la grammaire BNF des expressions
La forme de Backus-Naur (BNF) est un format pratique pour résumer ce qui compte comme une expression en Catala. Si vous êtes familier avec ce format, vous pouvez le lire ci-dessous :
<expr> ::=
| (<expr 1>, <expr 2>, ...) # N-uplet
| <expr>.<nom champ> # Accès champ structure
| <expr>.<entier> # Accès composant n-uplet
| [<expr 1>; <expr 2>; ...] # Liste
| <structure> { -- <nom champ 1>: <expr 1> -- ...} # Valeur structure
| <variante énum> contenu <expr> # Valeur énum
| <expr 1> de <expr 2>, <expr 3>, ... # Appel fonction
| résultat de <nom champ d'application> avec # Appel direct champ d'application
{ -- <variable 1>: <expr 1> -- ... }
| selon <expr> sous forme # Filtrage par motif
-- <variante énum 1>: <expr 1>
-- <variante énum 2> contenu <variable>: <expr 2>
| <expr> sous forme <variante énum> # Test variante motif
| <expr> sous forme <variante énum> contenu <variable> # Idem avec liaison contenu
et <expr> # et test sur la variable
| <expr> mais en remplaçant # Mises à jour partielles structure
{ -- <variable 1>: <expr 1> -- ... }
| - <expr> # Négation
| <expr>
< + - * / et ou non ou bien > < >= <= == != > <expr> # Opérations binaires
| si <expr 1> alors <expr 2> sinon <expr 3> # Conditionnelles
| soit <variable> égal à <expr 1> dans <expr 2> # Liaison locale
| assertion <expr 1> dans <expr 2> # Assertion locale
| ... # Variable, littéraux,
# opérateurs de liste, ...
Références à d’autres variables
À l’intérieur d’une expression, vous pouvez faire référence au nom d’autres variables du même champ d’application, ou à des constantes et fonctions de haut niveau.
Certaines variables de champ d’application peuvent avoir plusieurs états.
Supposons que vous ayez une variable de champ d’application foo qui a les états bar et baz dans cet ordre.
Vous pouvez soit faire référence à foo, foo état bar ou foo état baz, mais la capacité
ou la signification de ces références dépend du contexte selon les règles suivantes.
- À l’intérieur de l’expression de
définition foo état bar, vous ne pouvez pas faire référence àfoo, nifoo état barnifoo état baz, puisquebarest le premier état etfooest en cours de définition pour le premier état. - À l’intérieur de l’expression de
définition foo état baz, vous pouvez faire référence àfooet cela fera en fait référence à l’état précédemment défini pourfoo, icibar. Doncfooetfoo état barsont équivalents dans ce contexte, et vous ne pouvez pas faire référence àfoo état bazpuisqu’il est en cours de définition. - En dehors des définitions de
foo, vous pouvez faire référence àfoo état baretfoo état baz. Si vous faites référence simplement àfoo, cela prendra par défaut le dernier état, icibaz. - Si
fooest une variableentréedu champ d’application, alors son premier état ne peut pas être défini et sera valorisé par l’argument du champ d’application lorsqu’il est appelé.
Pour référencer une variable d’un autre module, utilisez la syntaxe
<nom du module>.<nom de la variable>.
Valeurs et opérations
Toutes les valeurs et opérations présentées précédemment sont des expressions à part entière.
Parenthèses
Vous pouvez utiliser des parenthèses (...) autour de n’importe quelle partie ou sous-partie d’une expression
pour vous assurer que le compilateur comprendra correctement ce que vous tapez.
Variables locales et liaisons let
À l’intérieur d’une définition complexe d’une variable de champ d’application, il est souvent utile de donner un nom à une
quantité intermédiaire pour promouvoir sa réutilisation, ou simplement pour rendre le code plus
lisible. Bien qu’il
soit toujours possible d’introduire une nouvelle variable de
champ d’application à cet effet, vous pouvez également utiliser une
variable locale plus légère qui n’affecte que l’expression courante. La syntaxe pour celles-ci est soit foo égal à ... dans .... Par exemple :
champ d'application Bar:
définition baz égal à
soit foo égal à [4; 6; 5; 1] dans
somme entier de foo - maximum de foo
Si vous avez une valeur x de type (entier, booléen), vous pouvez utiliser
x.0 et x.1 pour accéder aux deux composants du n-uplet. Mais vous pouvez aussi
lier les deux composants à deux nouvelles variables y et z avec :
soit (y, z) = x dans
si z alors y sinon 0
Cette syntaxe reflète l’utilisation plus générale des motifs dans les liaisons let dans les langages de programmation fonctionnelle comme OCaml et Haskell. Cependant, pour le moment, seuls les n-uplets peuvent être déstructurés de la sorte.
Conditionnelles
Vous êtes encouragé à utiliser des exceptions aux définitions de variables de champ d’application pour encoder la logique cas de base/exception des textes juridiques. Seules les exceptions et les définitions conditionnelles de variables de champ d’application vous permettent de diviser votre code Catala en petits morceaux, chacun attaché au morceau de texte juridique qu’il encode.
Cependant, parfois, il est tout simplement logique d’utiliser une conditionnelle classique à l’intérieur d’une expression pour
distinguer entre deux cas. Dans ce cas, utilisez le traditionnel si ... alors ... sinon .... Notez
que vous devez inclure le sinon à chaque fois car cette conditionnelle est une expression produisant toujours
une valeur et non une instruction qui met à jour conditionnellement une cellule mémoire.
Structures
Comme expliqué précédemment, les valeurs de structure sont construites avec la syntaxe suivante :
Individu {
-- date_naissance: |1930-09-11|
-- revenu: 100 000 €
-- nombre_enfants: 2
}
Pour accéder au champ d’une structure, utilisez simplement la syntaxe individu.revenu.
Supposons que vous ayez une valeur foo contenant une grande structure Bar avec une douzaine de champs,
y compris baz. Vous voulez obtenir une nouvelle valeur de structure similaire à foo
mais avec une valeur différente pour bar. Vous pourriez écrire :
Bar {
-- baz: 42
-- fizz: foo.fizz
-- ...
}
Mais c’est très fastidieux car vous devez copier tous les champs. Au lieu de cela, vous pouvez écrire :
foo mais en remplaçant { -- baz: 42 }
Énumérations
Comme expliqué précédemment, le type de chaque cas de l’énumération est
obligatoire et introduit par contenu. Il est possible d’imbriquer des énumérations (déclarer le type d’un
champ d’une énumération comme une autre énumération ou structure), mais pas récursivement.
Les valeurs d’énumération sont construites avec la syntaxe suivante :
# Premier cas
PasDeCreditImpot,
# Deuxième cas
CreditImpotPourIndividu contenu Individu {
-- date_naissance: |1930-09-11|
-- revenu: 100 000 €
-- nombre_enfants: 2
},
# Troisième cas
CreditImpotApresDate contenu |2000-01-01|
Filtrage par motif
Le filtrage par motif est une fonctionnalité populaire des langages de programmation qui vient de la programmation fonctionnelle, introduite dans le grand public par Rust, mais suivie par d’autres langages comme Java ou Python. En Catala, le filtrage par motif fonctionne sur les valeurs d’énumération dont le type a été déclaré par l’utilisateur. Supposons que vous ayez déclaré le type
déclaration énumération PasDeCreditImpot:
-- PasDeCreditImpot
-- CreditImpotPourIndividu contenu Individu
-- CreditImpotApresDate contenu date
et vous avez une valeur foo de type PasDeCreditImpot. foo est soit une instance
de PasDeCreditImpot, soit CreditImpotPourIndividu, soit CreditImpotApresDate. Si
vous voulez utiliser foo, vous devez fournir des instructions pour ce qu’il faut faire dans chacun des
trois cas, puisque vous ne savez pas à l’avance lequel ce sera. C’est
le but du filtrage par motif ; dans chacun des cas, fournir une
expression produisant ce qui devrait être le résultat dans ce cas. Ces expressions de cas
peuvent également utiliser le contenu stocké à l’intérieur du cas des énumérations, rendant
le filtrage par motif un moyen puissant et intuitif d’“inspecter” le contenu imbriqué.
Par exemple, voici la syntaxe de filtrage par motif pour calculer le crédit d’impôt
dans notre exemple :
selon foo sous forme
-- PasDeCreditImpot: 0 €
-- CreditImpotPourIndividu contenu individu: individu.revenu * 10%
-- CreditImpotApresDate contenu date: si date_courante >= date alors 1000 € sinon 0 €
Dans CreditImpotPourIndividu contenu individu, alors que CreditImpotPourIndividu est
le nom du cas d’énumération inspecté, individu est un nom de variable
choisi par l’utilisateur représentant le contenu de ce cas d’énumération. En d’autres termes :
vous pouvez choisir votre propre nom pour la variable dans la syntaxe à cet endroit !
Il est important de noter que le filtrage par motif vous aide également à éviter d’oublier de gérer des cas. En effet, si vous déclarez un cas dans le type mais l’oubliez dans le filtrage par motif, vous obtiendrez une erreur du compilateur.
Souvent, le résultat du filtrage par motif devrait être le même dans beaucoup de cas,
vous conduisant à répéter la même expression de résultat pour chaque cas d’énumération.
Pour la concision et la précision, vous pouvez utiliser le cas attrape-tout n'importe quel comme
le dernier cas de votre filtrage par motif. Par exemple, ici cela calcule si
vous devez appliquer un crédit d’impôt ou non :
selon foo sous forme
-- PasDeCreditImpot: vrai
-- n'importe quel: faux
Vous pouvez créer un test booléen pour un cas spécifique d’une valeur énum avec le filtrage par motif :
selon foo sous forme
-- CreditImpotPourIndividu contenu individu: vrai
-- n'importe quel: faux
Cependant, écrire ce filtrage par motif complet pour un simple test booléen d’un cas spécifique est lourd. Catala offre un sucre syntaxique pour rendre les choses plus concises ; le code ci-dessous est exactement équivalent au code ci-dessus.
foo sous forme CreditImpotPourIndividu
Maintenant supposons que vous vouliez tester si foo est CreditImpotPourIndividu
et que le revenu de l’individu est supérieur à 10 000 €. Vous pourriez écrire :
selon foo sous forme
-- CreditImpotPourIndividu contenu individu: individu.revenu >= 10 000 €
-- n'importe quel: faux
Mais à la place vous pouvez aussi écrire le plus concis :
foo sous forme CreditImpotPourIndividu contenu individu et individu.revenu >= 10 000 €
Le filtrage par motif de Catala est-il aussi puissant que celui d’OCaml ou Haskell ?
Le filtrage par motif de Catala est-il aussi puissant que celui d’OCaml ou Haskell ?
Non, actuellement le filtrage par motif de Catala est basique et permet seulement de faire correspondre le type d’énumération le plus externe de la valeur. L’équipe Catala a des plans pour implémenter progressivement des fonctionnalités de filtrage par motif plus avancées, mais elles n’ont pas encore été implémentées.
N-uplets
Comme expliqué précédemment, vous pouvez construire des valeurs de n-uplet avec la syntaxe suivante :
(|2024-04-01|, 30 €, 1%) # Cette valeur a le type (date, argent, décimal)
Vous pouvez également accéder au n-ième élément d’un n-uplet, commençant à 1, avec la syntaxe <n-uplet>.n.
Listes
Vous pouvez construire des valeurs de liste en utilisant la syntaxe suivante :
[1; 6; -4; 846645; 0]
Toutes les opérations disponibles pour les listes sont disponibles sur la page de référence pertinente.
Appels de fonction
Pour appeler la fonction foo avec les arguments 1, baz et vrai, la syntaxe est :
foo de 1, baz, vrai
Les fonctions que vous pouvez appeler sont soit des fonctions de haut niveau définies par l’utilisateur,
soit des opérateurs intégrés comme accès_jour. Pour appeler
un champ d’application comme une fonction, voir juste en dessous.
Appels directs de champ d’application
L’équipe Catala préconise l’utilisation de déclarations de
sous-champ d’application et d’appel de
sous-champ d’application lorsque c’est possible (avec
un appel de sous-champ d’application unique et statique), car cela permet d’utiliser des définitions
conditionnelles et des exceptions sur les arguments du sous-champ d’application. Cependant, parfois
un champ d’application doit être appelé dynamiquement sous certaines conditions ou à l’intérieur d’une boucle,
ce qui rend impossible l’utilisation du mécanisme précédent. Dans ces situations, vous pouvez
utiliser des appels directs de champ d’application qui sont l’équivalent des appels directs de fonction, mais
pour les champs d’application, comme une expression. Par exemple, supposons que vous soyez à l’intérieur d’une expression
et que vous vouliez appeler le champ d’application Foo avec les arguments bar et baz ; la syntaxe est :
résultat de Foo avec {
-- bar: 0
-- baz: vrai
}
Notez que la valeur renvoyée par ce qui précède est la structure de résultat du
champ d’application de Foo, contenant
un champ par variable de résultat. Vous pouvez stocker cette valeur de résultat dans une variable
locale et ensuite
accéder à ses champs pour récupérer les valeurs
pour chaque variable de résultat.
Cas “impossible”
Lorsque certains cas ne sont pas censés se produire dans le flux d’exécution normal d’un
programme, ils peuvent être marqués comme impossible. Cela rend l’intention du
programmeur claire, et supprime le besoin d’écrire une valeur fictive. Si, pendant
l’exécution, impossible est atteint, le programme s’arrêtera avec une erreur fatale.
Il est conseillé de toujours accompagner impossible d’un commentaire justifiant pourquoi le
cas est jugé impossible.
impossible a le type n'importe quel, de sorte qu’il peut être utilisé à la place de n’importe quelle valeur.
Par exemple :
selon foo sous forme
-- CreditImpotPourIndividu contenu individu : individu.date_naissance
-- n'importe quel :
impossible # Nous savons que foo n'est sous aucune autre forme à ce stade car...
Attention, toute valeur qui n’est pas gardée par une condition ou un filtrage peut être calculée,
même si elle n’est pas directement nécessaire pour calculer le résultat (en d’autres termes, Catala n’est pas
un langage paresseux). Par conséquent, impossible n’est pas adapté pour initialiser des champs de
structures, par exemple, même si ces champs ne sont jamais utilisés.
Il est possible d’attacher un attribut
#[error.message] à un impossible pour
y ajouter un message qui sera affiché en cas de déclenchement.
Assertions
Comme nous avons vu, des assertions peuvent être définies au niveau des
définitions d’un champ
d’application, afin de valider des invariants sur ses variables. Il est
également possible, pour des contraintes plus locales, d’insérer des assertions
portant sur les variables locales directement à l’intérieur d’une expression. La
syntaxe à utiliser est assertion ... dans ...:
soit foo égal à ... dans
assertion foo > 0 dans
...
De même que pour les assertions au niveau du champ d’application, il est
possible d’attacher un attribut
#[error.message] à l’assertion
locale afin d’y ajouter un message qui sera affiché en cas d’échec.
Modules
Après les champs d’application et les déclarations de haut niveau, les modules sont le deuxième niveau d’abstraction en Catala. Précisément, les modules sont simplement un groupe de champs d’application et de déclarations de haut niveau qui forment une unité de compilation séparée pour le compilateur Catala. En tant que tels, ils présentent une interface publique ou API, dont les éléments peuvent être référencés ou appelés à l’exécution par un autre module.
Déclarations de module
Un fichier source Catala peut être transformé en module en insérant en haut une
déclaration de module. Par exemple, si le fichier est nommé foo.catala_fr,
la déclaration de module est simplement :
> Module Foo
Le nom du fichier et le nom du module dans la déclaration de module doivent correspondre, mais des différences de casse sont autorisées car les noms de modules doivent être en CamlCase et les noms de fichiers sont généralement en snake_case.
Si vous oubliez de mettre > Module Foo en haut de votre fichier, le fichier ne
sera pas considéré comme un module par le système de construction clerk. En particulier,
vous ne pourrez pas appeler les fonctions de ce fichier depuis d’autres modules.
Imports
Les modules peuvent “utiliser” d’autres modules pour importer leurs types, champs d’application et
constantes publics. Si vous voulez utiliser le module Bar à l’intérieur du module Foo, le haut de
foo.catala_fr devrait ressembler à :
> Module Foo
> Usage de Bar
Vous pouvez alors faire référence aux types, champs d’application et constantes de Bar, par exemple Fizz, en écrivant
Bar.Fizz dans le module Foo. Si vous ne voulez pas taper Bar. à chaque fois, vous pouvez
donner à Bar un alias à l’intérieur de Foo avec :
> Module Foo
> Usage de Bar en tant que B
Alors, B.Fizz fera référence à Bar.Fizz.
Certains modules ont vocation à gérer un type de données particulier, comme par
exemple le module Période. Ce
module définit une structure Période, qu’on devra ensuite utiliser sous le nom
qualifié Période.Période.
Pour alléger cette syntaxe, Catala détecte lorsqu’un type (énumération,
structure ou externe) a le même nom que le module qui le définit. Dans ce cas,
le type devient accessible directement sans avoir à expliciter le module: on
peut écrire simplement Période au lieu de Période.Période.
Cette possibilité est désactivée si le nom du module a été aliasé (en utilisant
en tant que).
Inclusions
Parfois, le texte juridique et le code Catala qui doivent tenir dans un seul
module sont trop volumineux pour un seul fichier. C’est généralement le cas lorsque la
spécification juridique pour un champ d’application donné s’étend sur plusieurs sources juridiques qui ont
des références mutuelles entre elles. Dans ces cas, vous voulez que chaque source juridique
ait son propre fichier .catala_fr, tandis que l’implémentation du champ d’application et le module englobant
s’étendent sur tous ces fichiers.
Pour accommoder cette pratique, Catala a un mécanisme d’inclusion textuelle qui
vous permet d’inclure le contenu d’un fichier dans un autre. Par exemple, imaginez
que le module dans le fichier prestation.catala_fr tire son implémentation des
fichiers prestation_loi.catala_fr, prestation_reglement.catala_fr, et prestation_instructions.catala_fr.
Alors, le contenu de prestation.catala_fr devrait ressembler à ceci pour inclure
tous les autres fichiers :
> Module Prestation
> Inclusion: prestation_loi.catala_fr
> Inclusion: prestation_reglement.catala_fr
> Inclusion: prestation_instructions.catala_fr
L’ordre des instructions > Inclusion dans prestation.catala_fr déterminera
l’ordre dans lequel le contenu du fichier est copié. Bien que cet ordre
n’ait aucune influence sur la sémantique du calcul, il changera la façon dont les choses
sont imprimées dans les backends de programmation littéraire.
Ce qui vient après Inclusion: est en fait un chemin de fichier de style Unix, qui peut
faire référence à des sous-répertoires ou au répertoire parent (../).
Interface publique et visibilité
L’équipe Catala pense que les programmeurs devraient contrôler précisément quelle interface ils rendent publiquement disponible pour leurs modules. En effet, ne pas exposer les fonctions internes est la clé pour préserver la capacité de refactoriser le code plus tard sans casser les points de terminaison utilisés par les clients du module.
C’est la raison pour laquelle en Catala, toutes les déclarations de type, de champ d’application et de constante
à l’intérieur des blocs ```catala sont privées : elles ne seront pas accessibles par d’autres
modules. Pour rendre une déclaration de type, de champ d’application ou de constante publique et donc
accessible par d’autres modules, vous devez transformer le bloc ```catala
contenant en un bloc ```catala-metadata. C’est tout !
Bibliothèque standard
Pour éviter d’avoir à réinventer la roue, Catala dispose d’une bibliothèque standard contenant diverses fonctions utiles permettant de manipuler les types de bases. Ci-dessous, vous trouverez la liste des prototypes de fonction de la bibliothèque standard, classifiée par modules. Pour utilister une fonction de la bibliothèque standard, vous pouvez taper:
<nom module>.<nom fonction> de <arguments>
Exemple:
Date.dernier_jour_du_mois de |2024-01-21| # retourne |2024-01-31|
Il n’est pas nécessaire d’écrire > Usage de <Module> pour les
modules de la bibliothèque standard. Ces modules sont automatiquement accessibles.
En outre, seuls les modules français, décrits ci-dessous, sont
accessibles. Les modules anglais ne sont pas inclus.
Voici un résumé de tous les modules disponibles de la bibliothèque standard:
Entier– Fonctions arithmétiques sur les valeurs de typeentierDécimal– Fonctions arithmétiques sur les valeurs de typedécimalArgent– Fonctions arithmétiques et financières sur le typeargentDate– Fonctions de représentation et de manipulation du typedateMoisAnnée– Structure et fonctions sur les mois d’année spécifiquesPériode– Période de temps et utilitaires pour les manipulerListe– Fonctions pour créer, accéder et manipuler les valeurs de typeliste
Les sources de la bibliothèque standard sont installées en même temps que
Catala. Au sein de chaque projet, elles sont copiées dans le dossier
_build/libcatala dès que nécessaire, en particulier lors de l’utilisation de
clerk build ou clerk run.
Avec plaisir! Catala est un projet open-source ; vous pouvez
contribuer à l’extension de la bibliothèque standard en créant une
“Pull Request” mettant à jour les fichiers dans le dossier
stdlib.
Module Entier
## Renvoie le plus petit des deux arguments.
déclaration min
contenu entier
dépend de
n1 contenu entier,
n2 contenu entier
## Renvoie le plus grand des deux arguments.
déclaration max
contenu entier
dépend de
n1 contenu entier,
n2 contenu entier
## Applique une limite supérieure à `n`, et renvoie une valeur qui n'est
## jamais plus grande que `valeur_max`.
déclaration plafond
contenu entier
dépend de
variable contenu entier,
valeur_max contenu entier
## Applique une limite inférieure à `n`, et renvoie une valeur qui n'est
## jamais plus petite que `valeur_min`.
déclaration plancher
contenu entier
dépend de
variable contenu entier,
valeur_min contenu entier
## Renvoie l'argument s'il est positif, 0 sinon.
déclaration positif
contenu entier
dépend de variable contenu entier
Module Décimal
## Renvoie le plus petit des deux arguments.
déclaration min
contenu décimal
dépend de
d1 contenu décimal,
d2 contenu décimal
## Renvoie le plus grand des deux arguments.
déclaration max
contenu décimal
dépend de
d1 contenu décimal,
d2 contenu décimal
## Applique une limite supérieure à `d`, et renvoie une valeur qui n'est
## jamais plus grande que `valeur_max`.
déclaration plafond
contenu décimal
dépend de
d contenu décimal,
valeur_max contenu décimal
## Applique une limite inférieure à `n`, et renvoie une valeur qui n'est
## jamais plus petite que `valeur_min`.
déclaration plancher
contenu décimal
dépend de
d contenu décimal,
valeur_min contenu décimal
## Renvoie l'argument s'il est positif, 0,0 sinon.
déclaration positif
contenu décimal
dépend de d contenu décimal
## Enlève les chiffres après la virgule.
## **Exemples:**
## - `troncature de 7,61 = 7,0`
## - `troncature de -7,61 = -7,0`
déclaration troncature
contenu décimal
dépend de d contenu décimal
## Arrondit à l'entier supérieur.
## **Exemples:**
## - `arrondi_par_excès de 4,34 = 5,0`
## - `arrondi_par_excès de -4,34 = -4,0`
déclaration arrondi_par_excès
contenu décimal
dépend de d contenu décimal
## Arrondit à l'entier inférieur.
## **Exemples:**
## - `arrondi_par_défaut de 3,78 = 3,0`
## - `arrondi_par_défaut de -3,78 = -4,0`
déclaration arrondi_par_défaut
contenu décimal
dépend de d contenu décimal
## Arrondit à la `nième_décimale` spécifiée. `nième_décimale` peut être une
## valeur négative.
## **Exemples:**
## - `arrondi_à_la_décimale de 123,4567, 3 = 123,457`
## - `arrondi_à_la_décimale de 123,4567, -2 = 100,0`
déclaration arrondi_à_la_décimale
contenu décimal
dépend de
d contenu décimal,
nième_décimale contenu entier
Module Argent
## Renvoie le plus petit des deux arguments.
déclaration min
contenu argent
dépend de
m1 contenu argent,
m2 contenu argent
## Renvoie le plus grand des deux arguments.
déclaration max
contenu argent
dépend de
m1 contenu argent,
m2 contenu argent
## Applique une limite supérieure à `a`, et renvoie une valeur qui n'est
## jamais plus grande que `valeur_max`.
déclaration plafond
contenu argent
dépend de
a contenu argent,
valeur_max contenu argent
## Applique une limite inférieure à `a`, et renvoie une valeur qui n'est
## jamais plus petite que `valeur_min`.
déclaration plancher
contenu argent
dépend de
a contenu argent,
valeur_min contenu argent
## Renvoie l'argument s'il est positif, 0€ sinon.
déclaration positif
contenu argent
dépend de a contenu argent
## Enlève d'un montant d'argent ses chiffres après la virgule.
## **Exemples:**
## - `troncature de 7,61€ = 7€`
## - `troncature de -7,61€ = -7€`
déclaration troncature
contenu argent
dépend de a contenu argent
## Arrondi un montant d'argent à l'euro supérieur.
## **Exemples:**
## - `arrondi_par_excès de 4,34€ = 5€`
## - `arrondi_par_excès de -4,34€ = -4€`
déclaration arrondi_par_excès
contenu argent
dépend de a contenu argent
## Arrondit un montant d'argent à l'euro inférieur.
## **Exemples:**
## - `arrondi_par_défaut de 3,78€ = 3€`
## - `arrondi_par_défaut de -3,78€ = -4€`
déclaration arrondi_par_défaut
contenu argent
dépend de a contenu argent
## Arrondit un montant d'argent à la `nième_décimale` spécifiée.
## `nième_décimale` peut être une valeur négative.
## **Exemples:**
## - `arrondi_à_la_décimale de 123,45€, 1 = 123,5€`
## - `arrondi_à_la_décimale de 123,45€, -2 = 100€`
déclaration arrondi_à_la_décimale
contenu argent
dépend de
a contenu argent,
nième_décimale contenu entier
Opérations financières
## Retourne le montant positif duquel `a` dépasse de `référence`
## (sinon 0€).
déclaration en_excès
contenu argent
dépend de
a contenu argent,
référence contenu argent
## Retourne le montant positif duquel `a` est en dessous de `référence`
## (sinon 0€).
déclaration en_défaut
contenu argent
dépend de
a contenu argent,
référence contenu argent
Module Date
Fonctions utilitaires
## Renvoie la plus ancienne des deux dates.
déclaration min
contenu date
dépend de
d1 contenu date,
d2 contenu date
## Renvoie la plus récente des deux dates.
déclaration max
contenu date
dépend de
d1 contenu date,
d2 contenu date
Dates et années, mois et jours
## Construit une date à partir du numéro de l'année, du mois (à partir de 1)
## et du jour (à partir de 1).
déclaration depuis_année_mois_jour
contenu date
dépend de
dannée contenu entier,
dmois contenu entier,
djour contenu entier
## Renvoie le numéro de l'année, du mois (1-12) et du jour (1-31) de la date
## passée en argument.
déclaration vers_année_mois_jour
contenu (entier, entier, entier)
dépend de d contenu date
## Renvoie le numéro de l'année d'une date.
déclaration accès_année
contenu entier
dépend de d contenu date
## Renvoie le numéro du mois (1-12) d'une date.
déclaration accès_mois
contenu entier
dépend de d contenu date
## Renvoie le numéro du jour (1-31) d'une date.
déclaration accès_jour
contenu entier
dépend de d contenu date
Aller vers le passé ou le futur
## Renvoie le premier jour du mois de la date passée en argument.
## **Exemple:** `premier_jour_du_mois de |2024-01-21| = |2024-01-01|`.
déclaration premier_jour_du_mois
contenu date
dépend de d contenu date
## Renvoie le dernier jour du mois de la date passée en argument.
## **Exemple:** `dernier_jour_du_mois de |2024-01-21| = |2024-01-31|`
déclaration dernier_jour_du_mois
contenu date
dépend de d contenu date
## Renvoie le premier jour de l'année de la date passée en argument.
## **Exemple:** `premier_jour_de_l_année de |2024-03-21| = |2024-01-01|`
déclaration premier_jour_de_l_année
contenu date
dépend de d contenu date
## Renvoie le dernier jour de l'année de la date passée en argument.
## **Exemple:** `dernier_jour_de_l_année de |2024-03-21| = |2024-12-31|`
déclaration dernier_jour_de_l_année
contenu date
dépend de d contenu date
Mois nommés
déclaration énumération Mois:
-- Janvier
-- Février
-- Mars
-- Avril
-- Mai
-- Juin
-- Juillet
-- Août
-- Septembre
-- Octobre
-- Novembre
-- Décembre
## Renvoie le numéro de mois (1-12) associé à un mois nommé.
déclaration mois_vers_entier
contenu entier
dépend de m contenu Mois
## Renvoie le mois nommé correspondant au numéro de mois (1-12).
## **Échoue:** si l'argument n'est pas entre 1 et 12
déclaration entier_vers_mois
contenu Mois
dépend de i contenu entier
Comparaisons de dates
## Teste si une personne née à `date_de_naissance` est au moins âgée de `âge` à
## la date `à_date`. Cette fonction calcule l'anniversaire en arrondissant à
## l'**inférieur**.
## **Exemples:**
## - `est_assez_âgé_arrondi_inférieur de |2000-06-01|, 24 an, |2024-06-15| = vrai`
## - `est_assez_âgé_arrondi_inférieur de |2000-06-01|, 24 an, |2024-05-15| = faux`
## - `est_assez_âgé_arrondi_inférieur de |2000-01-31|, 1 mois, |2000-02-29| = vrai`
déclaration est_assez_âgé_arrondi_inférieur
contenu booléen
dépend de
date_de_naissance contenu date,
âge contenu durée,
à_date contenu date
## Teste si une personne née à `date_de_naissance` est au moins âgée de `âge` à
## la date `à_date`. Cette fonction calcule l'anniversaire en arrondissant au
## **supérieur**.
## **Exemples:**
## - `est_assez_âgé_arrondi_supérieur de |2000-06-01|, 24 an, |2024-06-15| = vrai`
## - `est_assez_âgé_arrondi_supérieur de |2000-06-01|, 24 an, |2024-05-15| = faux`
## - `est_assez_âgé_arrondi_supérieur de |2000-01-31|, 1 mois, |2000-02-29| = faux`
déclaration est_assez_âgé_arrondi_supérieur
contenu booléen
dépend de
date_de_naissance contenu date,
âge contenu durée,
à_date contenu date
## Teste si une personne née à `date_de_naissance` a un âge strictement
## inférieur à `âge` à la date `à_date`. Cette fonction calcule l'anniversaire
## en arrondissant à l'**inférieur**.
## **Exemples:**
## - `est_assez_jeune_arrondi_inférieur de |2000-06-01|, 24 an, |2024-06-15| = faux`
## - `est_assez_jeune_arrondi_inférieur de |2000-06-01|, 24 an, |2024-05-15| = vrai`
## - `est_assez_jeune_arrondi_inférieur de |2000-01-31|, 1 mois, |2000-02-29| = faux`
déclaration est_assez_jeune_arrondi_inférieur
contenu booléen
dépend de
date_de_naissance contenu date,
âge contenu durée,
à_date contenu date
## Teste si une personne née à `date_de_naissance` a un âge strictement
## inférieur à `âge` à la date `à_date`. Cette fonction calcule l'anniversaire
## en arrondissant au **supérieur**.
## **Exemples:**
## - `est_assez_jeune_arrondi_supérieur de |2000-06-01|, 24 an, |2024-06-15| = faux`
## - `est_assez_jeune_arrondi_supérieur de |2000-06-01|, 24 an, |2024-05-15| = vrai`
## - `est_assez_jeune_arrondi_supérieur de |2000-01-31|, 1 mois, |2000-02-29| = vrai`
déclaration est_assez_jeune_arrondi_supérieur
contenu booléen
dépend de
date_de_naissance contenu date,
âge contenu durée,
à_date contenu date
Module MoisAnnée
déclaration structure MoisAnnée:
donnée nom_mois contenu D.Mois
donnée année contenu entier
## Extrait le mois d'année depuis une date en ignorant le jour.
déclaration depuis_date
contenu MoisAnnée
dépend de d contenu date
## Transforme un `MoisAnnée` vers une `date` en choisissant
## le premier jour du mois.
déclaration premier_jour_du_mois
contenu date
dépend de m contenu MoisAnnée
## Transforme un `MoisAnnée` vers une `date` en choisissant
## le dernier jour du mois.
déclaration dernier_jour_du_mois
contenu date
dépend de m contenu MoisAnnée
## Teste si la date survient strictement avant le mois concerné.
déclaration est_avant_le_mois
contenu booléen
dépend de m contenu MoisAnnée, d contenu date
## Teste si la date survient strictement après le mois concerné.
déclaration est_après_le_mois
contenu booléen
dépend de m contenu MoisAnnée, d contenu date
## Teste si la date survient durant le mois concerné.
déclaration est_dans_le_mois
contenu booléen
dépend de m contenu MoisAnnée, d contenu date
(est_après_le_mois_précédent de m, d)
et (est_avant_le_mois_suivant de m, d)
## Teste si la date survient avant le premier jour du mois suivant.
## **Exemples:**
## - `est_avant_le_mois_suivant de mai_2025, |2025-04-13| = vrai`
## - `est_avant_le_mois_suivant de mai_2025, |2025-05-31| = vrai`
## - `est_avant_le_mois_suivant de mai_2025, |2025-06-01| = faux`
déclaration est_avant_le_mois_suivant
contenu booléen
dépend de m contenu MoisAnnée, d contenu date
## Teste si la date survient après le dernier jour du mois suivant.
## **Exemples:**
## - `est_après_le_mois_précédent de mai_2025, |2025-06-15| = vrai`
## - `est_après_le_mois_précédent de mai_2025, |2025-05-01| = vrai`
## - `est_après_le_mois_précédent de mai_2025, |2025-04-30| = faux`
déclaration est_après_le_mois_précédent
contenu booléen
dépend de m contenu MoisAnnée, d contenu date
Module Période
Définitions et opérations
Une période est composée d’une date de début et d’une date de fin.
déclaration structure Période:
donnée début contenu date
# La date de fin est incluse dans la période par convention
donnée fin contenu date
## Retourne la période englobant le mois donné de l'année donnée.
déclaration depuis_mois_et_année
contenu Période
dépend de
pmois contenu Date.Mois,
pannée contenu entier
## Retourne la période englobant l'année donnée.
déclaration depuis_année
contenu Période
dépend de pannée contenu entier
## Vérifie si la période est bien cohérente (elle débute avant sa fin, et dure
## au moins un jour).
déclaration valide
contenu booléen
dépend de p contenu Période
## Durée d'une période, en nombre de jours.
déclaration durée
contenu durée
dépend de p contenu Période
## Deux périodes sont adjacentes si la seconde commence aussitôt après la fin
## de la première.
déclaration sont_adjacentes
contenu booléen
dépend de
p1 contenu Période,
p2 contenu Période
## Retourne la période qui englobe `p1` et `p2`.
déclaration union
contenu Période
dépend de
p1 contenu Période,
p2 contenu Période
## Retourne la période contenue à la fois dans p1 et p2, si elle existe.
déclaration intersection
contenu optionnel de Période
dépend de
p1 contenu Période,
p2 contenu Période
## Teste si les périodes se chevauchent d'au moins un jour.
déclaration chevauche
contenu booléen
dépend de
p1 contenu Période,
p2 contenu Période
## Teste si la période `longue` englobe la période `courte`.
déclaration englobe
contenu booléen
dépend de
longue contenu Période,
courte contenu Période
## Teste si la date `d` est contenue dans la période `p`.
déclaration est_contenue
contenu booléen
dépend de
p contenu Période,
d contenu date
## Teste si la date survient *strictement* avant la période.
déclaration est_avant
contenu booléen
dépend de
p contenu Période,
d contenu date
## Teste si la date survient *strictement* après la période.
déclaration est_après
contenu booléen
dépend de
p contenu Période,
d contenu date
## Trouve la première période dans la liste `l` qui contient la date `d`.
déclaration trouve_période
contenu optionnel de Période
dépend de
l contenu liste de Période,
d contenu date
Opérations sur des listes associées indexées par des périodes
## Trie la liste de périodes en fonction de la date de début.
## Si deux périodes commencent le même jour, leur ordre dans la liste est
## préservé
déclaration tri_par_date
contenu liste de (Période, n'importe quel de type t)
dépend de l contenu liste de (Période, n'importe quel de type t)
Diviser des périodes
## Divise la période en autant de sous-périodes qu'elle contient de mois
## calendaires. Les premiers et derniers éléments retournés peuvent donc être
## des mois incomplets.
## Si la période donnée est invalide, retourne une liste vide.
déclaration divise_par_mois
contenu liste de Période
dépend de p contenu Période
## Divise la période en autant de sous-périodes qu'elle contient d'années du
## calendrier. Les premiers et derniers éléments retournés peuvent donc être
## des années incomplètes.
## Si la période donnée est invalide, retourne une liste vide.
déclaration divise_par_année
contenu liste de Période
dépend de
mois_de_départ contenu Date.Mois,
p contenu Période
Module Liste
## Donne la liste constituée des entiers consécutifs de `début` à `fin`.
## Si `fin <= début`, le résultat est une liste vide.
## **Exemple**: `séquence de 3, 6` donne la liste `[ 3; 4; 5; 6 ]`
déclaration séquence
contenu liste de entier
dépend de
début contenu entier,
fin contenu entier
## Donne l'élément à l'`index` donné dans la liste, encapsulé dans
## le constructeur `Présent`.
## Si l'index est plus petit que 1, ou en-dehors de la liste, la valeur
## `Absent` est retournée
## **Exemple**: `nième_élément de [101€; 102€; 103€], 2 = Présent contenu 102€`
déclaration nième_élément
contenu optionnel de n'importe quel de type t
dépend de
l contenu liste de n'importe quel de type t,
index contenu entier
## Donne le premier élément de la liste encapsulé dans le constructeur
## `Présent`.
## Si la liste est vide, retourne `Absent`.
déclaration premier_élément
contenu optionnel de n'importe quel de type t
dépend de l contenu liste de n'importe quel de type t
## Donne le dernier élément de la liste encapsulé dans le constructeur
## `Présent`.
## Si la liste est vide, retourne `Absent`.
déclaration dernier_élément
contenu optionnel de n'importe quel de type t
dépend de l contenu liste de n'importe quel de type t
## Supprime l'élément à l'`index` donné de la liste. Les index des éléments
## suivants sont décalés.
## Si l'index donné est invalide, retourne la liste sans modification.
déclaration retire_nième_élément
contenu liste de n'importe quel de type t
dépend de
l contenu liste de n'importe quel de type t,
index contenu entier
## Retourne la liste sans son premier élément.
## La liste vide est retournée inchangée.
déclaration retire_premier_élément
contenu liste de n'importe quel de type t
dépend de l contenu liste de n'importe quel de type t
## Retourne la liste sans son dernier élément.
## La liste vide est retournée inchangée.
déclaration retire_dernier_élément
contenu liste de n'importe quel de type t
dépend de l contenu liste de n'importe quel de type t
## Inverse l'ordre des éléments la liste donnée.
déclaration inverse
contenu liste de n'importe quel de type t
dépend de l contenu liste de n'importe quel de type t
Fonctionnalités supplémentaires
Ces sous-sections décrivent les fonctionnalités supplémentaires de Catala qui sont utiles pour une utilisation avancée de Catala mais peuvent nécessiter une connaissance plus approfondie des mécanismes internes de Catala.
Attributs
Le langage peut être étendu avec des attributs, permettant une variété d’extensions.
Un attribut est de la forme #[clé.de.extension] ou #[clé.de.extension = VALEUR],
où VALEUR peut être de la forme "CHAÎNE", ou une expression en syntaxe Catala.
Un attribut est toujours lié à l’élément qui le suit directement : une extension
pourrait par exemple les utiliser pour récupérer des étiquettes pour les champs d’entrée d’un
champ d’application afin de générer un formulaire web :
déclaration champ d'application UnCalcul:
#[form.label = "Entrez le nombre d'enfants satisfaisant la condition XXX"]
entrée enfants_age contenu entier
Attributs prédéfinis
Le compilateur Catala reconnaît un certain nombre d’attributs prédéfinis.
#[test]
S’applique aux déclarations de champs d’application, pour indiquer leur rôle de test. Voir la section sur les tests.
#[doc] or ##
Associé à une déclaration, un champ de structure, un cas d’énumération ou un
argument de fonction, l’attribut #[doc = "texte d'explication"] permet de le
documenter. Cette information sera rendue disponible aux utilisateurs du module,
et devrait indiquer le rôle et les modalités d’utilisation de l’élément associé.
Un commentaire commençant par ## est automatiquement interprété comme un
attribut de documentation de l’élément qui suit: cette syntaxe allégée ## texte d'explication est à privilégier.
#[error.message]
L’attribut #[error.message = "message informatif"] s’attache à une assertion
ou au mot-clef impossible. Si l’erreur est déclenchée, le message sera affiché
à l’utilisateur en plus des informations habituelles sur le type de l’erreur et
sa position d’origine dans le code. Le message est affiché lors de
l’interprétation, mais également dans les programmes générés en utilisant les
différents backends.
#[debug.print]
Cet attribut s’applique à une expression et n’a d’effet que lors de
l’interprétation, si l’option --debug de catala est active (-c--debug si
exécuté depuis clerk). Une fois calculée, la valeur de l’expression attachée
sera affichée sur la console.
Un label peut être ajouté pour différencier plusieurs affichages de débug, de la
sorte: #[debug.print = "variable 001"].
Attention: à cause de la façon dont fonctionne la compilation, il n’y a pas de
garantie sur l’affichage des messages de débug: en particulier, il peut arriver
qu’ils soient dupliqués ou disparaissent, suivant l’élément où ils apparaissent
et la présence du flag d’optimisation (-O). En cas de souci de ce type, il est
conseiller de déplacer l’attribut à la racine de la définition d’une variable
plutôt que sur un élément intermédiaire.
#[implicit_position_argument]
Cet attribut s’applique à un paramètre de fonction de type position_source
uniquement. Il peut être utile en particulier lors de l’utilisation de modules
externes). Lorsqu’il est utilisé, le paramètre
concerné disparaît des appels à cette fonction, et sera renseigné implicitement
avec la position de l’appelant.
Le but est, dans le cas où une fonction définie dans un module a des
préconditions, de pouvoir signaler l’erreur comme venant du lieu d’appel plutôt
que de la fonction elle-même. Par exemple, lors d’un appel à
Utils.division_custom de 2, 0, donner la position où division_custom est
définie dans le module Utils ne serait pas très utile.
Modules externes
Les modules externes sont un moyen d’intégrer une logique externe dans un projet Catala. Par exemple, si un programme Catala nécessite une logique qui est trop lourde (ou tout simplement impossible) à exprimer en Catala, on peut se rabattre sur des modules externes pour les implémenter dans le langage cible du backend souhaité – par exemple, C, Java, OCaml. Les utilisateurs sont tenus de remplir l’implémentation du module externe pour chaque cible backend souhaitée. Par exemple, si un projet ne cible que la compilation vers Java, seule une implémentation de module externe Java doit être présente.
Cependant, pour interpréter un programme Catala (clerk run ou clerk test
sans spécifier l’option --backend), l’implémentation du module externe OCaml
est requise.
Déclarer des modules externes
Certains modules contiennent une logique qui ne peut pas être implémentée en
Catala. C’est normal, puisque Catala est un langage dédié (DSL) et non un
langage de programmation généraliste. Cela signifie que les fonctionnalités de
Catala sont intentionnellement limitées ; par exemple, vous ne pouvez pas écrire
de fonctions récursives ou manipuler des chaînes de caractères
en Catala. Mais parfois, vous avez besoin d’appeler depuis un module Catala
régulier une fonction contenant ce morceau de logique non implémentable. C’est le
but du module externe. Un module externe en Catala est un fichier de code source
Catala contenant un module avec l’annotation externe, comme ceci :
> Module Foo externe
```catala-metadata
déclaration structure Période:
donnée date_début contenu date
donnée date_fin contenu date
déclaration mois_dans_période contenu liste de date
dépend de période contenu Période
```
Ce module externe déclare un type (Période), ainsi qu’une fonction de haut
niveau (mois_dans_période), mais cette dernière n’a pas d’implémentation !
C’est voulu car les modules externes ne doivent pas contenir de code Catala,
mais simplement des déclarations de types, des déclarations de champs
d’application et des déclarations de constantes et de fonctions de haut niveau.
Ces déclarations (à l’intérieur des blocs ```catala-metadata) exposent une
interface publique pour le module externe, qui peut être utilisée par d’autres
modules.
Cependant, pour faire fonctionner tout cela en pratique, vous devrez toujours implémenter le module externe en OCaml (pour l’interpréteur) et dans votre langage cible. Voir la section de référence pertinente.
Implémenter des modules externes
Les modules externes doivent correspondre précisément à l’interface attendue par le code Catala.
Les modules externes sont très dépendants des détails des backends implémentés par Catala. Vous pouvez vous attendre à ce que des mises à jour du code externe soient nécessaires avec les prochaines versions du compilateur.
Pour aider à cela, la commande catala supporte un drapeau --gen-external qui
générera une implémentation modèle dans le backend donné :
$ catala ocaml --gen-external foo.catala_fr
┌─[RESULT]─
│ Generated template external implementations:
│ "foo.template.ml"
│ "foo.template.mli"
└─
Renommez les fichiers (en supprimant la partie .template), et éditez-les pour
remplacer les espaces réservés d’erreur Impossible par votre implémentation.
- Les définitions de types, l’interface et les parties d’enregistrement du module doivent être laissées inchangées
- Procédez de la même manière pour chaque backend vers lequel votre projet sera compilé. Une implémentation OCaml est requise pour exécuter l’interpréteur Catala sur le projet, et fortement recommandée.
Le runtime Catala s’attend à ce que les appels de fonction soient “purs” : un appel à une fonction donnée ne doit dépendre d’aucun contexte. En d’autres termes, appeler la même fonction avec les mêmes arguments devrait toujours donner le même résultat, et il est déconseillé de stocker un état persistant dans un module externe.
Lors de la compilation, le système de construction Clerk récupérera automatiquement l’implémentation fournie.
En cas de changements dans l’interface Catala (ou lors de la mise à jour du
compilateur Catala), vous devez réexécuter la commande --gen-external et
résoudre les divergences entre votre implémentation et le nouveau fichier
.template.
Types externes
Il est parfois utile, lorsqu’on définit des modules externes, de manipuler des valeurs qui ne peuvent être définies en Catala pur. Les types externes le permettent:
déclaration type TypeAbstrait: externe
Le type TypeAbstrait défini ici peut être manipulé en Catala (utilisé comme
membre de structures, entrées de champ d’application, etc.) mais reste opaque
pour le reste du programme: seules les fonctions définies à l’intérieur du
module externe seront en mesure d’en observer le contenu.
L’usage des types externes nécessite cependant des précautions particulières:
- Suivant le backend utilisé, l’implémentation concrète du type doit se plier
aux règles de Catala pour ce backend. Par exemple, un type externe implémenté
en Java doit implémenter l’interface
CatalaValue. - Comme mentionné ci-dessus, Catala est basé sur un modèle fonctionnel:
n’utilisez pas de types mutables. Les modifications en-place de valeurs
sont interdites. Supposons par exemple un type externe
Ensemblequi permette de stocker des collections de valeurs: la fonction ajoutant un élément à l’ensemble devra renvoyer un nouvel ensemble comportant le nouvel élément, et en aucun cas modifier l’ensemble passé en argument.
Support du JSON
Catala est capable d’accepter du JSON en entrée de programmes Catala. Cela permet de simplifier le prototypage ou des tests simples.
L’écriture de tests n’utilisant que des données JSON en entrée peut sembler pratique mais nous décourageons cependant cette pratique. L’écriture de tests en Catala apporte de meilleures garanties en terme de maintenance (typage, test backends, etc.).
Pour illustrer comment intéragir avec un programme Catala avec des
entrées en JSON, considérons le champ d’application
CalculImpôtRevenu du tutoriel:
déclaration structure Individu:
donnée revenu contenu argent
donnée nombre_enfants contenu entier
déclaration champ d'application CalculImpôtRevenu:
entrée date_courante contenu date
entrée individu contenu Individu
entrée territoires_outre_mer contenu booléen
interne taux_imposition contenu décimal
résultat impôt_revenu contenu argent
L’exécution de ce champ d’application nécessite de lui fournir des valeurs concrètes en entrée, c’est-à-dire, une date courante, un individu et un booléen. Ceci est possible en passant ces valeurs au travers d’un objet JSON. Écrivons un fichier JSON (e.g., “entree-champ.json”) contenant ces données:
{ "date_courante": "2024-03-01",
"individu": { "revenu": 20000.00, "nombre_enfants": 0 },
"territoires_outre_mer": false }
Nous pouvons désormais directement exécuter ce champ d’application
grâce à l’option --input:
$ clerk run tutoriel.catala_en --scope=CalculImpôtRevenu --input entree-champ.json
┌─[RESULT]─ CalculImpôtRevenu ─
│ impôt_revenu = 4 200,00 €
└─
L’option --input accepte en entrée un fichier ou directement une
chaîne de caractères JSON. Il est également possible de lire depuis
l’entrée standard en donnant - en argument (i.e., --input=-).
Format JSON des entrées de champs d’application
L’exemple présenté ci-dessus est relativement simple à encoder en
JSON, cependant, pour des champs d’application plus complexe il est
très facile de commettre des erreurs. Dans notre exemple, si un champ
requis manque dans l’objet JSON fourni, disons le booléen, clerk
échouera et affichera une erreur contenant le format du JSON attendu:
$ clerk run tutoriel.catala_en --scope=CalculImpôtRevenu --input mauvaise-entree.json
┌─[ERROR]─
│
│ Failed to validate JSON:
│ Missing object field territoires_outre_mer
│
│ Expected JSON object of the form:
│ { "date_courante": $date,
│ "individu": $Individu,
│ "territoires_outre_mer": boolean }
│ $Individu: { "revenu": integer ∈ [-2^53, 2^53] || number || string,
│ "nombre_enfants": integer ∈ [-2^53, 2^53] || string }
│ $date:
│ /* Catala date */
│ string
│ /* Accepts strings with the following format: YYYY-MM-DD, e.g., "1970-01-31" */
│ || { /* Accepts date objects: {"year":<int>, "month":<int>, "day":<int>} */
│ "year": integer ∈ [0, 9999],
│ "month": integer ∈ [1, 12],
│ "day": integer ∈ [1, 31] }
│
└─
Ce format est plus compréhensible par les humains cependant l’approche
standardisé est d’utiliser les JSON
Schema. Pour obtenir le JSON schema d’un champ d’application, vous pouvez utiliser la commande clerk json-schema:
$ clerk json-schema tutoriel.catala_en --scope CalculImpôtRevenu
[
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Scope CalculImpôtRevenu input",
"description": "Input structure of scope CalculImpôtRevenu",
"$ref": "#/definitions/CalculImpôtRevenu_in",
"definitions": {
"CalculImpôtRevenu_in": {
"type": "object",
"properties": {
"date_courante": { "$ref": "#/definitions/date" },
"individu": { "$ref": "#/definitions/Individu" },
"territoires_outre_mer": { "type": "boolean" }
},
"required": [ "territoires_outre_mer", "individu", "date_courante" ],
"additionalProperties": false
},
...
},
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Scope CalculImpôtRevenu output",
"description": "Output structure of scope CalculImpôtRevenu",
"$ref": "#/definitions/CalculImpôtRevenu",
"definitions": {
"CalculImpôtRevenu": {
"type": "object",
"properties": {
"impôt_revenu": {
"oneOf": [ { "type": "integer", "minimum": -9007199254740992.0, "maximum": 9007199254740992.0 }, { "type": "number" }, { "type": "string" } ]
}
},
"required": [ "impôt_revenu" ],
"additionalProperties": false
}
}
}
]
La sortie de la commande précédente étant très verbeuse, nous l’avons raccourci pour clarifier. L’important à retenir est que cette commande retourne un tableau JSON de deux éléments. Le premier élément est le JSON schema décrivant le format de l’objet attendu pour l’entrée du champ d’application tandis que le deuxième décrit le format de l’objet attendu pour sa sortie. À l’aide d’outils externes, il est, par exemple, possible de faciliter la création et la prévalidation de données JSON.
Sortie JSON de champs d’application
Il est également possible d’obtenir le résultat d’un champ
d’application sous la forme d’un objet JSON. Pour ce faire, il faut
ajouter l’option --output-format json (ou, plus succintement, -F json) à
votre ligne de commande clerk run:
$ clerk run tutoriel.catala_en --output-format json --scope=CalculImpôtRevenu --input entree-champ.json
{ "impôt_revenu": 4200.0 }
Cette commande affichera le résultat en JSON selon le JSON schema
retourné par la commande clerk json-schema décrit dans la section
précédente.
Souvent, les commandes clerk run ou clerk json-schema vont
également afficher des messages liés à la compilation sur la sortie
standard ce qui peut empêcher de traiter la sortie en tant qu’objet
JSON pur. Pour faire disparaître ces informations, vous pouvez
rajouter l’option --quiet à votre ligne de commande ce qui
permettra, par exemple, de rediriger la sortie de votre exécution vers
des outils acceptant du JSON en entrée ou vers un fichier .json.
Correspondance des valeurs Catala avec le JSON
La table ci-dessous donne une correspondance entre les valeurs pouvant être écrites dans des programmes Catala et leur équivalence en JSON. Notez que certains types de valeurs peuvent être exprimés de plusieurs façon. Par exemple, les entiers peuvent être représentés en JSON par des entiers JSON (jusqu’à 2^53) ou, pour une précision arbitraire, par une chaîne de caractère.
| Type Catala | Type JSON | Example de valeur Catala | Valeur convertie en JSON |
|---|---|---|---|
| booléen | boolean | vrai | true |
| entier | integer | 1 | 1 |
| entier | string | 123 | 123 |
| décimal | integer | 1,0 | 1 |
| décimal | number | 1,5 | 1.5 |
| décimal | string | 1/3 | "1/3" |
| argent | integer | 1€ | 1 |
| argent | number | 1,23€ | 1.23 |
| argent | string | 1,23€ | "1.23" |
| date | string | |2026-01-31| | "2026-01-31" |
| durée | string | 1 an + 2 mois | {"years":1,"months":2} |
| liste | array | [ 1 ; 2 ; 3 ] | [ 1, 2, 3 ] |
| tuple | array | (1, 3,0, faux) | [ 1, 3.0, false ] |
| énumeration | string | A | "A" |
| énumeration | object | B contenu 3 | { "B": 3 } |
| structure | object | {--x:1 --y:vrai} | { "x": 1, "y": true } |
Pour le moment, les entrées et sorties en JSON ne sont supportés que par l’interprète Catala. Cependant, nous prévoyons d’ajouter son support dans les backends existants, ou au moins une partie.
Le système de construction Clerk
clerk est le système de construction de Catala.
Dans la plupart des projets réels, vous interagirez avec clerk
et non directement avec le compilateur / interpréteur Catala.
clerk définit une structure de projet,
et fournit des outils pour construire, exécuter et tester des programmes Catala.
Fichier de configuration du projet
Cette section décrit entièrement le format de manifeste pour les fichiers de
configuration de projet. Le fichier clerk.toml contient des métadonnées qui
décrivent comment construire et empaqueter des programmes Catala dans un projet. Il est
écrit au format TOML.
Un exemple de configuration clerk.toml est disponible dans la section
3.1.
Format du manifeste
[project]– Table qui définit les options globales du projet.include_dirs– Les répertoires d’emplacement des sources.build_dir– Le répertoire de sortie des artefacts de construction.target_dir– Le répertoire de sortie des cibles.default_targets– Les cibles par défaut à construire.catala_opts– Surcharge des options Catala.catala_exe– Surcharge du chemin du binaire Catala.
[[target]]– Multi-table qui définit une cible de projet.name– Nom de la cible (Requis).modules– Modules liés à la cible (Requis).tests– Liste des répertoires contenant des tests liés à la cible.backends– Liste des backends vers lesquels cette cible sera construite.include_sources– Drapeau pour inclure les fichiers sources dans la cible compilée.include_objects– Drapeau pour inclure les fichiers objets dans la cible compilée.
[variables]– Table pour surcharger les variables liées à la compilation.
Options [project]
include_dirs
Définit dans quels répertoires clerk cherche les fichiers sources Catala.
Exemple : include_dirs = ["src", "src/utils"]
build_dir
Spécifie quel répertoire doit être utilisé pour sortir les fichiers d’artefacts de construction générés.
Exemple : build_dir = "catala_build/"
Par défaut "_build/".
target_dir
Spécifie quel répertoire doit être utilisé pour sortir les bibliothèques générées des cibles résultantes. Le répertoire contiendra les fichiers backend exportables.
Exemple : target_dir = "generated_targets/"
Par défaut "_target/".
default_targets
Définit quelles cibles seront construites si aucune n’est spécifiée lors de l’invocation de
clerk build sans arguments.
Exemple : default_targets = ["calcul_impot", "prestations_sociales"]
catala_opts
Définit quelles options seront passées au compilateur Catala lors de la construction des programmes Catala. Attention : utiliser avec précaution.
Exemple : catala_opts = ["--trace", "--whole-program"]
catala_exe
Surcharge quel compilateur Catala sera utilisé pour construire les fichiers sources.
Exemple : catala_exe = "chemin/vers/catala_personnalise.exe"
Options [[target]]
name
Nom donné à la cible. Cela créera un alias qui peut être utilisé par clerk pour construire la cible spécifique ou lancer des tests dédiés.
Exemple : name = "calcul_impot"
Invoquer $ clerk build calcul_impot ne construira que la
cible calcul_impot.
modules
Modules qui seront utilisés pour compiler la [[target]] vers les backends
spécifiés.
Exemple : modules = ["Section_121", "Section_132"]
tests
Spécifie la liste des répertoires qui contiennent des tests Catala qui sont
liés à la cible. Exécuter clerk test <nom_cible> exécutera les
tests trouvés dans les répertoires donnés (et sous-répertoires
récursivement).
Exemple : tests = ["tests/tests_impot/unitaires/", "tests/tests_impot/"]
backends
Spécifie la liste des backends qui seront générés pour cette
cible. La liste des backends actuellement supportés est : "ocaml",
"java", "c", "python"
Exemple : backends = ["ocaml", "c", "java"]
Par défaut ["ocaml"] si omis.
include_sources
Spécifie s’il faut copier les fichiers sources générés par le backend (par exemple,
le .c ou .java) dans le répertoire target.
Exemple : include_sources = true
Par défaut false.
include_objects
Spécifie s’il faut copier les fichiers compilés générés par le backend (par exemple,
le .o ou .class) dans le répertoire target.
Exemple : include_objects = true
Par défaut false.
[variables]
Table globale utilisée pour surcharger les variables de construction clerk. La liste complète des
variables peut être consultée en utilisant clerk list-vars.
Exemple :
[variables]
CATALA_FLAGS_C = "-O"
CC = "clang"
JAVAC = "/usr/bin/javac"
Commandes et processus
clerk build
Construit les fichiers ou les cibles clerk listés dans votre fichier clerk.toml.
Cette commande est assez polyvalente, et peut être utilisée pour construire des fichiers spécifiques
individuels dans n’importe lequel des backends autorisés par Catala (voir l’aide ci-dessous), mais
nous conseillons de n’utiliser clerk build qu’avec des cibles correctement définies
dans clerk.toml.
Les résultats de clerk build sont disponibles dans le répertoire _targets par
défaut, si vous n’avez pas spécifié un autre répertoire cible dans clerk.toml.
Chaque cible génère un ensemble de fichiers de code source dans les langages de programmation
cibles, générés par le compilateur Catala à partir des fichiers de code source
Catala. Veuillez vous référer au guide de compilation et déploiement
pour plus d’exemples sur la façon d’utiliser clerk build et les artefacts
résultants.
clerk build ‐‐help
clerk build ‐‐help
NAME
clerk-build - Build command for either individual files or clerk
targets.
SYNOPSIS
clerk build [OPTION]… [TARGET(S)]…
DESCRIPTION
For individual files, and given the corresponding Catala module is
declared, this can be used to build .ml, .cmxs, .c, .py files, etc.
These files, along with their dependencies, are written into build-dir
(by default _build). If a file with a catala extension is used as
target, this compiles all its dependencies. The format of the targets
is src-dir/BACKEND/file.ext. For example, to build a C object file
from foo/bar.catala_en, one would run:
clerk build foo/c/bar.o
and the resulting file would be in _build/foo/c/bar.o. When given
clerk targets, that are defined in a clerk.toml configuration file, it
will build all their required dependencies for all their specified
backends along with their source files and copy them over to the
target-dir (by default _target).
For instance, clerk build my-target will generate a directory
target-dir/my-target/c/ that contains all necessary files to export
the target as a self contained library. When no arguments are given,
clerk build will build all the defined clerk targets found in the
clerk.toml or the project's default targets if any.
ARGUMENTS
TARGET(S)
Clerk target(s) or individual file(s) to process
OPTIONS
--autotest
When compiling to the backends, enable the Catala --autotest
option that runs an evaluation of test scopes (understood as
scopes that need no input) and instruments their compiled version
with assertions that the results match. This shouldn't be
specified directly using --catala-opts=--autotest because that
wouldn't guarantee that the necessary artifacts for interpretation
are present.
--build-dir=DIR (absent CLERK_BUILD_DIR env)
Directory where intermediate compilation artifacts should be
written. Defaults to '_build'.
-c FLAG, --catala-opts=FLAG
Option to pass to the Catala compiler. Can be repeated. If neither
this nor --test-flags is specified, the flags for the different
backends default to -O.
--color[=VAL] (default=always) (absent=auto or CATALA_COLOR env)
Allow output of colored and styled text. Use auto, to enable when
the standard output is to a terminal, never to disable.
--config=FILE
Clerk configuration file to use, instead of looking up
"clerk.toml" in parent directories.
-d, --debug
Prints debug information
-e EXE, --exe=EXE
Catala compiler executable.
-I DIR, --include=DIR (absent CATALA_INCLUDE env)
Make modules from the given directory available from everywhere.
Several dirs can be specified by repeating the flag or separating
them with ':'.
--makeflags=FLAG (absent MAKEFLAGS env)
Provides the contents of a MAKEFLAGS variable to pass on to Ninja.
Currently recognizes the -i and -j options.
-o FILE, --output=FILE
FILE is the file that will contain the build.ninja file output. If
not specified, the build.ninja file is set to
/clerk.ninja in debug mode, and a temporary file
otherwise
--target-dir=DIR (absent CLERK_TARGET_DIR env)
Directory where final compilation targets should be written.
Defaults to '_target'.
-W, --whole-program
Use Catala --whole-program option when testing or executing Catala
scopes.
COMMON OPTIONS
--help[=FMT] (default=auto)
Show this help in format FMT. The value FMT must be one of auto,
pager, groff or plain. With auto, the format is pager or plain
whenever the TERM env var is dumb or undefined.
--version
Show version information.
EXIT STATUS
clerk build exits with:
0 on success.
123 on indiscriminate errors reported on standard error.
124 on command line parsing errors.
125 on unexpected internal errors (bugs).
ENVIRONMENT
These environment variables affect the execution of clerk build:
CATALA_COLOR
See option --color.
CATALA_INCLUDE
See option --include.
CLERK_BUILD_DIR
See option --build-dir.
CLERK_TARGET_DIR
See option --target-dir.
MAKEFLAGS
make-compatible flags handling. Currently recognizes the -i and -j
options and forwards them through to Ninja.
SEE ALSO
clerk(1)
clerk test
Découvre, construit et exécute les tests.
Exécuter les tests
clerk test peut être utilisé sans aucun argument à la racine d’un
projet Catala, avec la sortie suivante :
$ clerk test
┏━━━━━━━━━━━━━━━━━━━━━━━━ ALL TESTS PASSED ━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ FAILED PASSED TOTAL ┃
┃ files 0 37 37 ┃
┃ tests 0 261 261 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
La ligne tests de ce rapport compte le nombre de tests échoués et réussis.
La ligne files affiche le nombre de fichiers où soit tous les tests
passent, soit il y a au moins un test qui échoue.
Vous pouvez imprimer les détails sur les fichiers contenant des tests réussis et échoués
avec le drapeau --verbose. De plus, il est possible d’exécuter la
commande avec le drapeau --xml pour obtenir un rapport compatible JUnit.
Vous pouvez restreindre la portée des tests exécutés par clerk test en fournissant un autre argument :
clerk test <fichier>exécutera uniquement les tests dans<fichier>;clerk test <dossier>exécutera uniquement les tests à l’intérieur des fichiers dans<dossier>(ou ses sous-répertoires) ;clerk test <cible>exécutera uniquement les tests liés à la construction<cible>.
clerk test exécute les tests avec l’interpréteur Catala. Si votre déploiement
utilise un backend spécifique, disons python, il est fortement recommandé d’inclure également
une exécution de clerk test --backend=python dans votre CI. Avec cette option,
clerk test exécute Python sur le code Python généré par le compilateur Catala.
De cette façon, vous serez protégé de l’éventualité qu’un bug dans le backend
que vous utilisez conduise à un résultat différent pour le même programme Catala. La confiance
n’exclut pas de vérifier minutieusement !
Déclarer les tests
Catala supporte deux types distincts de tests, adaptés à des objectifs différents :
- Les tests de champ d’application devraient être le moyen principal d’écrire des tests qui valident
des résultats attendus sur un calcul donné. C’est la façon naturelle d’automatiser les
commandes
clerk run --scope=TestXxxque vous utilisez pour exécuter vos tests manuellement. - Les tests Cram fournissent un moyen d’exécuter des commandes catala personnalisées et de vérifier leur
sortie sur
stdoutetstderr. Ils sont parfois utiles pour des besoins plus spécifiques, comme s’assurer que la bonne erreur est déclenchée dans une situation donnée.
La commande clerk test peut être exécutée sur n’importe quel fichier ou répertoire contenant des fichiers
catala, traitera les deux types de tests et imprimera un rapport.
Tests de champ d’application
Un test de champ d’application est un champ d’application qui est marqué avec l’attribut “test” : écrivez simplement
#[test] juste avant son mot-clé déclaration.
#[test]
déclaration champ d'application TestArrondiArgent:
résultat resultat contenu argent
Il y a deux exigences pour un test de champ d’application :
- Le champ d’application doit être public (déclaré dans une section
```catala-metadata) afin qu’il puisse être exécuté et vérifié dans des conditions réelles - Il ne doit nécessiter aucune entrée : seules les variables
interne,résultatetcontextesont autorisées
La sortie attendue du test doit être validée avec des instructions assertion.
Sans elles, la seule chose que le test validerait est que le calcul
ne déclenche pas d’erreur.
champ d'application TestArrondiArgent:
définition resultat égal à 100 € / 3
assertion resultat = 33,33 €
Comme vu dans le tutoriel, un test de champ d’application prend presque toujours la forme d’un appel au vrai champ d’application que vous voulez tester, en lui fournissant des entrées spécifiques et un résultat attendu :
#[test]
déclaration champ d'application Test_CalculImpotRevenu_1:
résultat calcul contenu CalculImpotRevenu
champ d'application Test_CalculImpotRevenu_1:
# Définir le calcul comme CalculImpotRevenu appliqué à des entrées spécifiques
définition calcul égal à
résultat de CalculImpotRevenu avec {
-- individu:
Individu {
-- revenu: 20 000 €
-- nombre_enfants: 0
}
}
# Vérifier que le résultat est comme attendu
assertion calcul.impot_revenu = 4 000 €
Test Cram
Le test Cram (ou test CLI) fournit un moyen de valider la sortie d’une commande Catala donnée, ce qui peut être utile dans des scénarios d’intégration plus spécifiques. Il est inspiré par le système de test Cram, en ce sens qu’un seul fichier source inclut à la fois la commande de test et sa sortie attendue.
Par exemple, vérifier qu’une erreur se produit quand attendu ne peut pas être fait avec
des tests de champ d’application, qui doivent réussir. Vous voudrez peut-être inclure des tests qui utilisent
catala proof. Ou vous pourriez vouloir, pour un test simple, valider que la trace est
exactement comme prévu. Pour cela, une section ```catala-test-cli doit être
introduite dans le fichier source Catala. La première ligne commence toujours par
$ catala , suivi de la commande Catala à exécuter sur le fichier actuel ; le
reste est la sortie attendue de la commande ; de plus, si la commande
s’est terminée avec une erreur, la dernière ligne affichera le code d’erreur.
```catala-test-cli
$ catala interpret --scope=Test --trace
[LOG] ☛ Definition applied:
─➤ tutoriel.catala_fr:214.14-214.25:
│
214 │ définition calcul égal à
│ ‾‾‾‾‾‾
Test
[LOG] → CalculImpotRevenu.direct
...
[LOG] ≔ CalculImpotRevenu.direct.résultat: CalculImpotRevenu { -- impot_revenu: 4 000,00 € }
[LOG] ← CalculImpotRevenu.direct
[LOG] ≔ Test.calcul: CalculImpotRevenu { -- impot_revenu: 4 000,00 € }
┌─[RESULT]─ Test ─
│ calcul = CalculImpotRevenu { -- impot_revenu: 4 000,00 € }
└─
```
Lors de l’exécution de clerk test, la commande spécifiée est exécutée sur le fichier ou répertoire (tronqué
à ce point). Si la sortie correspond exactement à ce qui est dans le fichier, les tests
passent. Sinon, il échoue, et les différences précises sont affichées côte à côte.
Attention, les tests cram ne peuvent pas être utilisés pour tester le code généré par le backend ; donc clerk test --backend=... n’exécutera pas les tests cram.
Notez que pour ces ```catala-test-cli, $ catala test-scope Test est un raccourci pour
$ catala interpret --scope=Test
De plus, ils permettent d’exécuter le test avec des drapeaux variables en utilisant le drapeau --test-flags de clerk run. Voir clerk test --help pour les détails.
Si un test cram échoue, mais en raison d’une différence légitime (par exemple, un changement de numéro de ligne
dans l’exemple ci-dessus), il est possible d’exécuter
clerk test --reset pour mettre à jour automatiquement le résultat attendu. Cela fera
immédiatement passer le test cram, mais les systèmes de gestion de versions
et une revue de code standard mettront en évidence les changements.
clerk test --reset peut également être utilisé pour initialiser un nouveau test, à partir d’une
section ```catala-test-cli qui ne fournit que la commande sans sortie attendue.
clerk test ‐‐help
clerk test ‐‐help
NAME
clerk-test - Scan the given files, directories or clerk targets for
catala tests, build their requirements and run them all. If --backend
is unspecified or interpret, both scope tests and CLI tests are run ;
--reset can be used to rewrite the expected results of CLI tests to
their current result. For any other --backend, CLI tests are skipped
and scope tests are compiled to the specified backend with the catala
option --autotest, and then run, ensuring the consistency of results.
When clerk targets are provided, only their specifically defined tests
will be executed.
SYNOPSIS
clerk test [OPTION]… [TARGET(S)]…
ARGUMENTS
TARGET(S)
Clerk target(s), individual file(s) or folder(s) to process
OPTIONS
--backend=BACKEND (absent=interpret)
Run the program using the given backend. BACKEND must be one of
interpret, ocaml, c, python, java
--build-dir=DIR (absent CLERK_BUILD_DIR env)
Directory where intermediate compilation artifacts should be
written. Defaults to '_build'.
-c FLAG, --catala-opts=FLAG
Option to pass to the Catala compiler. Can be repeated. If neither
this nor --test-flags is specified, the flags for the different
backends default to -O.
--color[=VAL] (default=always) (absent=auto or CATALA_COLOR env)
Allow output of colored and styled text. Use auto, to enable when
the standard output is to a terminal, never to disable.
--config=FILE
Clerk configuration file to use, instead of looking up
"clerk.toml" in parent directories.
-d, --debug
Prints debug information
--diff[=VAL] (default=) (absent CATALA_DIFF_COMMAND env)
Use a standard diff command instead of the default side-by-side
view. If no argument is supplied, the command will be patdiff if
available or diff otherwise. A supplied argument will be used as
diff command with arguments pointing to the reference file and the
output file
-e EXE, --exe=EXE
Catala compiler executable.
--failures
Show details of files with failed tests only
-I DIR, --include=DIR (absent CATALA_INCLUDE env)
Make modules from the given directory available from everywhere.
Several dirs can be specified by repeating the flag or separating
them with ':'.
--makeflags=FLAG (absent MAKEFLAGS env)
Provides the contents of a MAKEFLAGS variable to pass on to Ninja.
Currently recognizes the -i and -j options.
-o FILE, --output=FILE
FILE is the file that will contain the build.ninja file output. If
not specified, the build.ninja file is set to
/clerk.ninja in debug mode, and a temporary file
otherwise
-r, --reset
Used with the `test` command, resets the test output to whatever
is output by the Catala compiler.
--short
Don't display detailed test failures diff
--summary
Only display a summary of the test results
--target-dir=DIR (absent CLERK_TARGET_DIR env)
Directory where final compilation targets should be written.
Defaults to '_target'.
--test-flags[=FLAGS] (default=) (absent CATALA_TEST_FLAGS env)
Flags to pass to the catala interpreter on catala test-scope
tests. Comma-separated list. A subset may also be applied to the
compilation of modules, as needed. WARNING: flag shortcuts are not
allowed here (i.e. don't use non-ambiguous prefixes such as
--closure for --closure-conversion) NOTE: if this is set, all cli
tests that are not catala test-scope are skipped to avoid
redundant testing.
-v, --verbose
Display the full list of tests that have been run
-W, --whole-program
Use Catala --whole-program option when testing or executing Catala
scopes.
--xml (absent CATALA_XML_REPORT env)
Output the test report in JUnit-compatible XML format
COMMON OPTIONS
--help[=FMT] (default=auto)
Show this help in format FMT. The value FMT must be one of auto,
pager, groff or plain. With auto, the format is pager or plain
whenever the TERM env var is dumb or undefined.
--version
Show version information.
EXIT STATUS
clerk test exits with:
0 on success.
123 on indiscriminate errors reported on standard error.
124 on command line parsing errors.
125 on unexpected internal errors (bugs).
ENVIRONMENT
These environment variables affect the execution of clerk test:
CATALA_COLOR
See option --color.
CATALA_DIFF_COMMAND
See option --diff.
CATALA_INCLUDE
See option --include.
CATALA_TEST_FLAGS
See option --test-flags.
CATALA_XML_REPORT
See option --xml.
CLERK_BUILD_DIR
See option --build-dir.
CLERK_TARGET_DIR
See option --target-dir.
MAKEFLAGS
make-compatible flags handling. Currently recognizes the -i and -j
options and forwards them through to Ninja.
SEE ALSO
clerk(1)
clerk ci
Scanne le projet et exécute toutes les actions possibles. Cela inclut
l’interprétation de tous les tests catala et tests CLI (équivalent à
l’exécution de la commande clerk test), et aussi, la construction de toutes les cibles clerk
(équivalent à l’exécution de la commande clerk build) aux côtés de
l’exécution de leurs tests contre tous leurs backends définis. Cette
commande est utile pour l’exécution d’intégrations continues (CIs)
où toutes les actions de construction et de test sont souvent destinées à être exécutées.
clerk ci ‐‐help
clerk ci ‐‐help
NAME
clerk-ci - Scan the project and run all possible actions. This
includes the interpretation of all catala tests and CLI tests
(equivalent to running the clerk test command), and also, the build of
all clerk targets (equivalent to running the clerk build command)
alongside the execution of their tests against all their defined
backend. This command is useful for the execution of continuous
integrations (CIs) where all build and test actions are often meant to
be executed. Run with --debug for the full log of events.
SYNOPSIS
clerk ci [OPTION]…
OPTIONS
--build-dir=DIR (absent CLERK_BUILD_DIR env)
Directory where intermediate compilation artifacts should be
written. Defaults to '_build'.
-c FLAG, --catala-opts=FLAG
Option to pass to the Catala compiler. Can be repeated. If neither
this nor --test-flags is specified, the flags for the different
backends default to -O.
--color[=VAL] (default=always) (absent=auto or CATALA_COLOR env)
Allow output of colored and styled text. Use auto, to enable when
the standard output is to a terminal, never to disable.
--config=FILE
Clerk configuration file to use, instead of looking up
"clerk.toml" in parent directories.
-d, --debug
Prints debug information
--diff[=VAL] (default=) (absent CATALA_DIFF_COMMAND env)
Use a standard diff command instead of the default side-by-side
view. If no argument is supplied, the command will be patdiff if
available or diff otherwise. A supplied argument will be used as
diff command with arguments pointing to the reference file and the
output file
-e EXE, --exe=EXE
Catala compiler executable.
--failures
Show details of files with failed tests only
-I DIR, --include=DIR (absent CATALA_INCLUDE env)
Make modules from the given directory available from everywhere.
Several dirs can be specified by repeating the flag or separating
them with ':'.
-o FILE, --output=FILE
FILE is the file that will contain the build.ninja file output. If
not specified, the build.ninja file is set to
/clerk.ninja in debug mode, and a temporary file
otherwise
--short
Don't display detailed test failures diff
--summary
Only display a summary of the test results
--target-dir=DIR (absent CLERK_TARGET_DIR env)
Directory where final compilation targets should be written.
Defaults to '_target'.
--test-flags[=FLAGS] (default=) (absent CATALA_TEST_FLAGS env)
Flags to pass to the catala interpreter on catala test-scope
tests. Comma-separated list. A subset may also be applied to the
compilation of modules, as needed. WARNING: flag shortcuts are not
allowed here (i.e. don't use non-ambiguous prefixes such as
--closure for --closure-conversion) NOTE: if this is set, all cli
tests that are not catala test-scope are skipped to avoid
redundant testing.
-v, --verbose
Display the full list of tests that have been run
-W, --whole-program
Use Catala --whole-program option when testing or executing Catala
scopes.
--xml (absent CATALA_XML_REPORT env)
Output the test report in JUnit-compatible XML format
COMMON OPTIONS
--help[=FMT] (default=auto)
Show this help in format FMT. The value FMT must be one of auto,
pager, groff or plain. With auto, the format is pager or plain
whenever the TERM env var is dumb or undefined.
--version
Show version information.
EXIT STATUS
clerk ci exits with:
0 on success.
123 on indiscriminate errors reported on standard error.
124 on command line parsing errors.
125 on unexpected internal errors (bugs).
ENVIRONMENT
These environment variables affect the execution of clerk ci:
CATALA_COLOR
See option --color.
CATALA_DIFF_COMMAND
See option --diff.
CATALA_INCLUDE
See option --include.
CATALA_TEST_FLAGS
See option --test-flags.
CATALA_XML_REPORT
See option --xml.
CLERK_BUILD_DIR
See option --build-dir.
CLERK_TARGET_DIR
See option --target-dir.
SEE ALSO
clerk(1)
clerk run
Exécute l’interpréteur Catala sur les fichiers donnés, après
avoir construit leurs dépendances. Le champ d’application à exécuter peut être spécifié
en utilisant l’option -s.
Au moment de la rédaction, clerk run est restreint aux champs d’application qui ne
nécessitent pas d’entrées, il est donc utilisé pour exécuter des tests de champ d’application.
Exemple
$ clerk run tests/tests_allocations_familiales.catala_fr -s Test1
clerk run ‐‐help
clerk run ‐‐help
NAME
clerk-run - Runs the Catala interpreter on the given files, after
building their dependencies. The scope to be executed can be specified
using the -s option.
SYNOPSIS
clerk run [OPTION]… [FILE]…
ARGUMENTS
FILE
File(s) or folder(s) to process
OPTIONS
--backend=BACKEND (absent=interpret)
Run the program using the given backend. BACKEND must be one of
interpret, ocaml, c, python, java
--build-dir=DIR (absent CLERK_BUILD_DIR env)
Directory where intermediate compilation artifacts should be
written. Defaults to '_build'.
-c FLAG, --catala-opts=FLAG
Option to pass to the Catala compiler. Can be repeated. If neither
this nor --test-flags is specified, the flags for the different
backends default to -O.
--color[=VAL] (default=always) (absent=auto or CATALA_COLOR env)
Allow output of colored and styled text. Use auto, to enable when
the standard output is to a terminal, never to disable.
--command=CMD (absent=interpret)
The catala command to run on the input files. Normally interpret,
this flag can be used to run typecheck or a custom plugin instead.
This is ignored if --backend isn't interpret.
--config=FILE
Clerk configuration file to use, instead of looking up
"clerk.toml" in parent directories.
-d, --debug
Prints debug information
-e EXE, --exe=EXE
Catala compiler executable.
-I DIR, --include=DIR (absent CATALA_INCLUDE env)
Make modules from the given directory available from everywhere.
Several dirs can be specified by repeating the flag or separating
them with ':'.
--makeflags=FLAG (absent MAKEFLAGS env)
Provides the contents of a MAKEFLAGS variable to pass on to Ninja.
Currently recognizes the -i and -j options.
-o FILE, --output=FILE
FILE is the file that will contain the build.ninja file output. If
not specified, the build.ninja file is set to
/clerk.ninja in debug mode, and a temporary file
otherwise
--prepare-only
Compile dependencies of the target(s) but do not run it.
-s SCOPE, --scope=SCOPE
Used with the `run` command, selects which scope of a given Catala
file to run.
--target-dir=DIR (absent CLERK_TARGET_DIR env)
Directory where final compilation targets should be written.
Defaults to '_target'.
-W, --whole-program
Use Catala --whole-program option when testing or executing Catala
scopes.
COMMON OPTIONS
--help[=FMT] (default=auto)
Show this help in format FMT. The value FMT must be one of auto,
pager, groff or plain. With auto, the format is pager or plain
whenever the TERM env var is dumb or undefined.
--version
Show version information.
EXIT STATUS
clerk run exits with:
0 on success.
123 on indiscriminate errors reported on standard error.
124 on command line parsing errors.
125 on unexpected internal errors (bugs).
ENVIRONMENT
These environment variables affect the execution of clerk run:
CATALA_COLOR
See option --color.
CATALA_INCLUDE
See option --include.
CLERK_BUILD_DIR
See option --build-dir.
CLERK_TARGET_DIR
See option --target-dir.
MAKEFLAGS
make-compatible flags handling. Currently recognizes the -i and -j
options and forwards them through to Ninja.
SEE ALSO
clerk(1)
clerk clean
Supprime les fichiers et répertoires précédemment générés par clerk, notamment
le répertoire _build et _targets. Utile pour nettoyer la machine après
un travail CI ou pour s’assurer que vous n’incluez pas de fichiers périmés et anciens dans votre pipeline de construction.
clerk clean ‐‐help
clerk clean ‐‐help
NAME
clerk-clean - Removes files and directories previously generated by
clerk if any.
SYNOPSIS
clerk clean [OPTION]…
OPTIONS
--build-dir=DIR (absent CLERK_BUILD_DIR env)
Directory where intermediate compilation artifacts should be
written. Defaults to '_build'.
-c FLAG, --catala-opts=FLAG
Option to pass to the Catala compiler. Can be repeated. If neither
this nor --test-flags is specified, the flags for the different
backends default to -O.
--color[=VAL] (default=always) (absent=auto or CATALA_COLOR env)
Allow output of colored and styled text. Use auto, to enable when
the standard output is to a terminal, never to disable.
--config=FILE
Clerk configuration file to use, instead of looking up
"clerk.toml" in parent directories.
-d, --debug
Prints debug information
-e EXE, --exe=EXE
Catala compiler executable.
-I DIR, --include=DIR (absent CATALA_INCLUDE env)
Make modules from the given directory available from everywhere.
Several dirs can be specified by repeating the flag or separating
them with ':'.
-o FILE, --output=FILE
FILE is the file that will contain the build.ninja file output. If
not specified, the build.ninja file is set to
/clerk.ninja in debug mode, and a temporary file
otherwise
--target-dir=DIR (absent CLERK_TARGET_DIR env)
Directory where final compilation targets should be written.
Defaults to '_target'.
-W, --whole-program
Use Catala --whole-program option when testing or executing Catala
scopes.
COMMON OPTIONS
--help[=FMT] (default=auto)
Show this help in format FMT. The value FMT must be one of auto,
pager, groff or plain. With auto, the format is pager or plain
whenever the TERM env var is dumb or undefined.
--version
Show version information.
EXIT STATUS
clerk clean exits with:
0 on success.
123 on indiscriminate errors reported on standard error.
124 on command line parsing errors.
125 on unexpected internal errors (bugs).
ENVIRONMENT
These environment variables affect the execution of clerk clean:
CATALA_COLOR
See option --color.
CATALA_INCLUDE
See option --include.
CLERK_BUILD_DIR
See option --build-dir.
CLERK_TARGET_DIR
See option --target-dir.
SEE ALSO
clerk(1)