Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

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.

Accéder à un état particulier d’une variable de champ d’application

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, ni foo état bar ni foo état baz, puisque bar est le premier état et foo est 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 à foo et cela fera en fait référence à l’état précédemment défini pour foo, ici bar. Donc foo et foo état bar sont équivalents dans ce contexte, et vous ne pouvez pas faire référence à foo état baz puisqu’il est en cours de définition.
  • En dehors des définitions de foo, vous pouvez faire référence à foo état bar et foo état baz. Si vous faites référence simplement à foo, cela prendra par défaut le dernier état, ici baz.
  • Si foo est une variable entrée du 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

N-uplets et liaisons locales

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 ., comme individu.revenu.

Mettre à jour des structures de manière concise

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.

Cas attrape-tout dans le filtrage par motif

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

Tester un cas spécifique

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 ?

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.