Introduction
Welcome to the Catala domain-specific programming language, an introductory book about Catala!
The Catala domain-specific language lets you annotate a legal specification with executable and deployable code in an efficient, high-assurance fashion. Automating the enforcement of a law or regulation through the use of a computer program is not neutral for the Rule of Law as well as the rights of users. Please thread lightly when considering automating anything else than tax or social benefits computation.
Contrary to a rules engine, Catala is a fully-fledged programming language with modular abstractions inspired by functional programming. But most importantly, it enables real collaboration between lawyers and computer scientists by allowing the practice of pair programming through literate programming for writing and updating the programs and their legal specifications.
Who Catala is for
Catala is a domain-specific programming language, hence its use is targeted to a specific class of users and organizations.
Multi-disciplinary teams
Catala is designed to be used by teams mixing legal and computer science expertise. Indeed, automating the enforcement of a law or a regulation requires bridging the ambiguity gap of the legal text to a computer program that knows what to do in every possible situation. Bridging this ambiguity gap is one of missions of lawyers, trained in interpreting legal texts and performing legal research to come up with a decision that can be justified withing the framework of the Rule of Law.
If lawyers should be deciding what the program does, they are not trained in programming concepts and abstractions, nor do they know how to write thousands of lines of code in a clean and maintainable fashion. Hence, the role of the computer scientist is to ensure that the program automating the enforcement of a law or regulation is clean, concise, featuring correctly-scoped abstractions that prevent code duplication and ease maintenance and future changes. Moreover, the computer scientist is also responsible for deploying the program in a bigger IT system that might break its assumptions or stress its performance.
By reusing existing software engineering techniques and tools, while exhibiting lawyer-friendly concepts at its surface, Catala lets lawyers and computer scientists collaborate in a truly agile manner, going beyond the separation between development and quality assurance teams that communicate only via test cases and specification documents.
Government agencies and public service organizations
Automating the enforcement of a law or regulation through a computer program is no small task. To make sure all are treated fairly under the Rule of Law, your program should exhaustively take into account every situation described by the law or regulation. While automating only the most simple situation corresponding to a majority of users might acceptable in certain situations, it cannot be the basis for a production-ready public service. Indeed, creating a difference between an automated "simple path" and a non-automated handling of complex situation widens the digital divide, and increases confusion for users and case workers alike.
Catala shines when the goal is to automate exhaustively and with high assurance a given law or regulation, and to maintain this automation over time. The language and tooling will help you manage the growing complexity, maintenance over legal changes, corner cases and production-ready deployment inside a legacy IT system. If you are looking for a tool to make a first-level user-help chatbot backend that only answers basic questions, or a simplified model of a law for an economic study, then you should consider using other tools. On the other hand, if you are in an organization responsible for running a public service like taxes or social benefits, then you should have the means to properly engineer the computer program responsible for the automation, and use Catala to help you with it.
Because Catala compiles to mainstream programming languages like Python or C, it yields portable source code libraries that are embeddable in virtually any IT system, legacy or modern. More particularly, Catala programs can be deployed behind a Web API or embarked in a desktop application, enabling "write once, use anywhere". Hence, Catala is particularly suited to historical government agencies that have been operating their IT systems for decades in the past, and will continue to do so decades in the future.
Students and academics in CS and/or Law
Formalizing law, which is a more general term for translating law into executable computer code, has been a subject of scholarly attention for a very long time. AI & Law has historically been the umbrella community for this line of work, with three sequential trends: logic programming, ontologies, and now machine learning and especially natural language processing. While these trends have uncovered important results for formalizing law, big research questions remain. How to model complex legal provisions spanning large corpuses? Can formalizing law be automated? Can legal drafting be technologically augmented? How to detect inconsistencies or loopholes by static or dynamic analysis?
With strong roots to the research community, its commitment to open-source, its formalized semantics and its extensible compiler, Catala as a programming language is an opportunity for student learning and research projects. While teachers and students can use Catala for hands-on exploring of tax and social benefits law, researchers can use Catala programs as datasets or program a new analysis that can be readily deployed to the users of Catala.
If you're a student, a professor, or anything else actually, you're welcome to use Catala for free and contribute back to it by filing issues, proposing pull requests, or develop plugins and tooling around it.
Who this book is for
This book is primarily geared towards programmers that want to learn Catala, set up a project using it to translate some legal text into executable code, and be guided through the process. The book assume basic knowledge of functional programming idioms, and generally software engineering experience with another mainstream programming language.
If you, a programmer, work in a multidisciplinary team with one or several lawyers, it is up to you to explain to them what Catala is and how to work with it. This guide will thus also cover various topics around the collaboration between lawyers and programmers.
If you are a lawyer and stumble across this book, you are also welcome to read it, although parts of it will not be relevant to you. You might checkout instead introductory articles that set up the context around computer code, translating law to computer code and introduce the specificities of Catala:
- 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.
How to use this book
The book is organized into two parts: the user guide and the reference guide. While the user guide is meant to be read linearly and aimed at new users, the reference guide is the go-to stop for checking items about the Catala language and tooling as you're using it, whether you are a new or experienced user.
The user guide starts with Chapter 1 and the Catala equivalent of "Hello, world!" program. Chapter 2 explains the core concepts of Catala with a hands-on tutorial centered on the automation of a basic, fictional tax system. Moving onto serious business, Chapter 3 dives into the setting up of a real-world Catala project with version control, monitoring legal changes, testing, continuous integration, automated deployment, etc. Finally, Chapter 4 speeds up your learning by answering to (almost) all the questions that you'll normally stumble upon while coding the law with Catala.
In the reference guide, Chapter 5 details all the features
available in the Catala language, while Chapter
6 focus on the command line interfaces and features of the two
binaries that you will be working with: clerk
and catala
.
The source files from which this book is generated can be found on GitHub. While the contents of this book are expected to correspond to the latest version of Catala, some inconsistencies might appear. If you spot one, or have comments or suggestions about the book, please file an issue!
Getting started
Welcome to the getting started chapter! This chapter is all about setting you up for the beginning of your journey into Catala. First, you'll need to install Catala on your machine, as Catala is not a Software as a Service but rather a fully-fledged programming language toolchain. Second, you should test whether everything is OK by creating and running your first, "Hello, world!"-like Catala program.
You will get introduced to :
- the Catala compiler
catala
; - the Catala build system
clerk
; - the Catala IDE tooling (language plugin and formatter).
Installing Catala on your machine
Currently, Catala is only available through source building. We plan to package Catala in binary format in the future, which will greatly ease the installation process.
Catala is a programming language primarily designed to be installed on your machine and run locally in your favorite development environment. Materially, Catala is comprised of several executables that together complete the tooling for the programming language:
- the Catala compiler
catala
, together with the build systemclerk
; - the Catala Language Server Protocol (LSP) server
catala-lsp
; - the Catala auto formatting tool
catala-format
; - the Catala plugin for your text editor or IDE.
Under the hood, most of these executables are produced using the
OCaml software toolchain, so the installation process
begins with opam
, the package and build system for OCaml.
The installation instructions all assume proficiency with the command line and basic knowledge about the filesystem and the general process of building executables from sources using a package manager.
If your installation failed even though you were following the installation guide, please file an issue or start a thread on the Catala community online chat.
In your issue or post, please provide:
- your platform and operating system;
- a log of the commands you executed with their command line output.
The installation instructions are different whether you are on an Unix-compatible system (Linux, MacOS, Windows Subsystem for Linux), or on plain Windows. Please pick the appropriate guide for your situation.
During the installation steps several prompts might occur, choosing
the default option (by pressing enter each time) or answering yes (by
typing y
then enter) is enough.
Linux/Mac/WSL
Unless noted otherwise, the installation of the Catala tooling happens on a regular command-line terminal.
For WSL2 users
For WSL2 users
We assume all the given commands are invoked in a
WSL2 environment. WSL2 can be installed by running > wsl --install
in a Window's PowerShell (Windows key + R
then type powershell
in
the prompt). WSL2 will install by default a Ubuntu-like virtual
machine. Then, you may enter the WSL2 environment and the virtual
machine by typing wsl
in the PowerShell.
Getting opam
Install the latest version of opam
(version >= 2.2), through
the official installation instructions
that we repeat here for convenience.
With aptitude (debian-like linux distributions):
$ sudo apt update
$ sudo apt install opam
Without aptitude:
$ bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh)"
At this point, opam
should be initialized on your machine. But it is not over,
as opam
needs to create a switch with a specific version of OCaml, where
all the packages that we'll install later will be compiled and stored. To
initialize opam
and create this first switch, enter the following:
$ opam init -c 4.14.2
$ eval $(opam env)
Then you can keep your current switch for installing Catala, or create a new, specific one with
$ opam switch create 4.14.2
Catala normally supports OCaml versions 4.14.X
and 5.0.X
.
Getting Catala
Run the following command to install the latest Catala version via opam
:
$ opam pin catala.dev git+https://github.com/CatalaLang/catala
Once this finishes, the Catala compiler (and its build system) should
be installed. You should be able to succesfully call $ catala --version
in your terminal. If that's not the case, try invoking $ eval $(opam env)
priorly.
At any time, you can retrieve the latest Catala development version using these simple commands:
$ opam update
$ opam upgrade catala
This method also works for the other opam
packages presented below:
just replace upgrade catala
by upgrade catala-lsp
, etc.
Getting the LSP server (needed by the VSCode extension)
The VSCode extension requires the Catala's Language Server Protocol to be installed. This can be done by running:
$ opam pin catala-lsp.dev git+https://github.com/CatalaLang/catala-language-server -y
Getting the VSCode extension
Install VSCode and open it. Browse the extension marketplace and
install the Catala
extension.
For WSL2 installations
For WSL2 installations
VSCode needs to reach the installed WSL
environment to retrieve the Catala tools. This can be done by
installing the official WSL VSCode extension. Once this is
installed, you will need to load a WSL VSCode window by pressing F1
(which opens the VSCode prompt) and execute the following command
WSL: Connect to WSL
.
Getting the Catala code formatter
Run the following command:
$ opam pin catala-format.dev git+https://github.com/CatalaLang/catala-format
This installation will take some time as it requires installing a Rust
toolchain. If you already have a Rust tool chain installed (check by typing cargo
in the terminal), select
ignore pin depends
when asked for.
Once this is installed, you may refresh your VSCode environment (F1
, then
Developer: Reload Window
) which will notify the Catala extension that the
formatter is now available. You can invoke the formatter using F1
, then
Format Document
or by a user-defined's key-binding.
Windows
The Windows installation is currently experimental, as the Windows support for the OCaml software toolchain dates from the early 2020s. If possible, use WSL (Windows Subsystem for Linux) instead.
Binary installer
You can download and install Catala using this binary installer - you might need administrator privileges:
Currently, this installer provides the basic Catala compiler with the LSP server used by the VS Code extension along with the Catala code formatter. This is handy for experimenting with the language but this does not include the full compilation toolchain and the Catala build system required to use modules. To get those, you will need to install from the sources directly.
Once this Catala setup file is installed, you might need to restart
VS Code if it was previously launched. To make sure everything was
properly installed, you can open a VS Code terminal and type $ catala --version
. If this does not display an error, everything should be
properly setup.
To install the Catala VS Code extension, please refer to this section.
Installing from sources
Getting Opam
Open a PowerShell and install opam by invoking
$ Invoke-Expression "& { $(Invoke-RestMethod https://opam.ocaml.org/install.ps1) }"
If an unexpected error occurs, try another opam
installation method as
listed on the OCaml on Windows official webpage.
Then, initialize opam
:
$ opam init -c 4.14.2
Getting Catala
Currently, the opam
Catala package is not directly buildable on
Windows. However, the Catala's lsp server bundles a subset of Catala
which is fine. This may be installed with the following command
$ opam pin catala-lsp git+https://github.com/CatalaLang/catala-language-server -y
If the installation step fails to find the "ninja" tool, you may install it using winget.
In a powershell, type winget install Ninja-build.Ninja
as described
here.
Setting up the Catala LSP server
After the previous step, the Catala LSP server should be built in
opam
's binaries directory. In order for VS Code to be able to get it,
this directory must be added to Windows' PATH
environment variable.
To change the PATH
environment variable, follow these
instructions.
The directory in question should be located in
%LOCALAPPDATA%\opam\default\bin
(n.b., default
might be named
something else such as "4.14.2", double-check the directory location).
Getting the VS Code extension
Install VS Code and open it. Browse the extension marketplace and
install the Catala
extension.
Getting the Catala code formatter
Currently, the code formatter is not yet available on Windows.
Creating your first Catala program
Now that you have installed the Catala tooling, you can test its correct
execution with the equivalent of a Hello, world!
program.
Catala programs are just text files that can be handled by any text editor/IDE. Hence, to kickstart your Catala development, we recommend you open your favorite text editor.
The Catala maintenance team currently only provides full support for the VSCode text editor (syntax highlighting, language server, formatter).
However the Catala community has written a number of plugins for other text editors and IDEs, whose maintenance is performed on a best-effort basis. Please contribute if you add support to your favorite text editor!
In your text editor/IDE, create a new folder for your Catala developments (for
instance named catala
) and inside it an empty text file (for instance named
hello_world.catala_en
).
Writing some specifications into a Catala file
Copy paste the following text into your file:
# Catala tutorial
## Hello, world!
Your first Catala program should output the integer `42` as the
Answer to the Ultimate Question of Life, the Universe, and Everything.
At this point, your file looks like a regular Markdown file, and does not contain any Catala source code per se. Indeed, as Catala uses literate programming, any text inside your file is assumed to be Markdown specification by default. Let's now see how to actually write some code!
Writing your first Catala code block
Below the ## Hello, world!
paragraph, open a Markdown code block indicating
the catala
language:
```catala
<you will insert your Catala code here !>
```
These Catala code blocks can be placed anywhere through the regular Markdown of your source file. Actually, if you're following the Catala methodology to translate law into code, your source file will mostly look like a big Markdown document with lots of little Catala code blocks interspersed.
Your Catala source code should always be placed inside a Catala code block
introduced by a line with ```catala
and ended by a line with ```
.
Otherwise, the Catala compiler will just ignore your code.
Now, inside the Catala code block, copy-paste the following:
declaration scope HelloWorld:
output answer_everything content integer
scope HelloWorld:
definition answer_everything equals 42
It is not important to understand what this code does now. You will learn about it later in the the tutorial.
Typechecking and running the Catala program
Since Catala is a strongly typed language, you can typecheck your program
without running it to see whether there are some syntax or typing errors. This
is done through the catala typecheck
command:
$ catala typecheck hello_world.catala_en
The result of this command should be:
┌─[RESULT]─
│ Typechecking successful!
└─
If the program typechecks, we can run it through the interpreter contained
inside the catala
compiler. This is done with the following command, indicating
that we want to run the --scope
named HelloWorld
inside the file hello_world.catala_en
:
$ catala interpret hello_world.catala_en --scope=HelloWorld
The result of this command should be, as it is customary:
┌─[RESULT]─
│ answer_everything = 42
└─
You should now be all set to continue your Catala journey through the tutorial!
Tutorial : computing your taxes
Welcome to this tutorial, whose objective is to guide you through the features of the Catala language and teach you how to annotate a simple legislative text using the language, and get out an executable program that compute your taxes!
This tutorial does not cover the installation of Catala. For more information about this, please refer to the installation section. If you want follow this tutorial locally, read the section about creating your first program and simply copy-paste the code snippets of the tutorial into your Catala program file.
At any point, please refer to the Catala syntax cheat sheet or the reference guide for an exhaustive view of the syntax and features of Catala; this tutorial is rather designed to ease you into the language and its common use patterns.
There are three sections in the tutorial, that are designed to be complete in order as they cover increasingly difficult and advanced features of the language:
- the first section is about the basic blocks of the language programs with simple data flow;
- the second section is about what makes Catala unique: its first-class handling of conditional definitions and exceptions;
- the third section is about scaling up the codebase with lists of items and multiple scopes ;
- the fourth section finishes with variable states and calling scopes dynamically.
This tutorial is designed to be an interactive experience. While reading the
text of the different sections, we encourage you to create a tutorial.catala_en
empty text
file and fill it by copy-pasting the code snippets presented. Through this
companion file, you will be able to see first-hand how the Catala typechecker
and interpreter behaves on the different examples, and even make your own
experiences by tweaking the code yourself.
Moreover, after each section, some hands on exercises will allow you to put to test what you have learnt. We encourage you to complete these exercises before moving on to the next section.
You should be all set to begin now. Godspeed!
Basic blocks of a Catala program
In this section, the tutorial introduces the basic blocks of a Catala program : the difference between law and code, data structures, scopes, variables and formulas. By the end of the section, you should be able to write a simple Catala program equivalent to a single function with local variables whose definitions can refer to one another.
A recap of the tutorial section with the full code that is expected to be
in your tutorial.catala_en
companion file is attached at the end of this page.
Please refer to it if you feel lost during the reading and want to have a
vibes check of whether you are on track to complete your tutorial.catala_en
file.
Mixing law and code
Catala is a language designed around the concept of literate programming, that is the mixing between the computer code and its specification in a single document. Why literate programming? Because it enables a fine-grained correspondance between the specification and the code. Whenever the specification is updated, knowing where to update the code is trivial with literal programming. This is absolutely crucial for enabling long-term maintenance of complex and high-assurance programs like tax or social benefits computation.
Hence, a Catala source code file looks like a regular Markdown
document, with the specification written down and styled as Markdown text,
with the Catala code only present in well-bounded Catala code blocks introduced
by a line with ```catala
and ended by a line with ```
.
Before writing any Catala code, we must introduce the specification of the code for this tutorial. This specification will be based on a fictional Tax Code defining a simple income tax. But in general, anything can be used as a specification for a Catala program: laws, executive orders, court cases motivations, legal doctrine, internal instructions, technical specifications, etc. These sources can be mixed to form a complete Catala program that relies on these multiple sources. Concretely, incorporating a legal source of specification into the Catala program amounts to copy-pasting the text and formatting it in Markdown syntax inside the source code file.
Without further ado, let us introduce the first bit of specification for our fictional income tax, article 1 of the CTTC (Catala Tutorial Tax Code):
The income tax for an individual is defined as a fixed percentage of the individual's income over a year.
Catala uses Markdown-like formatting for the legal text in the .catala_en
files. So, to copy the text of the article into your tutorial.catala_en
file, mark up the article header with ##
and put the text below, as such:
## Article 1
The income tax for an individual is defined as a fixed percentage of the
individual's income over a year.
The spirit of writing code in Catala is to stick to the specification at all times in order to put the code snippets where they belong. Hence, we will introduce below the Catala code snippets that translate article 1, which should be put just below article 1 in the Catala source code file.
These code snippets should describe the program that computes the income tax, and contain the rule defining it as a multiplication of the income as rate. It is time to dive into Catala as a programming language.
# We will soon learn what to write here in order to translate the meaning
# of article 1 into Catala code.
# To create a block of Catala code in your file, bound it with Markdown-style
# "```catala" and "```" delimiters. You can write comments in Catala code blocks
# by prefixing lines with "#"
# In the rest of the tutorial, when presenting Catala code snippets, it is
# assumed implicitly that you should copy-paste them into your
# tutorial.catala_en file inside a Catala code block enclosed between
# "```catala" and "```" delimiters, and placed near the article of law that
# it implements.
Setting up data structures
The content of article 1 assumes a lot of implicit context: there exists an individual with an income, as well as an income tax that the individual has to pay each year. Even if this implicit context is not verbatim in the law, we have to explicit it in the computer code, in the form of data structures and function signatures.
Catala is a strongly-typed, statically compiled language, so all data structures and function signatures have to be explicitly declared. So, we begin by declaring the type information for the individual, the taxpayer that will be the subject of the tax computation. This individual has an income and a number of children, both pieces of information which will be needed for tax purposes :
# Data structure declarations and generally any declaration in Catala often
# does not match any specific article of law. Hence, you can put all the
# declarations at the top of your tutorial.catala_en file, before article 1.
# The name of the structure, "Individual", must start with an
# uppercase letter: this is the CamelCase convention.
declaration structure Individual:
# In this line, "income" is the name of the structure field and
# "money" is the type of what is stored in that field.
# Available types include: "integer", "decimal", "money", "date",
# "duration", and any other structure or enumeration that you declare.
data income content money
# The field names "income" and "number_of_children" start by a lowercase
# letter, they follow the snake_case convention.
data number_of_children content integer
This structure contains two data fields, income
and number_of_children
.
Structures are useful to group together data that goes together. Usually, you
get one structure per concrete object on which the law applies (like the
individual). It is up to you to decide how to group the data together, but we
advise you to aim at optimizing code readability.
Sometimes, the law gives an enumeration of different situations. These enumerations are modeled in Catala using an enumeration type, like:
# The name "TaxCredit" is also written in CamelCase.
declaration enumeration TaxCredit:
# The line below says that "TaxCredit" can be a "NoTaxCredit" situation.
-- NoTaxCredit
# The line below says that alternatively, "TaxCredit" can be a
# "ChildrenTaxCredit" situation. This situation carries a content
# of type integer corresponding to the number of children concerned
# by the tax credit. This means that if you're in the "ChildrenTaxCredit"
# situation, you will also have access to this number of children.
-- ChildrenTaxCredit content integer
In computer science terms, such an enumeration is called a "sum type" or simply an enum. The combination of structures and enumerations allow the Catala programmer to declare all possible shapes of data, as they are equivalent to the powerful notion of algebraic data types.
Notice that these data structures that we have declared cannot always be attached naturally to a particular piece of the specification text. So, where to put these declarations in your literate programming file? Since you will be often going back to these data structure declarations during programming, we advise you to group them together in some sort of prelude in your code source file. Concretely, this prelude section containing the data structure declaration will be your one stop shop when trying to understand the data manipulated by the rules elsewhere in the source code file.
Scopes as basic computation blocks
We've defined and typed the data that the program will manipulate. Now, we have to define the logical context in which this data will evolve. Because Catala is a functional programming language, all code exists within a function. And the equivalent to a function in Catala is called a scope. A scope is comprised of :
- a name,
- input variables (similar to function arguments),
- internal variables (similar to local variables),
- output variables (that together form the return type of the function).
For instance, article 1 declares a scope for computing the income tax:
# Scope names use the CamelCase naming convention, like names of structs
# or enums Scope variables, on the other hand, use the snake_case naming
# convention, like struct fields.
declaration scope IncomeTaxComputation:
# The following line declares an input variable of the scope, which is akin to
# a function parameter in computer science term. This is the piece of
# data on which the scope will operate.
input individual content Individual
internal tax_rate content decimal
output income_tax content money
The scope is the basic abstraction unit in Catala programs, and scopes can be composed. Since a function can call other functions, scopes can also call other scopes. We will see later how to do this, but first let us focus on the inputs and outputs of scopes.
The declaration of the scope is akin to a function signature: it contains a list
of all the arguments along with their types. But in Catala, scope variables can
be input
, internal
or output
. input
means that the variable has to be
provided whenever the scope is called, and cannot be defined within the scope.
internal
means that the variable is defined within the scope and cannot be
seen from outside the scope; it's not part of the return value of the scope.
output
means that a caller can retrieve the computed value of the variable.
Note that a variable can also be simultaneously an input and an output of the
scope, in that case it should be annotated with input output
.
Once the scope has been declared, we can use it to define our computation rules and finally code up article 1!
Defining variables and formulas
Article 1 actually gives the formula to define the income_tax
variable of
scope IncomeTaxComputation
, which translates to the following Catala code:
The income tax for an individual is defined as a fixed percentage of the individual's income over a year.
scope IncomeTaxComputation:
definition income_tax equals
individual.income * tax_rate
Let us unpack the code above. Each definition
of a variable (here,
income_tax
) is attached to a scope that declares it (here,
IncomeTaxComputation
). After equals
, we have the actual expression for the
variable : individual.income * tax_rate
. The syntax for formulas uses
the classic arithmetic operators. Here, *
means multiplying an amount of
money
by a decimal
, returning a new amount of money
. The exact behavior of
each operator depends on the types of values it is applied on. For instance,
here, because a value of the money
type is always an integer number of cents,
*
rounds the result of the multiplication to the nearest cent to provide the
final value of type money
(see the FAQ for more information
about rounding in Catala). About individual.income
, we see that the .
notation
lets us access the income
field of individual
, which is actually a structure
of type Individual
.
Similarly to struct field access, Catala lets you inspect the contents of
a enumeration value with pattern matching, as it is usual in functional programming
language. Concretely, if tax_credit
is a variable whose type is TaxCredit
as
declared above, then you can define the amount of a tax credit that depends
on a number of eligible children with the following pattern matching:
match tax_credit with pattern
-- NoTaxCredit: $0
-- ChildrenTaxCredit of number_of_eligible_children:
$10,000 * number_of_eligible_children
In the branch -- ChildrenTaxCredit of number_of_eligible_children:
, you know
that tax_credit
is in the variant ChildrenTaxCredit
, and
number_of_eligible_children
lets you bind the integer
payload of the
variant. Like in a regular functional programming language, you can give any
name you want to number_of_eligible_children
, which is useful if you're
nesting pattern matching and want to differentiate the contents of two different
variant payloads.
Now, back to our scope IncomeTaxComputation
. at this point we're still missing
the definition of tax_rate
. This is a common pattern when coding the
law: the definitions for various variables are scattered in different articles.
Fortunately, the Catala compiler automatically collects all the definitions for
each scope and puts them in the right order. Here, even if we define
tax_rate
after income_tax
in our source code, the Catala compiler
will switch the order of the definitions internally because tax_rate
is used in the definition of income_tax
. More generally, the order of toplevel
definitions and declarations in Catala source code files does not matter, and
you can refactor code around freely without having to care about dependency
order.
In this tutorial, we'll suppose that our fictional CTTC specification defines the percentage in the next article. The Catala code below should not surprise you at this point.
The fixed percentage mentioned at article 1 is equal to 20 %.
scope IncomeTaxComputation:
# Writing 20% is just an alternative for the decimal "0.20".
definition tax_rate equals 20 %
Common values and computations in Catala
So far, we have seen values that have types like decimal
, money
, integer
.
One could object that there is no point in distinguishing these three concepts,
as they are merely numbers. However, the philosophy of Catala is to make every
choice that affects the result of the computation explicit, and the
representation of numbers does affect the result of the computation. Indeed,
financial computations vary according to whether we consider money amount as an
exact number of cents, or whether we store additional fractional digits after
the cent. Since the kind of programs Catala is designed for
implies heavy consequences for a lot of users, the language is quite strict
about how numbers are represented. The rule of thumb is that, in Catala,
numbers behave exactly according to the common mathematical semantics one
can associate to basic arithmetic computations (+
, -
, *
, /
).
In particular, that means that integer
values are unbounded and can never
overflow. Similarly, decimal
values can be arbitrarily precise (although they are always rational, belonging
to ℚ) and do not suffer from floating-point imprecisions. For money
, the
language makes an opinionated decision: a value of type money
is always
an integer number of cents.
These choices has several consequences:
integer
divided byinteger
gives adecimal
;money
cannot be multiplied bymoney
(instead, multiplymoney
bydecimal
) ;money
multiplied (or divided) bydecimal
rounds the result to the nearest cent ;money
divided bymoney
gives adecimal
(that is not rounded whatsoever).
Concretely, this gives:
10 / 3 = 3.333333333...
$10 / 3.0 = $3.33
$20 / 3.0 = $6.67
$10 / $3 = 3.33333333...
The Catala compiler will guide you into using the correct operations explicitly, by reporting compiler errors when that is not the case.
For instance, typing to add an integer
and a decimal
gives the
following error message from the Catala compiler:
┌─[ERROR]─
│
│ I don't know how to apply operator + on types integer and decimal
│
├─➤ tutorial_en.catala_en
│ │
│ │ definition x equals 1 + 2.0
│ │ ‾‾‾‾‾‾‾
│
│ Type integer coming from expression:
├─➤ tutorial_en.catala_en
│ │
│ │ definition x equals 1 + 2.0
│ │ ‾
│
│ Type decimal coming from expression:
├─➤ tutorial_en.catala_en
│ │
│ │ definition x equals 1 + 2.0
│ │ ‾‾‾
└─
To fix this error, you need to use explicit casting, for instance by replacing
1
by decimal of 1
. Refer to the language reference for all
possible casting, operations and their associated semantics.
Catala also has built-in date
and duration
types with the common associated
operations (adding a duration to a date, substracting two dates to get a
duration, etc.). For a deeper look at date computations (which are very tricky!), look at the language reference.
Testing the code
Now that we have implemented a few articles in Catala, it is time to test our code to check that it behaves correctly. We encourage you to test your code a lot, as early as possible, and check the test result in a continuous integration system to prevent regressions.
The testing of Catala code is done with the interpreter inside the compiler,
accessible with the interpret
command and the --scope
option that specifies
the scope to be interpreted?
Why can't I test IncomeTaxComputation
directly?
Why can't I test IncomeTaxComputation
directly?
The reflex at this point is to execute the following command:
$ catala interpret tutorial.catala_en --scope=IncomeTaxComputation
┌[ERROR]─
│
│ This scope needs input arguments to be executed. But the Catala built-in interpreter does not have a way to retrieve input values from the command line, so it cannot execute this scope.
│ Please create another scope that provides the input arguments to this one and execute it instead.
│
├─➤ tutorial.catala_en
│ │
│ │ input individual content Individual
│ │ ‾‾‾‾‾‾‾‾‾‾
└─
As the error message says, trying to interpret directly IncomeTaxComputation
is like
trying to compute the taxes of somebody without knowing the income of the person!
To be executed, the scope needs to be called with concrete values for the income
and the number of children of the individual. Otherwise, Catala will complain
that input
variables of the scope are missing for the interpretation.
The pattern for testing make use of concepts that will be seen
later in the tutorial, so it is okay to take part of
the following as some mysterious syntax that performs what we want. Basically,
we will be creating for our test case a new test that will pass specific
arguments to IncomeTaxComputation
which is being tested:
declaration scope Test:
# The following line is mysterious for now
output computation content IncomeTaxComputation
scope Test:
definition computation equals
# The following line is mysterious for now
output of IncomeTaxComputation with {
# Below, we pass the input variables for "IncomeTaxComputation"
-- individual:
# "individual" has a structure type, so we need to build the
# structure "Individual" with the following syntax
Individual {
# "income" and "number_of_children" are the fields of the structure;
# we give them the values we want for our test
-- income: $20,000
-- number_of_children: 0
}
}
This test can now be executed through the Catala interpreter:
$ catala interpret tutorial.catala_en --scope=Test
┌─[RESULT]─
│ computation = IncomeTaxComputation { -- income_tax: $4,000.00 }
└─
We can now check that $4,000 = $20,000 * 20%
; the result is correct.
Use this test to regularly play with the code during the tutorial and inspect its results under various input scenarios. This will help you understand the behavior of Catala programs, and spot errors in your code 😀
You can also check that there is no syntax or typing error in your code, without testing it, with the following command:
$ catala typecheck tutorial.catala_en
┌─[RESULT]─
│ Typechecking successful!
└─
Checkpoint
This concludes the first section of the tutorial. By setting up data structures
like structure
and enumeration
, representing the types of scope
variables, and definition
of formulas for these variables, you should now be able to
code in Catala the equivalent of single-function programs that perform common
arithmetic operations and define local variables.
Recap of the current section
Recap of the current section
For reference, here is the final version of the Catala code consolidated at the end of this section of the tutorial.
```catala
declaration structure Individual:
data income content money
data number_of_children content integer
declaration scope IncomeTaxComputation:
input individual content Individual
internal tax_rate content decimal
output income_tax content money
```
## Article 1
The income tax for an individual is defined as a fixed percentage of the
individual's income over a year.
```catala
scope IncomeTaxComputation:
definition income_tax equals
individual.income * tax_rate
```
## Article 2
The fixed percentage mentioned at article 1 is equal to 20 %.
```catala
scope IncomeTaxComputation:
label article_2
definition tax_rate equals 20%
```
## Test
```catala
declaration scope Test:
output computation content IncomeTaxComputation
scope Test:
definition computation equals
output of IncomeTaxComputation with {
-- individual:
Individual {
-- income: $20,000
-- number_of_children: 0
}
}
```
Hands-on exercise: "Nothing is certain except death and taxes"
In this section, we present a practical exercise aiming to familiarize
the writing of Catala programs and understanding its basic concepts.
We strongly invite you to setup a working Catala
environment, open an editor (such as vscode
)
and copy-paste the following exercise template in a catala file; you
may name it exercise-2-1-1.catala_en
.
'exercise-2-1-1.catala_en' template file
'exercise-2-1-1.catala_en' template file
# Catala Book: Exercise 2-1-1 ```catala declaration structure Individual: data date_of_birth content date data date_of_death content DateOfDeath declaration enumeration DateOfDeath: -- Deceased content date -- StillAlive ``` # Question 1 Based on the `test_person1` declaration, declare a `test_person2` born on the 21st of December 1977 that is still alive. ```catala # Declaration and definition of a constant value named test_person1 declaration test_person1 content Individual equals Individual { -- date_of_birth: |1981-10-05| # Date format is |YYYY-MM-DD| -- date_of_death: Deceased content |2012-05-12| } declaration test_person2 content Individual equals test_person1 # <= Remove this line and replace it with your answer ``` ```catala declaration scope KillPerson: input fateful_date content date input victim content Individual output killed_individual content Individual ``` # Question 2 Given the `KillPerson` scope declaration, define a `KillPerson` scope output variable `killed_individual` that uses the `fateful_date` and `victim` input variables to create a new Individual that is a copy of the `victim` input but with its `date_of_death` updated. For the sake of simplicity, we will not check whether or not we kill an already deceased individual. You can test your solution by invoking the TestKillPerson Catala scope: `catala interpret exercise-2-1-1.catala_en --scope TestKillPerson`. ```catala declaration scope TestKillPerson: output computation content KillPerson scope TestKillPerson: definition computation equals output of KillPerson with { -- fateful_date: |2025-01-20| -- victim: test_person2 } # Define your KillPerson scope here # scope KillPerson: # ... ``` ```catala declaration structure Couple: data household_yearly_income content money data person_1 content Individual data person_2 content Individual # Define your test_couple definition here # declaration test_couple content Couple equals # ... ``` ```catala declaration scope TaxComputation: input processing_date content date input couple content Couple internal person1_dead_before_processing_date content boolean internal person2_dead_before_processing_date content boolean output tax_amount content money scope TaxComputation: definition tax_amount equals if person1_dead_before_processing_date or person2_dead_before_processing_date then $0 else couple.household_yearly_income * 15% ``` ## Question 4 Define another `TaxComputation` scope definition that defines both internal boolean variables `person1_dead_before_processing_date` and `person2_dead_before_processing_date`. ```catala # Define your new TaxComputation scope here # scope TaxComputation: # ... ``` ## Question 5 Lastly, we need to define a test for our computation. Based on the previously defined tests, write a definition of the `TestTaxComputation` test scope. Then, tweak the given test values to make sure your implementation is correct. ```catala declaration scope TestTaxComputation: output test_tax_computation content TaxComputation # Define your TestTaxComputation scope here # scope TestTaxComputation: # ... ```
Throughout this exercise, do not hesitate to refer to the Catala syntax cheat-sheet! In particular if you struggle with the language's syntactic constructions.
In this exercise, we want to define (yet another!) tax computation. This time, the tax amount an household is required to pay depends on whether individuals are still alive or not. In order to model such mechanism, we introduce the following Catala structures representing individuals.
An individual is referenced using only two informations: its date of
birth and its possible date of death. If an individual is still
alive, it has no date of death. Expressing this possibility can be
done using an enumeration: if the individual is still alive, its
date_of_death
entry will be StillAlive
otherwise it will be
Deceased
that must come along a date value as specified in the
following declaration.
declaration structure Individual:
data date_of_birth content date
data date_of_death content DateOfDeath
declaration enumeration DateOfDeath:
-- Deceased content date
-- StillAlive
# Declaration and definition of a constant value named test_person1
declaration test_person1 content Individual equals
Individual {
-- date_of_birth: |1981-10-05|
# Date format is |YYYY-MM-DD|
-- date_of_death: Deceased content |2012-05-12|
}
Question 1
Based on the test_person1
declaration, try to define a
test_person2
born on the 21st of December 1977 that is still alive.
Solution to Question 1
Solution to Question 1
Answer:
declaration test_person2 content Individual equals
Individual {
-- date_of_birth: |1977-12-21|
-- date_of_death: StillAlive
}
We now want a way to update the status of a person from alive to dead. We do
it through a dedicated KillPerson
scope whose declaration is:
declaration scope KillPerson:
input fateful_date content date
input victim content Individual
output killed_individual content Individual
Question 2
Given the KillPerson
scope declaration, define a KillPerson
scope
output variable killed_individual
that uses the fateful_date
and
victim
input variables to create a new Individual that is a copy of
the victim
input but with its date_of_death
updated. For the sake
of simplicity, we will not check whether or not we kill an already
deceased individual.
You can test your solution using the following TestKillPerson scope by invoking this command in a console:
catala interpret exercise-2-1-1.catala_en --scope TestKillPerson
declaration scope TestKillPerson:
output computation content KillPerson
scope TestKillPerson:
definition computation equals
output of KillPerson with {
-- fateful_date: |2025-01-20|
-- victim: test_person2
}
Solution to Question 2
Solution to Question 2
scope KillPerson:
definition killed_individual equals
Individual {
-- date_of_birth: victim.date_of_birth
-- date_of_death: Dead content kill_date
}
You can also use the replace
syntax to modify specific fields of a
structure. It allows you to only modify specific fields which can be
useful especially when a structure defines a lot of fields!
scope KillPerson:
definition killed_individual equals
victim but replace { -- date_of_death: Dead content kill_date }
We now define a Couple
structure that represent a simple
household. This structure has three different entries:
- Two individuals:
person_1
andperson_2
; - And, their combined
household_yearly_income
.
declaration structure Couple:
data person_1 content Individual
data person_2 content Individual
data household_yearly_income content money
Question 3
Again, define a test value named test_couple
that reuses the
previously defined individuals (namely test_person_1
and
test_person_2
) and sets their house_yearly_income
to $80,000
.
Solution to Question 3
Solution to Question 3
declaration test_couple content Couple equals
Couple {
-- household_yearly_income: $80,000
-- person_1: test_person1
-- person_2: test_person2
}
Let's now consider a tax computation law article with a very simple definition:
The income tax for a couple's household is defined as 15% of their yearly income unless one (or both) of the individuals are deceased prior to the file's processing date.
This translates to the following Catala code:
declaration scope TaxComputation:
input processing_date content date
input couple content Couple
internal person1_dead_before_processing_date content boolean
internal person2_dead_before_processing_date content boolean
output tax_amount content money
scope TaxComputation:
definition tax_amount equals
if person1_dead_before_processing_date
or person2_dead_before_processing_date then
$0
else
couple.household_yearly_income * 15%
In order to trivialise the definition of tax_amount
, we introduced
two internal
scope variables that represent boolean values (true
or false
). However, even if we provided the definition of the output
variable tax_amount
, these internal variables are yet to be defined
otherwise we won't be able to effectively do the computation.
Question 4
In Catala, scope definitions can be scattered over the whole file. In doing so, it allows to locally implement the logic defined by a law article without introducing boilerplate that would hinder a reviewing process.
Define another TaxComputation
scope definition that defines both
internal boolean variables person1_dead_before_processing_date
and
person2_dead_before_processing_date
.
In order to decompose and reason on enumeration values, one can use
the pattern-matching construction. For example, pattern-matching a DateOfDeath
enumeration looks like this:
definition individual_age equals
match individual.date_of_death with
-- StillAlive: current_date - individual.date_of_birth
-- Deceased of deceased_date: deceased_date - individual.date_of_birth
Nota bene: all the different branches of a pattern-matching must contain same data type expressions.
Solution to Question 4
Solution to Question 4
scope TaxComputation:
definition person1_dead_before_processing_date equals
match couple.person_1.date_of_death with pattern
-- StillAlive: false
-- Deceased of d: d < processing_date
definition person2_dead_before_processing_date equals
# Another possible syntax for testing patterns
couple.person_1.date_of_death with pattern
Deceased of d and d < processing_date
Question 5
Lastly, we need to define a test for our computation. Based on the
previously defined tests, write a definition of the
TestTaxComputation
test scope. Then, tweak the given test values to
make sure your implementation is correct.
declaration scope TestTaxComputation:
output test_tax_computation content TaxComputation
Same as before, you can use a similar command to execute your test:
catala interpret exercise-2-1-1.catala_en --scope TestTaxComputation
Solution to Question 5
Solution to Question 5
scope TestTaxComputation:
definition test_tax_computation equals
output of TaxComputation with {
# Processing date is a week before test_person1's death:
-- processing_date: |2012-05-01|
# Processing date is a week before test_person2's death:
# -- processing_date: |2012-05-20|
-- couple: test_couple
}
Conditional definitions and exceptions
In this section, the tutorial introduces the killer feature of Catala when it comes to coding the law: exceptions in the definitions of variables. By the end of the section, you should understand the behavior of computations involving exceptions, and be able to structure groups of variable definitions according to their exceptional status and relative priority.
Recap of the previous section
Recap of the previous section
This section of the tutorial builds up on the previous one, and will reuse the same running example, but all the Catala code necessary to execute the example is included below for reference.
```catala
declaration structure Individual:
data income content money
data number_of_children content integer
declaration scope IncomeTaxComputation:
input individual content Individual
internal tax_rate content decimal
output income_tax content money
```
## Article 1
The income tax for an individual is defined as a fixed percentage of the
individual's income over a year.
```catala
scope IncomeTaxComputation:
definition income_tax equals
individual.income * tax_rate
```
## Article 2
The fixed percentage mentioned at article 1 is equal to 20 %.
```catala
scope IncomeTaxComputation:
label article_2
definition tax_rate equals 20%
```
## Test
```catala
declaration scope Test:
output computation content IncomeTaxComputation
scope Test:
definition computation equals
output of IncomeTaxComputation with {
-- individual:
Individual {
-- income: $20,000
-- number_of_children: 0
}
}
```
Conditional definitions and exceptions
Specifications coming from legal text do not always neatly divide up each variable definition into its own article. Sometimes, and this is a very common pattern, a later article redefines a variable already defined previously, but with a twist in a certain exceptional situation. For instance, article 3 of CTTC:
If the individual is in charge of 2 or more children, then the fixed percentage mentioned at article 1 is equal to 15 %.
This article actually gives another definition for the fixed percentage, which
was already defined in article 2. However, article 3 defines the percentage
conditionally to the individual having more than 2 children. How to redefine
tax_rate
? Catala allows you precisely to redefine a variable under a
condition with the under condition ... consequence
syntax between the name
of the variable being defined and the equals
keyword:
scope IncomeTaxComputation:
definition tax_rate under condition
individual.number_of_children >= 2
consequence equals 15 %
What does this mean? If the individual has more than two children, then
tax_rate
will be 15 %
. Conditional definitions let you define
your variables piecewise, one case at a time; the Catala compiler stitches
everything together for execution. More precisely, at runtime, we look at
the conditions of all piecewise definitions for a same variable, and pick
the one that is valid.
To test what happens when the rules for articles 2 and 3 are at play,
you can test the program when there are three children, by tweaking the
Test
scope as follows:
scope Test:
definition computation equals
output of IncomeTaxComputation with {
-- individual:
Individual {
-- income: $20,000
-- number_of_children: 3
}
}
During the above test, two definitions for tax_rate
are valid at the
same time. What happens then? In these cases, Catala will abort
execution and return an error message like the one below:
$ catala interpret tutorial.catala_en --scope=Test
┌─[ERROR]─
│
│ During evaluation: conflict between multiple valid consequences for assigning the same variable.
│
├─➤ tutorial_en.catala_en
│ │
│ │ definition tax_rate equals 20 %
│ │ ‾‾‾‾
├─ Article 2
│
├─➤ tutorial_en.catala_en
│ │
│ │ consequence equals 15 %
│ │ ‾‾‾‾
└─ Article 3
If the specification is correctly drafted, then these error situations should
not happen, as one and only one conditional definition should be valid at all
times. Here, however, our definition of tax_rate
conflicts with the
more general definition that we gave above. To correctly model situations like
this, Catala allows us to define precedence of one conditional definitions
over another. It is as simple as adding exception
before the definition.
For instance, here is a more correct version of the code for article 3:
If the individual is in charge of 2 or more children, then the fixed percentage mentioned at article 1 is equal to 15 %.
scope IncomeTaxComputation:
exception definition tax_rate under condition
individual.number_of_children >= 2
consequence equals 15 %
With exception
, the conditional definition at article 3 will be picked over
the base case at article 1 when the individual has two children or more. This
exception
mechanism is modeled on the logic of legal drafting: it is the key
mechanism that lets us split our variables definition to match the structure of
the specification. Without exception
, it is not possible to use the literate
programming style. This is precisely why writing and maintaining computer
programs for taxes or social benefits is very difficult with mainstream
programming languages. So, go ahead and use exception
as much as possible,
since it is a very idiomatic Catala concept.
When defining exceptions in your Catala code, it is importand to understand precisely their underlying semantics, i.e. what will be the end result of the computation. The semantics of Catala are formally defined and based on prioritized default logic, which translates intuitively to the following algorithm describing how to compute exceptions:
- Gather all definitions (conditional or not) for a given variable;
- Among these definitions, check all that apply (whose conditions evaluate to
true
):- If no definition apply, the program crashes with an error ("no definitions apply");
- If only one definition applies, then pick it and continue the execution of the program;
- If multiple definitions apply, then check their priorities:
- If there exists one definition that is an exception to all the others that apply, pick it and continue the execution of the program;
- Otherwise, the program crashes with an error ("conflicting definitions").
As described above, putting exception
in a Catala program alters the behavior
of the program, by providing a priority between conditional definitions of a
variable that Catala can use at execution time when hesitating between multiple
definitions that apply at the same time. So far, we have seen a very simple
situation with one base definition (in article 2) and a single exception (in
article 3). But the exception
mechanism can be much broader and help set
different priority lines among dozens of different conditional definitions for a
same variable. Let us explore this mechanism on a more complex example.
Dealing with multiple exceptions
It is frequent in legal text that an article setting up a general rule is followed by multiple articles defining exceptions to the base rule. Additionally to article 3 and the reduced tax rate for large families, the CTTC (Catala Tutorial Tax Code) indeed defines a tax exemption for low-income individuals, that we can encode as another exception to the definition of the tax rate in article 2:
Individuals earning less than $10,000 are exempted of the income tax mentioned at article 1.
scope IncomeTaxComputation:
exception definition tax_rate under condition
individual.income <= $10,000
consequence equals 0 %
But then, what happens when testing the code with an individual that earns less than $10,000 and has more than 2 children?
To test what happens when the rules for articles 3 and 4 are at play,
you can test the program when there are three children, and an income
lower than $10,000 by tweaking the Test
scope as follows:
scope Test:
definition computation equals
output of IncomeTaxComputation with {
-- individual:
Individual {
-- income: $5,000
-- number_of_children: 3
}
}
The program execution yields the following error at runtime:
$ catala interpret tutorial.catala_en --scope=Test
┌─[ERROR]─
│
│ During evaluation: conflict between multiple valid consequences for assigning the same variable.
│
├─➤ tutorial_en.catala_en
│ │
│ │ consequence equals 15 %
│ │ ‾‾‾‾
├─ Article 3
│
├─➤ tutorial_en.catala_en
│ │
│ │ consequence equals 0 %
│ │ ‾‾‾
└─ Article 4
In this situation, both conditional definitions from article 3 and article 4 apply, but also the base definition from article 2. We know article 3 and article 4 are exceptions to article 2, hence they both have priority over it. But we don't know which definition has priority between article 3 and article 4, hence the error message above!
In this situation, we need to prioritize the exceptions between each other.
The prioritization of exceptions requires legal expertise and research, as it is not always obvious which exception should prevail in any given situation. Hence, Catala error messages indicating a conflict during evaluation are an invitation to call the lawyer in your team and have them interpret the specification, rather than fixing the conflict yourself.
Here, because article 4 follows article 3, and because it is more favorable to
the taxpayer to pay $0 in tax rather than 15 % of their income, we can make the
legal decision to prioritize the exception of article 4 over the exception of
article 3. Now, let us see how to write that with Catala. Because article 2 is
the base case for the exception of article 3, and article 3 is the base case for
the exception of article 4, we need to give the definitions of tax_rate
at
articles 2 and 3 an explicit label
so that the exception
keywords in article
3 and 4 can refer to those labels:
Article 2
The fixed percentage mentioned at article 1 is equal to 20 %.
scope IncomeTaxComputation:
# The keyword "label" introduces the name of the label itself, here
# "article_2".
label article_2 definition tax_rate equals 20 %
Article 3
If the individual is in charge of 2 or more children, then the fixed percentage mentioned at article 1 is equal to 15 %.
scope IncomeTaxComputation:
# This definition is preceded by two indications:
# * it has its own label, "article_3";
# * this definition is an exception to the definition labeled "article_2".
label article_3 exception article_2
definition tax_rate under condition
individual.number_of_children >= 2
consequence equals 15 %
Article 4
Individuals earning less than $10,000 are exempted of the income tax mentioned at article 1.
scope IncomeTaxComputation:
label article_4 exception article_3
definition tax_rate under condition
individual.income <= $10,000
consequence equals 0 %
Thanks to labels, we can define chains of exceptions, where each definition is the exception to the previous one, and the base case for the next one. This pattern is the most usual in legal texts, and its behavior is straightfoward: when multiple definitions apply, pick the one with the highest priority in the chain. Here's a representation of the exception chain in our example so far:
But sometimes, it's not possible to arrange exceptions in a chain, since legal interpretation leads to different branches of exceptions.
Branches of exceptions
It may be difficult to see why some legal situations may lead to different branches of exceptions. Let us provide an example with a new article of the CTTC:
Individuals earning more than $100,000 are subjects to a tax rate of 30%, regardless of their number of children.
scope IncomeTaxComputation:
label article_5 exception article_3
definition tax_rate under condition
individual.income > $100,000
consequence equals 30 %
Now, article 3 has two exceptions : article 4, and article 5. These two exceptions have the following conditions:
- Article 4: income less than $10,000 ;
- Article 5: income more than $100,000.
Displaying the exception branches
Displaying the exception branches
As the codebase grows, it becomes more and more difficult to visualized all the conditional definitions of a variable as well as the prioritization between them. To help, the Catala compiler can display the exception branches with the following command:
$ catala exceptions tutorial.catala_en --scope=IncomeTaxComputation --variable=tax_rate
┌[RESULT]─
│ Printing the tree of exceptions for the definitions of variable "tax_rate" of scope "IncomeTaxComputation".
└─
┌─[RESULT]─
│ Definitions with label "article_2":
│
├─➤ tutorial.catala_en
│ │
│ │ label article_2 definition tax_rate equals 20 %
│ │ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
└─ Title
└─ Article 2
┌─[RESULT]─
│ Definitions with label "article_3":
│
├─➤ tutorial.catala_en
│ │
│ │ label article_3 exception article_2 definition tax_rate under condition
│ │ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
└─ Title
└─ Article 3
┌─[RESULT]─
│ Definitions with label "article_4":
│
├─➤ tutorial.catala_en
│ │
│ │ label article_4 exception article_3 definition tax_rate under condition
│ │ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
└─ Title
└─ Article 4
┌─[RESULT]─
│ Definitions with label "article_5":
│
├─➤ tutorial.catala_en
│ │
│ │ label article_5 exception article_3 definition tax_rate under condition
│ │ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
└─ Title
└─ Article 5
┌─[RESULT]─
│ The exception tree structure is as follows:
│
│ "article_2"───"article_3"──┬──"article_5"
│ │
│ └──"article_4"
└─
Theoretically, since the exceptions of article 4 and article 5 are not prioritized with each other, they could both apply at the same time and conflict. However, since the income cannot be both less than $10,000 and greater than $100,000, the conflict cannot happen in practice. Hence, it is not necessary to prioritize the two exceptions, since they live in mutually exclusive conditional branches.
To test what happens when the rule for article 5 is at play,
you can test the program when there are 3 children, and an income
greater than $100,000 by tweaking the Test
scope as follows:
scope Test:
definition computation equals
output of IncomeTaxComputation with {
-- individual:
Individual {
-- income: $200,000
-- number_of_children: 3
}
}
The result of the execution is then:
$ catala interpret tutorial.catala_en --scope=Test
┌─[RESULT]─
│ computation = IncomeTaxComputation { -- income_tax: $60,000.00 }
└─
The 30% rate is respected, since $200,000 x 30% = $60,000.
It is then possible to extend these branches separately, for instance with a new article of the CTTC:
In the overseas territories, the tax rate for individuals earning more than $100,000 specified at article 5 is reduced to 25 %.
This article introduces a new bit of information about the tax computation:
are we in an overseas territory or not? We can model it with a new input to the
scope IncomeTaxComputation
, leading to a revised scope declaration:
declaration scope IncomeTaxComputation:
input individual content Individual
input overseas_territories content boolean
internal tax_rate content decimal
output income_tax content money
With this new input variable, the code for article 6 is as follows:
scope IncomeTaxComputation:
label article_6 exception article_5
definition tax_rate under condition
individual.income > $100,000 and overseas_territories
consequence equals 25 %
Note that in the condition for defining tax_rate
in article 6, we
have repeated the condition individual.income > $100,000
in conjunction
with the new clause overseas_territories
. In our fictional CTTC, the text
of article 6 is gently worded and explicitly reminds us that this exception
to article 5 only applies in the situations that also trigger article 5 (where
the income is greater than $100,000).
However, the legal text can sometimes omit this key information, or make it implicit, creating a danger of putting the wrong conditional in the Catala code in presence of exception branches. Suppose we had omitted the income condition in the code for article 6:
scope IncomeTaxComputation:
label article_6 exception article_5
definition tax_rate under condition
overseas_territories
consequence equals 25 %
With this code, the article 6 definition would have applied in overseas territories for all individuals, including those earning less than $100,000! Indeed, exceptional definitions in Catala do not inherit the conditions of their base case: the condition of article 6 does not inherit the condition of article 5, we need to repeat it in article 6 if we want to have the correct activation pattern.
Finally, we can recap the collection of exception branches as a tree of exceptions for our example:
Grouping conditional definitions together for exceptions
So far, we have seen how to define exception chains and mutually exclusive exception branches. But there is a very common pattern that introduces yet another exceptional shenanigan. Suppose than in the year 2000, a big tax reform changes the base taxation rate of article 2 with a slight increase:
Now, there are several strategies to deal with legal updates in Catala, that
are summed up in the how to section of this book. But here,
we'll suppose that we want both versions of the law (before and after 2000)
to coexist in the same Catala program. This choice leads us to introduce the
current date as a new input of the scope IncomeTaxComputation
:
declaration scope IncomeTaxComputation:
input current_date content date
input individual content Individual
input overseas_territories content boolean
internal tax_rate content decimal
output income_tax content money
This current_date
variable will allow us to introduce mutually exclusive
conditional definitions for the two different verions of article 2, each one
activating only before of after the year 2000. Note that is the two definitions
of article 2 were not mutually exclusive, they could conflict with each other,
forcing you to prioritize between them and change the shape of the overall
exception tree by introducing another layer of exception. However, we want in
this cas those two base definitions of article 2 to collectively be the base
case for all subsequent exceptions in the exception tree of tax_rate
! In a
nutshell, we want the following exception tree:
Catala is able to represent this exception tree, by grouping together
the two conditional definitions to article 2. Indeed, since article 3
is an exception to the label article_2
, it suffices to give the same label
article_2
to the two conditional definitions of the two versions of article_2
:
Article 2 (old version before 2000)
The fixed percentage mentioned at article 1 is equal to 20 %.
scope IncomeTaxComputation:
label article_2 definition tax_rate under condition
current_date < |2000-01-01|
consequence equals 20 %
Article 2 (new version after 2000)
The fixed percentage mentioned at article 1 is equal to 21 % %.
scope IncomeTaxComputation:
# Simply use the same label "article_2" as the previous definition to group
# them together
label article_2 definition tax_rate under condition
current_date >= |2000-01-01|
consequence equals 21 %
By using the definition grouping mechanism along with exception branches, Catala is able to express a wide range of legal text logic, and helps keeping the code alongside its specification.
Checkpoint
This concludes the second section of the tutorial. In Catala, variables can be
defined piece-wise, each piece of definition being activated by a condition. When
multiple conditions apply, one can prioritize the conditional definitions using
the exception
and label
keywords to form exception trees able to capture the
complex logic behind the legal texts while matching their structure.
Recap of the current section
Recap of the current section
For reference, here is the final version of the Catala code consolidated at the end of this section of the tutorial.
```catala
declaration structure Individual:
data income content money
data number_of_children content integer
declaration scope IncomeTaxComputation:
input current_date content date
input individual content Individual
input overseas_territories content boolean
internal tax_rate content decimal
output income_tax content money
```
## Article 1
The income tax for an individual is defined as a fixed percentage of the
individual's income over a year.
```catala
scope IncomeTaxComputation:
definition income_tax equals
individual.income * tax_rate
```
## Article 2 (old version before 2000)
The fixed percentage mentioned at article 1 is equal to 20 %.
```catala
scope IncomeTaxComputation:
label article_2
definition tax_rate under condition
current_date < |2000-01-01|
consequence equals 20%
```
## Article 2 (new version after 2000)
The fixed percentage mentioned at article 1 is equal to 21 % %.
```catala
scope IncomeTaxComputation:
# Simply use the same label "article_2" as the previous definition to group
# them together
label article_2
definition tax_rate under condition
current_date >= |2000-01-01|
consequence equals 21%
```
## Article 3
If the individual is in charge of 2 or more children, then the fixed
percentage mentioned at article 1 is equal to 15 %.
```catala
scope IncomeTaxComputation:
label article_3 exception article_2
definition tax_rate under condition
individual.number_of_children >= 2
consequence equals 15%
```
## Article 4
Individuals earning less than $10,000 are exempted of the income tax mentioned
at article 1.
```catala
scope IncomeTaxComputation:
label article_4 exception article_3
definition tax_rate under condition
individual.income <= $10,000
consequence equals 0%
```
## Article 5
Individuals earning more than $100,000 are subjects to a tax rate of
30%, regardless of their number of children.
```catala
scope IncomeTaxComputation:
label article_5 exception article_3
definition tax_rate under condition
individual.income > $100,000
consequence equals 30%
```
## Article 6
In the overseas territories, the tax rate for individuals earning
more than $100,000 specified at article 5 is reduced to 25 %.
```catala
scope IncomeTaxComputation:
label article_6 exception article_5
definition tax_rate under condition
individual.income > $100,000 and overseas_territories
consequence equals 25%
```
## Test
```catala
declaration scope Test:
output computation content IncomeTaxComputation
scope Test:
definition computation equals
output of IncomeTaxComputation with {
-- individual:
Individual {
-- income: $20,000
-- number_of_children: 0
}
-- overseas_territories: false
-- current_date: |1999-01-01|
}
```
Lists and scopes
In this section, the tutorial tackles a common pattern that significantly increases the complexity of a codebase: the need to deal with lists and rules applying to each element of the list. Here, we learn how to perform operations with lists and declaring a new scope to deal with rules applying to each element of the list.
The last two section of the tutorial are quite challenging and involves some complexity in the example. This complexity is necessary to illustrate how the features of Catala scale to real-world legal texts that involve complex features. We encourage the reader to persevere in their study of these sections, and to ask any question on the online Catala community chat.
Recap of the previous section
Recap of the previous section
This section of the tutorial builds up on the previous one, and will reuse the same running example, but all the Catala code necessary to execute the example is included below for reference.
```catala
declaration structure Individual:
data income content money
data number_of_children content integer
declaration scope IncomeTaxComputation:
input current_date content date
input individual content Individual
input overseas_territories content boolean
internal tax_rate content decimal
output income_tax content money
```
## Article 1
The income tax for an individual is defined as a fixed percentage of the
individual's income over a year.
```catala
scope IncomeTaxComputation:
definition income_tax equals
individual.income * tax_rate
```
## Article 2 (old version before 2000)
The fixed percentage mentioned at article 1 is equal to 20 %.
```catala
scope IncomeTaxComputation:
label article_2
definition tax_rate under condition
current_date < |2000-01-01|
consequence equals 20%
```
## Article 2 (new version after 2000)
The fixed percentage mentioned at article 1 is equal to 21 % %.
```catala
scope IncomeTaxComputation:
# Simply use the same label "article_2" as the previous definition to group
# them together
label article_2
definition tax_rate under condition
current_date >= |2000-01-01|
consequence equals 21%
```
## Article 3
If the individual is in charge of 2 or more children, then the fixed
percentage mentioned at article 1 is equal to 15 %.
```catala
scope IncomeTaxComputation:
label article_3 exception article_2
definition tax_rate under condition
individual.number_of_children >= 2
consequence equals 15%
```
## Article 4
Individuals earning less than $10,000 are exempted of the income tax mentioned
at article 1.
```catala
scope IncomeTaxComputation:
label article_4 exception article_3
definition tax_rate under condition
individual.income <= $10,000
consequence equals 0%
```
## Article 5
Individuals earning more than $100,000 are subjects to a tax rate of
30%, regardless of their number of children.
```catala
scope IncomeTaxComputation:
label article_5 exception article_3
definition tax_rate under condition
individual.income > $100,000
consequence equals 30%
```
## Article 6
In the overseas territories, the tax rate for individuals earning
more than $100,000 specified at article 5 is reduced to 25 %.
```catala
scope IncomeTaxComputation:
label article_6 exception article_5
definition tax_rate under condition
individual.income > $100,000 and overseas_territories
consequence equals 25%
```
## Test
```catala
declaration scope Test:
output computation content IncomeTaxComputation
scope Test:
definition computation equals
output of IncomeTaxComputation with {
-- individual:
Individual {
-- income: $20,000
-- number_of_children: 0
}
-- overseas_territories: false
-- current_date: |1999-01-01|
}
```
Making a household from a list of invidivuals
Previously, the Catala Tutorial Tax Code (CTTC) has defined an income tax for each individual and their children. But now, the CTTC is becoming greedier as a new, separate tax similar to Thatcher's infamous poll tax. At its inception, the household tax is such that each individual in a household is taxed a fixed sum, with a reduced rate for the children:
When several individuals live together, they are collectively subject to the household tax. The household tax owed is $10,000 per individual of the household, and half the amount per children.
Now, implementing this in Catala requires going beyond the
IncomeTaxComputation
scope that we used earlier. Indeed, this new tax requires
a new scope, HouseholdTaxComputation
! While it is fairly evident that the
output
of this new scope should be the household_tax
, its input
is the
collection of individuals that make up the household.
Fortunately, Catala has a built-in type for collection of things, called list
,
even though it behaves more like an array in traditionnal Computer Science
jargon.
declaration scope HouseholdTaxComputation:
# The syntax "list of <X>" designates the type whose values are lists of
# elements with type <X>.
input individuals content list of Individual
output household_tax content money
To define household_tax
, we must now:
- count the number of individuals in
individuals
; - count the number of children in each individual and add these counts together;
- multiply these counts by the right amount of tax.
We will perform each one of these steps in the body of the definition
of
household_tax
, in the scope HouseholdTaxComputation
, using local
variables.
When a variable definition gets complex like above, it is often useful to separate each step by defining intermediate variables. There are two ways of doing that.
First, you can declare inside the scope declaration an extra scope variable
with the label internal
instead of input
or output
, as seen in
the first section of the tutorial.
Second, if you are confident that you will only need the intermediate variable in the narrow context of a single scope variable definition, you can use a local variable inside the definition of the scope variable. These local variables are introduced and used with the following syntax:
# The following line defines local variable "x" as begin equal to 4 * 5
let x equals 4 * 5 in
# We can then use "x" after the "in" keyword in the rest of the code
x + 2
For step 1, we simply need to get the length of the list individuals
, which
can be done through the syntax number of individuals
(the syntax for all list
operations can be found in the syntax sheat cheet
or in the language reference). For step 2, we
need to aggregate the number of children for all individuals, which can be done
through the syntax sum integer of map each individual among individuals to individual.number_of_children
. Notice the type indication (integer
) for the sum
, which
indicates that if the list of individuals is empty, then the integer 0
should
be returned. Finally, we can piece steps 1 and 2 for the step 3 which computes
the amount of tax:
scope HouseholdTaxComputation:
definition household_tax equals
let number_of_individuals equals number of individuals in
let number_of_children equals
sum integer of
map each individual among individuals to individual.number_of_children
in
$10,000
* (
# "number_of_individuals" is an integer, but money can only be multiplied
# by decimals: we need to explicitly cast before using the value
decimal of number_of_individuals
+ decimal of number_of_children / 2.0
)
This implementation of article 7 is fairly direct and concise. It does the job, but notice a subtle shift between the text of article 7 and its Catala implementation: rather than aggregating separately the contribution of each individual and their children to the household tax, we count all individuals on one side, and all children on the other side. Addition is commutative and associative so this shift yields the same result. However, not following the spirit of the law in the implementation might not be future-proof, as we'll see just below...
To test what happens when the rule for article 7 is at play, you can test the program with a household:
declaration scope TestHousehold:
output computation content HouseholdTaxComputation
scope TestHousehold:
definition computation equals
output of HouseholdTaxComputation with {
-- individuals:
# The scope expects a list of individuals. In Catala, a list is built
# with the syntax "[<element 1>; <element 2>; ...]".
[ Individual {
-- income: $15,000
-- number_of_children: 0
} ;
Individual {
-- income: $80,000
-- number_of_children: 2
} ]
}
The result of the execution is then:
$ catala interpret tutorial.catala_en --scope=TestHousehold
┌─[RESULT]─
│ computation = HouseholdTaxComputation { -- household_tax: $30,000.00 }
└─
We have two individuals and two children so 2 x $10,000 + 2 x $5,000 = $30,000.
Refactoring to account for evolving requirements
Translating legal texts into executable code is often an emotional rollercoaster, as new requirements in later articles may completely break the invariants and structure of the implementation you used in earlier articles. Today, the Catala Tutorial Tax Code (CTTC) will be harsh on us, with the following fateful article:
The amount of income tax paid by each individual can be deducted from the share of household tax owed by this individual.
Now, there are several strategies to implement article 8, but not all are legally correct. One strategy could be to compute the total amount of income tax owed by all the individuals in the household, and substract that total amount of income tax from the the totam amount of household tax to perform the deduction. However, this strategy is incorrect, because the household tax deduction for one individual is implicitly capped by the amount of household tax due for this individual! This capping introduces a non-linearity in the formula that prevents rearranging the additions and substractions while keeping the same results in all configurations.
Suppose you have two individuals, A
and B
, with no children, in a household
that is not located in a overseas territory, before the year 2000.
Suppose also that A
's income is $20,000, while B
's income is $200,000.
According to articles 1 to 6, A
will pay $20,000 x 20% = $4,000 in income
tax, while B
will pay $200,000 x 30% = $60,000. So, in total, the household
pays $64,000 of income tax.
On the other hand, the household tax due by this household is $10,000 + $10,000 = $20,000. How to apply article 8 in this situation? Naively substracting the total income tax ($64,000) from the total household tax ($20,000) yields a revised household tax of $0, but this is not the legal amount. Indeed, the deduction can only occur at the individual level.
A
can deduct $4,000 from their $10,000 share of household tax, so they own
$6,000 in household tax. But B
can only deduct $10,000 from their $10,000
share of household tax, leaving them with with $0 household tax to pay, but
not -$50,000! This is the non-linearity in action.
So in total, the correct total amount of household tax to pay here is $6,000 and not $0 as the bulk substraction method computed.
So, we are stuck with explicitly decomposing the household tax computation into
two steps: first, computing the share of household tax owed by each individual,
and then aggregating the result of the first step for all individuals of the
household. Naturally, the existing scope HouseholdTaxComputation
is where the
second step will happen. But where to put the first step? Refactoring is needed!
Yes it is!
Theoretically, as Catala lets you structure the code by matching the structure of the legal text, adding new articles should not require changes in earlier blocks of code. This is the case for instance when a new article defines an exception to the base case of a variable, as we've experimented in the second section of the tutorial.
But adding exceptions is not the only things new articles can introduce. In this case, we see that article 8 makes explicit a computation step that was implicit or hidden in article 7 (namely, the computation of the share of household tax for each individual). Making this computation step explicit implies giving it a first-class status with a Catala concept (a variable, a scope, etc.), which may not have been the case in the Catala code written before. Hence, it is normal to refactor earlier code to code up the new article 8.
However, the goal of the refactoring is always to match up as precisely as possible the computation steps and the articles they are based on.
As it already happened for article 8, subsequent articles are likely to introduce refinements and exceptions for this share of household tax. In this case, it is preferable to use a fully-fledged scope to represent this extra computation step. The scope is readable by lawyers and has better convenient features to add input and output parameters, define exceptions for its local variables, etc.
Hence, we will d opt for creating a brand new scope for computing the share of
household tax owed by an individual, HouseholdTaxIndividualComputation
.
The missing scope : household tax computation for the individual
The new scope, HouseholdTaxIndividualComputation
, will have as input one
individual and return as a result the amount of household tax held. However,
because of article 8, the scope will also need to compute the amount of income
tax owed by the individual, to deduct it from the household tax. The call
graph between scopes will then be the following:
Hence, we will also need as input of HouseholdTaxIndividualComputation
the
inputs necessary for the IncomeTaxComputation
scope of the previous section
of the tutorial: overseas_territory
and
current_date
This gives the following scope declaration:
declaration scope HouseholdTaxIndividualComputation:
input individual content Individual
input overseas_territories content boolean
input current_date content date
output household_tax content money
Now, we know that we'll need to call IncomeTaxComputation
exactly one time to
compute the deduction for household_tax
. There is a bespoke method designed
for lawyer-readbility to do exactly that in Catala!
# The single, static sub-scope call to "IncomeTaxComputation" has to be
# declared in "HouseholdTaxIndividualComputation", so we repeat the
# scope declaration here with a new line.
declaration scope HouseholdTaxIndividualComputation:
input individual content Individual
input overseas_territories content boolean
input current_date content date
# The following line declares a static, single call to the sub-scope
# "IncomeTaxComputation" with the name "income_tax_computation".
income_tax_computation scope IncomeTaxComputation
output household_tax content money
scope HouseholdTaxIndividualComputation:
# Inside a "scope" block, we have to define the arguments to the sub-scope
# call "income_tax_computation": "individual", "overseas_territories" and
# "overseas_territories".
definition income_tax_computation.individual equals
individual
# The "individual" given as an argument to "income_tax_computation",
# which is the call to "IncomeTaxComputation", is the same "individual"
# that is the input to "HouseholdTaxIndividualComputation".
definition income_tax_computation.overseas_territories equals
overseas_territories
# These lines can appear tautological but they are essential for plugging
# scopes to sub-scopes in an non-ambiguous way. It is implicit that we evaluate
# the income tax for deduction at the same date as we evaluate the amount of
# household tax, but this line makes it explicit. Sometimes, you might want
# to call the income tax computation at an earlier date (like "current_date
# - 5 year") because of a legal requirement, and this is where you specify
# this!
definition income_tax_computation.current_date equals
current_date
That's it, the sub-scope call has been completely set up! The result
is now accessible at income_tax_computation.income_tax
, since
"income_tax" is the output variable of the sub-scope IncomeTaxComputation
.
At this point, it is be easy to define household_tax
in a single sweep
inside HouseholdTaxIndividualComputation
:
scope HouseholdTaxIndividualComputation:
definition household_tax equals
let tax equals
$10,000 * (1.0 + decimal of individual.number_of_children / 2.0)
in
let deduction equals income_tax_computation.income_tax in
# Don't forget to cap the deduction!
if deduction > tax then $0 else tax - deduction
To test what happens when the rule for articles 7 and 8 are at play you can test the program with an individual:
declaration scope TestIndividualHousehold:
output computation content HouseholdTaxIndividualComputation
scope TestIndividualHousehold:
definition computation equals
output of HouseholdTaxIndividualComputation with {
-- individual:
Individual {
-- income: $15,000
-- number_of_children: 0
}
-- current_date: |1999-01-01|
-- overseas_territories: false
}
The result of the execution is then:
$ catala interpret tutorial.catala_en --scope=TestIndividualHousehold
┌─[RESULT]─
│ computation = HouseholdTaxIndividualComputation { -- household_tax: $7,000.00 }
└─
The household tax owed by one individual with no children is $10,000, but we must deduct their income tax. With an income of $15,000, before 2000 and not in an overseas territory, the income tax rate is 20% according to Article 2, hence $3,000 of income tax. Therefore, the correct household tax owed with deduction is $7,000.
Conclusion
In this section of the tutorial, we have seen that in Catala, lists of items
are represented as values with their own type like list of money
or
list of Individual
. You can manipulate the list values with list operators
like lenght count, aggregation, but also map, filter, map, etc. Please refer
to the language reference for information about all the
list operators available in Catala. Furthermore, we have also seen in this
section of the tutorial that rather than programming all the rules for dealing
with items in lists inside the list operators, it is preferable to create
a new scope to write all the rules that apply to the items in the list. This has
allowed us to see how scopes can call each other and allow for a modular
codebase that can be refactored to account for evolving legal requirements.
However, right now our implementation of articles 7 and 8 is not complete, as
we're missing the step where HouseholdTaxComputation
calls
HouseholdTaxInvidualComputation
on each individual in the household to
complete the household tax computation with the correct deduction. This will be
the topic of the next and final section of the
tutorial.
Recap of the current section
Recap of the current section
For reference, here is the final version of the Catala code consolidated at the end of this section of the tutorial.
```catala
declaration structure Individual:
data income content money
data number_of_children content integer
declaration scope IncomeTaxComputation:
input current_date content date
input individual content Individual
input overseas_territories content boolean
internal tax_rate content decimal
output income_tax content money
```
## Article 1
The income tax for an individual is defined as a fixed percentage of the
individual's income over a year.
```catala
scope IncomeTaxComputation:
definition income_tax equals
individual.income * tax_rate
```
## Article 2 (old version before 2000)
The fixed percentage mentioned at article 1 is equal to 20 %.
```catala
scope IncomeTaxComputation:
label article_2
definition tax_rate under condition
current_date < |2000-01-01|
consequence equals 20%
```
## Article 2 (new version after 2000)
The fixed percentage mentioned at article 1 is equal to 21 % %.
```catala
scope IncomeTaxComputation:
# Simply use the same label "article_2" as the previous definition to group
# them together
label article_2
definition tax_rate under condition
current_date >= |2000-01-01|
consequence equals 21%
```
## Article 3
If the individual is in charge of 2 or more children, then the fixed
percentage mentioned at article 1 is equal to 15 %.
```catala
scope IncomeTaxComputation:
label article_3 exception article_2
definition tax_rate under condition
individual.number_of_children >= 2
consequence equals 15%
```
## Article 4
Individuals earning less than $10,000 are exempted of the income tax mentioned
at article 1.
```catala
scope IncomeTaxComputation:
label article_4 exception article_3
definition tax_rate under condition
individual.income <= $10,000
consequence equals 0%
```
## Article 5
Individuals earning more than $100,000 are subjects to a tax rate of
30%, regardless of their number of children.
```catala
scope IncomeTaxComputation:
label article_5 exception article_3
definition tax_rate under condition
individual.income > $100,000
consequence equals 30%
```
## Article 6
In the overseas territories, the tax rate for individuals earning
more than $100,000 specified at article 5 is reduced to 25 %.
```catala
scope IncomeTaxComputation:
label article_6 exception article_5
definition tax_rate under condition
individual.income > $100,000 and overseas_territories
consequence equals 25%
```
## Article 7
When several individuals live together, they are collectively subject to
the household tax. The household tax owed is $10000 per individual of the household,
and half the amount per children.
```catala
declaration scope HouseholdTaxComputation:
input individuals content list of Individual
output household_tax content money
declaration scope HouseholdTaxIndividualComputation:
input individual content Individual
input overseas_territories content boolean
input current_date content date
income_tax_computation scope IncomeTaxComputation
output household_tax content money
```
```catala
# scope HouseholdTaxIndividualComputation:
# # This definition is disabled because of the new one in Article 8.
# definition household_tax equals
# $10000 * (1.0 + decimal of individual.number_of_children / 2.0)
# The definition of household tax for the whole household below is wrong and
# will be refactored in the next section.
scope HouseholdTaxComputation:
definition household_tax equals
let number_of_individuals equals number of individuals in
let number_of_children equals
sum integer of
map each individual among individuals
to individual.number_of_children
in
$10,000
* (
decimal of number_of_individuals
+ decimal of number_of_children / 2.0
)
```
## Article 8
The amount of income tax paid by each individual can be deducted from the
share of household tax owed by this individual.
```catala
scope HouseholdTaxIndividualComputation:
definition income_tax_computation.individual equals
individual
definition income_tax_computation.overseas_territories equals
overseas_territories
definition income_tax_computation.current_date equals
current_date
definition household_tax equals
let tax equals
$10,000 * (1.0 + decimal of individual.number_of_children / 2.0)
in
let deduction equals income_tax_computation.income_tax in
# Don't forget to cap the deduction!
if deduction > tax then $0 else tax - deduction
```
## Test
```catala
declaration scope Test:
output computation content IncomeTaxComputation
scope Test:
definition computation equals
output of IncomeTaxComputation with {
-- individual:
Individual {
-- income: $20,000
-- number_of_children: 0
}
-- overseas_territories: false
-- current_date: |1999-01-01|
}
declaration scope TestHousehold:
output computation content HouseholdTaxComputation
scope TestHousehold:
definition computation equals
output of HouseholdTaxComputation with {
-- individuals:
[ Individual {
-- income: $15,000
-- number_of_children: 0
} ;
Individual {
-- income: $80,000
-- number_of_children: 2
} ]
}
declaration scope TestIndividualHousehold:
output computation content HouseholdTaxIndividualComputation
scope TestIndividualHousehold:
definition computation equals
output of HouseholdTaxIndividualComputation with {
-- individual:
Individual {
-- income: $15,000
-- number_of_children: 0
}
-- current_date: |1999-01-01|
-- overseas_territories: false
}
```
Variable states and dynamic scope calls
Introduction
In this section, the tutorial picks up where the previous section left, that is to say implementing the computation of the household tax for one individual. Now, we still have to aggregate the computations for individuals at the household level to generate the whole household tax.
Doing this aggregation will require calling the scope HouseholdTaxInvidualComputation
multiple times for a list aggregation inside HouseholdTaxComputation
. We
will cover this topic, but first we have to wrap up unfinished business
from the last section of the tutorial!
Recap of the previous section
Recap of the previous section
This section of the tutorial builds up on the previous one, and will reuse the same running example, but all the Catala code necessary to execute the example is included below for reference.
```catala
declaration structure Individual:
data income content money
data number_of_children content integer
declaration scope IncomeTaxComputation:
input current_date content date
input individual content Individual
input overseas_territories content boolean
internal tax_rate content decimal
output income_tax content money
```
## Article 1
The income tax for an individual is defined as a fixed percentage of the
individual's income over a year.
```catala
scope IncomeTaxComputation:
definition income_tax equals
individual.income * tax_rate
```
## Article 2 (old version before 2000)
The fixed percentage mentioned at article 1 is equal to 20 %.
```catala
scope IncomeTaxComputation:
label article_2
definition tax_rate under condition
current_date < |2000-01-01|
consequence equals 20%
```
## Article 2 (new version after 2000)
The fixed percentage mentioned at article 1 is equal to 21 % %.
```catala
scope IncomeTaxComputation:
# Simply use the same label "article_2" as the previous definition to group
# them together
label article_2
definition tax_rate under condition
current_date >= |2000-01-01|
consequence equals 21%
```
## Article 3
If the individual is in charge of 2 or more children, then the fixed
percentage mentioned at article 1 is equal to 15 %.
```catala
scope IncomeTaxComputation:
label article_3 exception article_2
definition tax_rate under condition
individual.number_of_children >= 2
consequence equals 15%
```
## Article 4
Individuals earning less than $10,000 are exempted of the income tax mentioned
at article 1.
```catala
scope IncomeTaxComputation:
label article_4 exception article_3
definition tax_rate under condition
individual.income <= $10,000
consequence equals 0%
```
## Article 5
Individuals earning more than $100,000 are subjects to a tax rate of
30%, regardless of their number of children.
```catala
scope IncomeTaxComputation:
label article_5 exception article_3
definition tax_rate under condition
individual.income > $100,000
consequence equals 30%
```
## Article 6
In the overseas territories, the tax rate for individuals earning
more than $100,000 specified at article 5 is reduced to 25 %.
```catala
scope IncomeTaxComputation:
label article_6 exception article_5
definition tax_rate under condition
individual.income > $100,000 and overseas_territories
consequence equals 25%
```
## Article 7
When several individuals live together, they are collectively subject to
the household tax. The household tax owed is $10000 per individual of the household,
and half the amount per children.
```catala
declaration scope HouseholdTaxComputation:
input individuals content list of Individual
output household_tax content money
declaration scope HouseholdTaxIndividualComputation:
input individual content Individual
input overseas_territories content boolean
input current_date content date
income_tax_computation scope IncomeTaxComputation
output household_tax content money
```
```catala
# scope HouseholdTaxIndividualComputation:
# # This definition is disabled because of the new one in Article 8.
# definition household_tax equals
# $10000 * (1.0 + decimal of individual.number_of_children / 2.0)
# The definition of household tax for the whole household below is wrong and
# will be refactored in the next section.
scope HouseholdTaxComputation:
definition household_tax equals
let number_of_individuals equals number of individuals in
let number_of_children equals
sum integer of
map each individual among individuals
to individual.number_of_children
in
$10,000
* (
decimal of number_of_individuals
+ decimal of number_of_children / 2.0
)
```
## Article 8
The amount of income tax paid by each individual can be deducted from the
share of household tax owed by this individual.
```catala
scope HouseholdTaxIndividualComputation:
definition income_tax_computation.individual equals
individual
definition income_tax_computation.overseas_territories equals
overseas_territories
definition income_tax_computation.current_date equals
current_date
definition household_tax equals
let tax equals
$10,000 * (1.0 + decimal of individual.number_of_children / 2.0)
in
let deduction equals income_tax_computation.income_tax in
# Don't forget to cap the deduction!
if deduction > tax then $0 else tax - deduction
```
## Test
```catala
declaration scope Test:
output computation content IncomeTaxComputation
scope Test:
definition computation equals
output of IncomeTaxComputation with {
-- individual:
Individual {
-- income: $20,000
-- number_of_children: 0
}
-- overseas_territories: false
-- current_date: |1999-01-01|
}
declaration scope TestHousehold:
output computation content HouseholdTaxComputation
scope TestHousehold:
definition computation equals
output of HouseholdTaxComputation with {
-- individuals:
[ Individual {
-- income: $15,000
-- number_of_children: 0
} ;
Individual {
-- income: $80,000
-- number_of_children: 2
} ]
}
declaration scope TestIndividualHousehold:
output computation content HouseholdTaxIndividualComputation
scope TestIndividualHousehold:
definition computation equals
output of HouseholdTaxIndividualComputation with {
-- individual:
Individual {
-- income: $15,000
-- number_of_children: 0
}
-- current_date: |1999-01-01|
-- overseas_territories: false
}
```
Variable states
Recall that we have defined household_tax
in a single sweep
inside HouseholdTaxIndividualComputation
:
scope HouseholdTaxIndividualComputation:
definition household_tax equals
let tax equals
$10,000 * (1.0 + decimal of individual.number_of_children / 2.0)
in
let deduction equals income_tax_computation.income_tax in
# Don't forget to cap the deduction!
if deduction > tax then $0 else tax - deduction
However, doing so merges together the specifications of article 7 and article 8,
which goes against the spirit of Catala to split the code in the same structure
as the legal text. So, instead of using two local variables inside the definition
of household_tax
, we want to split the formula into two distinct definition
.
Intuitively, this implies creating two scope variables in
HouseholdTaxIndividualComputation
, household_tax_base
(for article 7) and
household_tax_with_deduction
(article 8). But really, this amounts to giving
two consecutive states for the variable household_tax
, and lawyers understand
the code better this way! So Catala has a feature to let you exactly that:
declaration scope HouseholdTaxIndividualComputation:
input individual content Individual
input overseas_territories content boolean
input current_date content date
income_tax_computation scope IncomeTaxComputation
output household_tax content money
# The different states for variable "household_tax" are declared here,
# in the exact order in which you expect them to be computed!
state base
state with_deduction
With our two states base
and with_deduction
, we can code up articles 7 and
8:
Article 7
When several individuals live together, they are collectively subject to the household tax. The household tax owed is $10,000 per individual of the household, and half the amount per children.
scope HouseholdTaxIndividualComputation:
definition household_tax state base equals
$10,000 * (1.0 + decimal of individual.number_of_children / 2.0)
Article 8
The amount of income tax paid by each individual can be deducted from the share of household tax owed by this individual.
scope HouseholdTaxIndividualComputation:
definition household_tax state with_deduction equals
# Below, "household_tax" refers to the value of "household_tax" computed
# in the previous state, so here the state "base" which immediately precedes
# the state "with_deduction" in the declaration.
if income_tax_computation.income_tax > household_tax then $0
else
household_tax - income_tax_computation.income_tax
# It is also possible to refer to variable states explicitely with the
# syntax "household_tax state base".
Elsewhere in HouseholdTaxIndividualComputation
, using household_tax
will
implicitly refer to the last state of the variable (so here with_deduction
),
matching the usual implicit convention in legal texts.
This completes our implementation of HouseholdTaxIndividualComputation
! Its
output variable household_tax
now contains the share of household tax owed by
each individual of the household, with the correct income tax deduction.
We can now use it in the computation of the global household tax in
HouseholdTaxComputation
.
Linking scopes together through list mapping
We can now finish coding up article 7 by adding together each share of the
household tax owned by all the individuals of the household. We will do
that through list aggregation, as previously, but the elements of the list to
aggregate are now the result of calling HouseholdTaxIndividualComputation
on each individual. Previously, we have showed how to call a sub-scope
statically and exactly one time. But here, this is not what we want: we want
to call the sub-scope as many times as there are individuals in the household.
We then have to use a different method for calling the sub-scope:
With all our refactorings, the declaration of the scope HouseholdTaxComputation
can be simplified (we don't need the function variable share_household_tax
anymore):
declaration scope HouseholdTaxComputation:
input individuals content list of Individual
output household_tax content money
Then, the definition of household_tax
could be re-written as follows next
to article 7:
scope HouseholdTaxComputation:
definition household_tax equals
sum money of
map each individual among individuals (
# Below is the syntax for calling the sub-scope
# "HouseholdTaxIndividualComputation" dynamically, on the spot.
# after "with" is the list of inputs of the scope.
output of HouseholdTaxIndividualComputation with {
# The next three lines are tautological in this example, because
# the names of the parameters and the names of the scope variables
# are identical, but the values of the scope call parameters can be
# arbitrarily complex!
-- individual: individual # <- this last "invididual" is the map variable
-- overseas_territories: overseas_territories
-- current_date: current_date
}
# The construction "output of <X> with { ... }" returns a structure
# containing all the output variables of scope <X>. Hence, we access
# output variable "household_tax" of scope
# "HouseholdTaxIndividualComputation" with the field access syntax
# ".household_tax".
).household_tax
That's it! We've finished implementing article 7 and article 8 in a clean, extensible, future-proof fashion using a series of scopes that call each other.
Testing and debugging the computation
We have written quite complex code in this tutorial section, it is high time to test and debug it. Similarly to the test presented in the first tutorial section, we can declare a new test scope for the household tax computation, and execute it:
declaration scope TestHousehold:
output computation content HouseholdTaxComputation
scope TestHousehold:
definition computation equals
output of HouseholdTaxComputation with {
-- individuals:
[ Individual {
-- income: $15,000
-- number_of_children: 0
} ;
Individual {
-- income: $80,000
-- number_of_children: 2
} ]
-- overseas_territories: false
-- current_date: |1999-01-01|
}
catala interpret tutorial.catala_en --scope=TestHousehold
┌─[RESULT]─
│ computation = HouseholdTaxComputation { -- household_tax: $21,500.00 }
└─
Is the result of the test correct ? Let's see by unrolling the computation manually:
- The household tax for two individuals and two children is
2 * $10,000 + 2 * $5,000
, so $30,000; - The first individual earns more than $10,000, less than $100,000, has no children and we are before the year 2000, so the income tax rate is 20 % per article 2 and their income tax is $3,000;
- The share of household tax for the first individual is $10,000, so the deduction for the first individual is the full $3,000;
- The second individual earns more than $10,000, less than $100,000$, but has two children so the income tax rate is 15 % per article 3 and their income tax is $12,000;
- The share of household tax for the second individual is $20,000, so the deduction for the second individual is the full $12,000$;
- The total deduction is thus $15,000$, which is capped at $8,500 per article 9;
- Applying the deduction to the base household tax yields $21,500.
So far so good, the test result is correct. But it might have gotten to the
right result by taking the wrong intermediate steps, so we'll want to
inspect them. Fortunately, the Catala interpreter can print the full
computation trace for that purpose. Here is the output on the interpretation
of TestHousehold
:
Trace of TestHousehold
Trace of TestHousehold
$ catala interpret tutorial.catala_en --scope=TestHousehold --trace
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition computation equals
│ ‾‾‾‾‾‾‾‾‾‾‾
Test
[LOG] → HouseholdTaxComputation.direct
[LOG] ≔ HouseholdTaxComputation.direct.
input: HouseholdTaxComputation_in { -- individuals_in: [Individual { -- income: $15,000.00 -- number_of_children: 0 }; Individual { -- income: $80,000.00 -- number_of_children: 2 }] -- overseas_territories_in: false -- current_date_in: 1999-01-01 }
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition shares_of_household_tax equals
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 7
[LOG] → HouseholdTaxIndividualComputation.direct
[LOG] ≔ HouseholdTaxIndividualComputation.direct.
input: HouseholdTaxIndividualComputation_in { -- individual_in: Individual { -- income: $15,000.00 -- number_of_children: 0 } -- overseas_territories_in: false -- current_date_in: 1999-01-01 }
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition household_tax equals
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 7
[LOG] ≔ HouseholdTaxIndividualComputation.household_tax: $10,000.00
[LOG] → IncomeTaxComputation.direct
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition income_tax_computation.current_date equals
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition income_tax_computation.individual equals
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition income_tax_computation.overseas_territories equals
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ≔ IncomeTaxComputation.direct.
input: IncomeTaxComputation_in { -- current_date_in: 1999-01-01 -- individual_in: Individual { -- income: $15,000.00 -- number_of_children: 0 } -- overseas_territories_in: false }
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ current_date < |2000-01-01|
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 2 (old version before 2000)
[LOG] ≔ IncomeTaxComputation.tax_rate: 0.2
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition income_tax equals
│ ‾‾‾‾‾‾‾‾‾‾
Article 1
[LOG] ≔ IncomeTaxComputation.income_tax: $3,000.00
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ income_tax_computation scope IncomeTaxComputation
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 7
[LOG] ≔ IncomeTaxComputation.direct.
output: IncomeTaxComputation { -- income_tax: $3,000.00 }
[LOG] ← IncomeTaxComputation.direct
[LOG] ≔ HouseholdTaxIndividualComputation.
income_tax_computation: IncomeTaxComputation { -- income_tax: $3,000.00 }
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition deduction equals
│ ‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ≔ HouseholdTaxIndividualComputation.deduction: $3,000.00
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ output of HouseholdTaxIndividualComputation with {
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- individual: individual
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- overseas_territories: overseas_territories
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- current_date: current_date
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ }
│ ‾
Article 7
[LOG] ≔ HouseholdTaxIndividualComputation.direct.
output: HouseholdTaxIndividualComputation { -- household_tax: $10,000.00 -- deduction: $3,000.00 }
[LOG] ← HouseholdTaxIndividualComputation.direct
[LOG] → HouseholdTaxIndividualComputation.direct
[LOG] ≔ HouseholdTaxIndividualComputation.direct.
input: HouseholdTaxIndividualComputation_in { -- individual_in: Individual { -- income: $80,000.00 -- number_of_children: 2 } -- overseas_territories_in: false -- current_date_in: 1999-01-01 }
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition household_tax equals
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 7
[LOG] ≔ HouseholdTaxIndividualComputation.household_tax: $20,000.00
[LOG] → IncomeTaxComputation.direct
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition income_tax_computation.current_date equals
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition income_tax_computation.individual equals
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition income_tax_computation.overseas_territories equals
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ≔ IncomeTaxComputation.direct.
input: IncomeTaxComputation_in { -- current_date_in: 1999-01-01 -- individual_in: Individual { -- income: $80,000.00 -- number_of_children: 2 } -- overseas_territories_in: false }
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ individual.number_of_children >= 2
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 3
[LOG] ≔ IncomeTaxComputation.tax_rate: 0.15
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition income_tax equals
│ ‾‾‾‾‾‾‾‾‾‾
Article 1
[LOG] ≔ IncomeTaxComputation.income_tax: $12,000.00
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ income_tax_computation scope IncomeTaxComputation
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 7
[LOG] ≔ IncomeTaxComputation.direct.
output: IncomeTaxComputation { -- income_tax: $12,000.00 }
[LOG] ← IncomeTaxComputation.direct
[LOG] ≔ HouseholdTaxIndividualComputation.
income_tax_computation: IncomeTaxComputation { -- income_tax: $12,000.00 }
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition deduction equals
│ ‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ≔ HouseholdTaxIndividualComputation.deduction: $12,000.00
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ output of HouseholdTaxIndividualComputation with {
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- individual: individual
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- overseas_territories: overseas_territories
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- current_date: current_date
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ }
│ ‾
Article 7
[LOG] ≔ HouseholdTaxIndividualComputation.direct.
output: HouseholdTaxIndividualComputation { -- household_tax: $20,000.00 -- deduction: $12,000.00 }
[LOG] ← HouseholdTaxIndividualComputation.direct
[LOG] ≔ HouseholdTaxComputation.
shares_of_household_tax: [HouseholdTaxIndividualComputation { -- household_tax: $10,000.00 -- deduction: $3,000.00 }; HouseholdTaxIndividualComputation { -- household_tax: $20,000.00 -- deduction: $12,000.00 }]
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition household_tax
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 7
[LOG] ≔ HouseholdTaxComputation.household_tax#base: $30,000.00
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition total_deduction
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ≔ HouseholdTaxComputation.total_deduction#base: $15,000.00
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition total_deduction
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 9
[LOG] ≔ HouseholdTaxComputation.total_deduction#capped: $8,500.00
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ definition household_tax
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 8
[LOG] ≔ HouseholdTaxComputation.household_tax#deduction: $21,500.00
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en
│
│ output of HouseholdTaxComputation with {
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- individuals:
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ [ Individual {
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- income: $15,000
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- number_of_children: 0
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ } ;
│ ‾‾‾
│ Individual {
│ ‾‾‾‾‾‾‾‾‾‾‾‾
│ -- income: $80,000
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- number_of_children: 2
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ } ]
│ ‾‾‾
│ -- overseas_territories: false
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ -- current_date: |1999-01-01|
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
│ }
│ ‾
Test
[LOG] ≔ HouseholdTaxComputation.direct.
output: HouseholdTaxComputation { -- household_tax: $21,500.00 }
[LOG] ← HouseholdTaxComputation.direct
[LOG] ≔ TestHousehold.computation: HouseholdTaxComputation { -- household_tax: $21,500.00 }
Inspecting the trace reveals the structure of the computation that matches closely the legal reasonning we did just above to compute the test output manually. With this powerful tool, it is possible to debug and maintain Catala programs at scale.
Conclusion
Congratulations for finishing the Catala tutorial! The last two sections have not presented features that are unique to Catala, unlike the exceptions from the second section. Rather, in Catala we use the classic software engineering techniques from functional programming to split the code into multiple functions that call each other at the right level of abstraction, with the goal to keep the code close where it is specified in the law. There are various ways to express something in Catala, but the proximity between the code and the legal specification should be the proxy for what is the idiomatic way to do things.
Refactoring the Catala code continuously as new legal requirements are added or updated is the key to maintaining the codebase efficiently over the long term, and avoiding the spaghetti code that is common when translating law to code. We hope this tutorial put you on the right track for your journey into Catala and the wonderful world of safely and faithfully automating legal provisions.
We encourage you to read the next chapters of this book to continue learning how to use Catala, as the tutorial is not set in a real-world software development projet setup, and misses a lot of tips about coding in Catala but also interacting with lawyers.
Recap of the current section
Recap of the current section
For reference, here is the final version of the Catala code consolidated at the end of this section of the tutorial.
```catala
declaration structure Individual:
data income content money
data number_of_children content integer
declaration scope IncomeTaxComputation:
input current_date content date
input individual content Individual
input overseas_territories content boolean
internal tax_rate content decimal
output income_tax content money
```
## Article 1
The income tax for an individual is defined as a fixed percentage of the
individual's income over a year.
```catala
scope IncomeTaxComputation:
definition income_tax equals
individual.income * tax_rate
```
## Article 2 (old version before 2000)
The fixed percentage mentioned at article 1 is equal to 20 %.
```catala
scope IncomeTaxComputation:
label article_2
definition tax_rate under condition
current_date < |2000-01-01|
consequence equals 20%
```
## Article 2 (new version after 2000)
The fixed percentage mentioned at article 1 is equal to 21 % %.
```catala
scope IncomeTaxComputation:
# Simply use the same label "article_2" as the previous definition to group
# them together
label article_2
definition tax_rate under condition
current_date >= |2000-01-01|
consequence equals 21%
```
## Article 3
If the individual is in charge of 2 or more children, then the fixed
percentage mentioned at article 1 is equal to 15 %.
```catala
scope IncomeTaxComputation:
label article_3 exception article_2
definition tax_rate under condition
individual.number_of_children >= 2
consequence equals 15%
```
## Article 4
Individuals earning less than $10,000 are exempted of the income tax mentioned
at article 1.
```catala
scope IncomeTaxComputation:
label article_4 exception article_3
definition tax_rate under condition
individual.income <= $10,000
consequence equals 0%
```
## Article 5
Individuals earning more than $100,000 are subjects to a tax rate of
30%, regardless of their number of children.
```catala
scope IncomeTaxComputation:
label article_5 exception article_3
definition tax_rate under condition
individual.income > $100,000
consequence equals 30%
```
## Article 6
In the overseas territories, the tax rate for individuals earning
more than $100,000 specified at article 5 is reduced to 25 %.
```catala
scope IncomeTaxComputation:
label article_6 exception article_5
definition tax_rate under condition
individual.income > $100,000 and overseas_territories
consequence equals 25%
```
## Article 7
When several individuals live together, they are collectively subject to
the household tax. The household tax owed is $10000 per individual of the household,
and half the amount per children.
```catala
declaration scope HouseholdTaxComputation:
input individuals content list of Individual
input overseas_territories content boolean
input current_date content date
internal shares_of_household_tax
content list of HouseholdTaxIndividualComputation
internal total_deduction content money
state base
state capped
output household_tax content money
state base
state deduction
declaration scope HouseholdTaxIndividualComputation:
input individual content Individual
input overseas_territories content boolean
input current_date content date
income_tax_computation scope IncomeTaxComputation
output household_tax content money
output deduction content money
```
```catala
scope HouseholdTaxIndividualComputation:
definition household_tax equals
$10000 * (1.0 + decimal of individual.number_of_children / 2.0)
scope HouseholdTaxComputation:
definition shares_of_household_tax equals
map each individual among individuals to
(
output of HouseholdTaxIndividualComputation with {
-- individual: individual
-- overseas_territories: overseas_territories
-- current_date: current_date
}
)
definition household_tax
state base
equals
sum money of
map each share_of_household_tax among shares_of_household_tax
to share_of_household_tax.household_tax
```
## Article 8
The amount of income tax paid by each individual can be deducted from the
share of household tax owed by this individual.
```catala
scope HouseholdTaxIndividualComputation:
definition income_tax_computation.individual equals
individual
definition income_tax_computation.overseas_territories equals
overseas_territories
definition income_tax_computation.current_date equals
current_date
definition deduction equals
if income_tax_computation.income_tax > household_tax then household_tax
else
income_tax_computation.income_tax
scope HouseholdTaxComputation:
definition total_deduction
state base
equals
sum money of
map each share_of_household_tax among shares_of_household_tax
to share_of_household_tax.deduction
definition household_tax
state deduction
equals
if total_deduction > household_tax then $0
else household_tax - total_deduction
```
## Article 9
The deduction granted at article 8 is capped at $8,500 for the whole household.
```catala
scope HouseholdTaxComputation:
definition total_deduction
state capped
equals
if total_deduction > $8,500 then $8,500 else total_deduction
```
## Test
```catala
declaration scope Test:
output computation content IncomeTaxComputation
scope Test:
definition computation equals
output of IncomeTaxComputation with {
-- individual:
Individual {
-- income: $20,000
-- number_of_children: 0
}
-- overseas_territories: false
-- current_date: |1999-01-01|
}
declaration scope TestHousehold:
output computation content HouseholdTaxComputation
scope TestHousehold:
definition computation equals
output of HouseholdTaxComputation with {
-- individuals:
[ Individual {
-- income: $15,000
-- number_of_children: 0
} ;
Individual {
-- income: $80,000
-- number_of_children: 2
} ]
-- overseas_territories: false
-- current_date: |1999-01-01|
}
```
Setting up a Catala project
This section of the Catala book has not yet been written, stay tuned for future updates!
Directory structure and configuration
This section of the Catala book has not yet been written, stay tuned for future updates!
Test and continuous integration workflow
Testing Catala programs
Catala supports two distinct flavors of tests, tailored for different purposes:
- Test scopes should be the main way to write tests that validate
expectations on a given computation. This is the natural way to automate the
catala interpret --scope=TestXxx
commands we have been running so far. - Cli tests provide a way to run custom catala commands and check their output. They make the computation output visible, and are sometimes useful for more specific needs, like ensuring the correct error is triggered in a given situation.
The command clerk test
can be run on any file or directory holding catala
files, will process both kinds of tests and print a report.
Test scopes
Declaration
A test scope is a scope that is marked with the "test" attribute: simply
write #[test]
just before its declaration
keyword.
#[test]
declaration scope TestMoneyRounding:
output result content money
There are two requirements for a test scope:
- The scope must be public (declared in a
```catala-metadata
section) so that it can be run and checked in real conditions - It must not require any input: only
internal
,output
andcontext
variables are allowed
Definition
The expected output of the test should be validated with assertion
statements. (Without them, the only thing the test would validate is that the computation doesn't trigger an error).
scope TestMoneyRounding:
definition result equals $100 / 3
assertion result = $33.33
Most often, as seen in 2.1, a test scope is used to run a more generic computation scope, providing it with specific inputs and an expected result.
#[test]
declaration scope Test_IncomeTaxComputation_1:
output computation content IncomeTaxComputation
scope Test_IncomeTaxComputation_1:
# Define the computation as IncomeTaxComputation applied to specific inputs
definition computation equals
output of IncomeTaxComputation with {
-- individual:
Individual {
-- income: $20,000
-- number_of_children: 0
}
}
# Check that the result is as expected
assertion computation.income_tax = $4,000
Running the tests
Simply use clerk test tutorial.catala_en
. You can specify a directory to test
everything below this directory; if you don't specify anything, the current
directory is used.
$ clerk test --verbose
██ tutorial.catala_en: 1 / 1 tests passed
■ scope Test passed
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ALL TESTS PASSED ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ FAILED PASSED TOTAL ┃
┃ tests 0 1 1 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
The summary will detail any failures. In addition, it is possible to run the
command with the flag --xml
to obtain a JUnit-compatible report.
The above tests the Catala code as run by the Catala interpreter (and through
the OCaml backend, for compiled modules). If your deployment uses a specific
backend, say python, it is highly recommended that you also include a run of
clerk test --backend=python
in your CI.
This command will compile the tests into an executable that validates the consistency of all outputs with what the interpreter returned, using the given backend; then run that target. This way, you will be shielded from the eventuality that a bug in the backend you use leads to a different outcome for the same Catala program.
Command-line interface (CLI) tests
This second flavor of tests provides a means to validate the output of a given Catala command, which may be useful in more specific integration scenarios. It is inspired by the Cram test system, in that a single source file includes both the test command and its expected output.
For example, checking that an error happens when expected cannot be done with
test scopes, which must succeed. You may want to include tests that make use of
catala proof
. Or you could want, for a simple test, to validate that the trace is
exactly as intended. For this, a ```catala-test-cli
section should be
introduced in the source Catala file. The first line always starts with
$ catala
, followed by the Catala command to run on the current file ; the
rest is the expected output from the command ; additionally, if the command
terminated with an error, the last line will show the error code.
```catala-test-cli
$ catala interpret --scope=Test --trace
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en:214.14-214.25:
│
214 │ definition computation equals
│ ‾‾‾‾‾‾‾‾‾‾‾
Test
[LOG] → IncomeTaxComputation.direct
[LOG] ≔ IncomeTaxComputation.direct.input: IncomeTaxComputation_in { -- current_date_in: 1999-01-01 -- individual_in: Individual { -- income: $20,000.00 -- number_of_children: 0 } -- overseas_territories_in: false }
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en:33.5-33.32:
│
33 │ current_date < |2000-01-01|
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
Article 2 (old version before 2000)
[LOG] ≔ IncomeTaxComputation.tax_rate: 0.2
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en:21.14-21.24:
│
21 │ definition income_tax equals
│ ‾‾‾‾‾‾‾‾‾‾
Article 1
[LOG] ≔ IncomeTaxComputation.income_tax: $4,000.00
[LOG] ☛ Definition applied:
─➤ tutorial.catala_en:215.5-223.6:
│
215 │ output of IncomeTaxComputation with {
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
216 │ -- individual:
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾
217 │ Individual {
│ ‾‾‾‾‾‾‾‾‾‾‾‾
218 │ -- income: $20,000
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
219 │ -- number_of_children: 0
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
220 │ }
│ ‾
221 │ -- overseas_territories: false
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
222 │ -- current_date: |1999-01-01|
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
223 │ }
│ ‾
Test
[LOG] ≔ IncomeTaxComputation.direct.output: IncomeTaxComputation { -- income_tax: $4,000.00 }
[LOG] ← IncomeTaxComputation.direct
[LOG] ≔ Test.computation: IncomeTaxComputation { -- income_tax: $4,000.00 }
┌─[RESULT]─ Test ─
│ computation = IncomeTaxComputation { -- income_tax: $4,000.00 }
└─
```
When running clerk test
, the specified command is run on the file (truncated
to that point). If the output exactly matches what is in the file, the tests
passes. Otherwise, it fails, and the precise differences are shown side-by-side.
There are two additional big differences with the "test scopes" approach:
- this cannot be used to test backend-generated code, only
catala
commands:clerk test --backend=...
won't run CLI tests. - if the tests fail, but due to a legitimate difference (for example, a line
number change in the example above), it is possible to run
clerk test --reset
to automatically update the expectations. This will immediately make the CLI test pass, but versionning systems and a standard code review will highlight the changes.
clerk test --reset
can also be used to initialise a new test, from a
```catala-test-cli
section that only provides the command without expected
output.
Compilation and deployment
This section of the Catala book has not yet been written, stay tuned for future updates!
External modules and compiler plugins
This section of the Catala book has not yet been written, stay tuned for future updates!
Agile development with lawyers and programmers
This section of the Catala book has not yet been written, stay tuned for future updates!
FAQ: How to code the law?
After installing Catala, doing the tutorial and setting up your project environment, you should be on your way to start your turning-law-into-code project. However, you might still have a number of your questions unanswered, and we have you covered. This chapter tries to answer to all the common questions users have about turning law into code in general, and Catala in particular.
General questions
Portions of these answers are adapted, with permission, from Lawsky, Coding the Code: Catala and Computationally Accessible Tax Law, 75 SMU L. Rev. 535 (2022).
What is pair programming for coding the law?
As the introduction of this book explains, Catala is designed to be used by teams that have both legal and computational expertise. Pair programming generally refers to two programmers working together side by side at the same computer to write computer code. Pair programming for coding the law does not involve two programmers with similar skills but rather a programmer and a lawyer, people with different areas of expertise, working side by side to write computer code that captures as accurately as possible the meaning of the law. Pair programming with a lawyer and a programmer allows the law to be encoded without the need for one person to be an expert in both tax law and coding. Pair programming also improves the quality of code because pair programming requires the lawyer to explain the law very clearly, so clearly that a programmer can understand it, and it requires the programmer to be able to articulate exactly how the computer code matches the law, thus improving precision and attention to detail for both the law and the code.
What is literate programming for coding the law?
Literate programming is the idea, first put forward by Donald Knuth, that a computer program doesn't just communicate to a computer what to do, but also communicates to the humans reading the program what the program wants the computer to do. Practically speaking, programs that follow the literate programming approach include a lot of comments. Literate programming is especially important in coding the law, beacuse it increases transparency and shows clearly how the code does (or does not) match up with the law. As explained in the first section of the tutorial, literate programming for the law in Catala means that Catala code includes not only the code that tells the computer what to do, but also the actual statutory or regulatory language that relates to the code for the computer. This makes it easier to update the computer code when the law changes. The comments also include the reasoning behind the decisions of the coding team, including extra-legal statutory support for the decisions. And the comments also can make explicit ambiguities the coding team found, and how and why the team chose to resolve the ambiguities.
Why shouldn't I use any programming language that I like to code the law?
The question of which programming language to use to code the law is, of course, a pragmatic question. That Catala can be compiled to other languages, such as Python, demonstrates that one could code the law and accurately capture its meaning, in some sense, in many different languages. But Catala has several advantages for coding law, especially law that is organized as general rules followed by exceptions, as many laws are.
First, a program in Catala mimics the structure of legal thinking by listing the relevant parties, structures, and variables first. Items are named before they are used, which allows humans looking at the computer code to identify relevant concepts before applying them. Second, Catala allows the programmer to easily define a scope over which various rules and definitions apply, consistent with the law's approach of having provisions that apply only to "this chapter," "this section," and so forth.
Additionally, and most importantly, as described in the second section of the tutorial, the semantics of Catala are based on a logic of general rules and exceptions ("prioritized default logic"). The structure of Catala programs can therefore closely track the structure of the law itself, if the law is organized as general rules followed by exceptions. This makes it easier to code the law; because the underlying logic tracks the statute, the default logic translation can simply follow the statute itself. And if the statute changes, it will be easier to change the Catala code than it would be to change code that did not follow the structure of the statute. Because Catala allows for exceptions that override more general rules, formalizing in Catala allows separate sections and subsections of the law to remain separate. Drafting is more modular and allows coders to swap new law in and old law out more easily.
Finally, Catala programs can be formally verified because Catala has a well-defined formal semantics.
Can we use large language models (LLMs) to translate the law into code?
Whether large language models (LLMs) can be used to translate the law into code is an open research question that various people have been pursuing, without great success at this point. Legal language is complex and not like "regular" language. Even beyond that, though, coding law is more than just straightforwardly "translating" statutory language into computer code. The law encompasses more than just the language of a statute. Coding law may, for example, require resolving ambiguities present in statutory text, or incorporating regulations or practices outside the statute. Translating law into code also requires precision, accuracy, and accountability, all of which are, at the time of this writing, in tension with using LLMs.
Can every law be formalized?
In some sense not particularly interesting sense, every law can be formalized. But at least when it comes to formalizing law with Catala, not every law is usefully formalized. Laws where the effort of formalization pays off conconcretely tend to be rule-heavy, complex laws, perhaps involving many numerical computations, and, to really take advantage of all that Catala has to offer, structured as general rules followed by exceptions.
Formalizing law also has impact on how it is enforced, and on the Rule of Law at large. See the output of the COHUBICOL research group for more detail.
Catala-specific questions
When to choose condition
, rule
vs booleans?
You may have noticed the keywords condition
and rule
in Catala scopes,
for instance:
declaration scope Foo:
input i content integer
output x condition
scope Foo:
rule x under condition i = 42 consequence fulfilled
The above is strictly equivalent to the following program:
```catala
declaration scope Foo:
input i content integer
output x content boolean
scope Foo:
definition x equals false
exception definition x under condition i = 42 consequence equals true
As the example shows, condition
is a syntactic sugar
for declaring a boolean scope variable whose default value is false
. In the body
of the scope, you must use rule
instead of definition
for defining under which
conditions the condition
should be fulfilled
(true
) or not fulfilled
(false
).
It is possible to use exception
and label
on rules like for definitions,
but all the rule
s are implicitly exceptions of a base case where the condition
is false
.
This behavior for condition
and rule
matches the legal intuition, making this
syntax easier to read for pieces of programs with complex code to set a boolean
variable.
Why do I have to cast values?
Some programming languages, like Javascript, do not make any distinction between
decimals and integers (there is a unique Number
type). In others, like Python,
the distinction is hidden because the compiler or interpreter inserts implicit
casts whenever you use an integer when a decimal was needed. This approach eases
programming as you do not need to worry how the number is represented in memory,
things just work.
However, this approach has a downside, precisely because the language decides for you how the number is represented in memory. The downside is that you are not in control of how precise the computations are, and how values are casted from one representation to another. For instance, when casting a decimal to an integer, you will lose precision because of rounding or truncating; there are multiple ways to convert a number of months into a number of days depending on what you are computing.
The philosophy of Catala is to give you full control over those choices, at
the expense of require explicit casting. Hence, Catala's base types (boolean
,
integer
, decimal
, money
, date
, duration
) strictly distinct and require
explicit casting between them. Using a decimal
where an integer
is needed
will yield a type error like the following:
┌─[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
│ │ ‾‾‾
└─
This error can be fixed by tweaking 2.0
to integer of 2.0
. See the
"Literals" section of the language reference for more
details about how to create literals with the correct type.
Why is there a distinct money type?
Correctly performing financial computations is hard. The precision and rounding rules required may vary from application to application, and should be balanced with performance requirements.
This is why Catala separates strictly the money
type from integer
s or
decimal
s. Using money
values in Catala, along with explicit casting (see
above), lets the compiler warn you when you're mixing money and non-money
numbers in your computation. Money, and the currency unit, becomes like a
dimensional unit in a physical formula that needs to check out coherently.
Once that we have these separate money
value, we have to give them a behavior
that accommodates most uses and respects the philosophy of the language. Hence,
money
values in Catala are a integer number of cents. Multiplying a money
value by a decimal
can yield a value that is not an exact number of cent;
in that case Catala rounds the result to the nearest cent.
If you want more precision for values representing money amount, you should
represent them as decimal
and cast them in (with the occasional rounding) and
out of money
when you need to.
How to round money up or down to a specific precision?
In Catala, monetary values are represented as an integer number of cents (see
above). A calculation with the catala money
type always result in an amount
rounded to the nearest cent. This means, that, when performing intermediate
computations on money, rounding must be considered by the programmer at each
step. This aims at making review by domain experts easier, since for each
intermediate value, they can follow along and perform example computations with
a simple desk calculator.
To round to the nearest monetary unit, use round of
. To round an amount to an
arbitrary multiple of a cent, you can perform a multiplication with that
multiple, round the amount and divide it by the same amount. For instance,
(round of $4.13 * 10.) / 10. = $4.10
. To round up or down, add or subtract
half a unit before performing the computation and rounding. For instance, to
round down to a cent the result of a multiplication between a money
value and
a decimal, you have to cast to decimal
, subtract half a cent, and round back
to the nearest cent by casting again to money
: `money of ((decimal of $149.26)
- 0.5% - 0.005) = $0.74
and not
0.75$`. This is a bit of a mouthful, but can be adapted to any desired rounding rule. Encapsulate these computation tidbits inside a global function to reuse them across your codebase. See the language reference for more details.
This technique can also be reused for decimal
values that require rounding up
or down to a specific precision.
Why mathematical integers and decimals instead of machine integers and floats?
Precision! Machine integers have a maximum and minimum value and wrap on overflow or underflow. Floating-point values cannot represent arbitrary small intervals between numbers and lose precision by accumulating errors, computation after computation. These weaknesses are usually ignored by computer scientists, as machine integers and floating points are precise enough for most applications, but financial computations for automatic administrative decision-making should not fail, even rarely, due to these low-level problems.
Hence, Catala uses the GMP library to feature true mathematically sound integers and decimal values whose representation in memory grows as more and more precision is needed from them. This choice adds some performance overhead but GMP includes state-of-the-art optimizations tailored for every architecture using assembly tricks to lower or even cancel this overhead for computations that don't really require the extra precision.
For instance and under the hood, Catala's decimal
are actually GMP rationals,
irreducible fractions made of two GMP infinite-precision integers.
How to create dates and durations from integers?
To get a duration, simply multiply the desired duration unit by the integer or decimal:
1 month * 24 = 24 month
declaration duration_of_days content duration
depends on number_of_days content integer
equals
1 day * number_of_days
However, you cannot build a YYYY-MM−DD
by directly concatenating together the
integer
values of YYYY-MM-DD
. Instead, convert the integer values to
durations, and add the durations to a starting date:
declaration date_of_YMD content date depends on
year_number content integer,
month_number content integer,
day_number content integer
equals
|0000-01-01| +
1 year * year_number +
1 month * (month_number - 1) +
1 day * (day_number - 1)
Using this helper function helps you avoid building invalid dates; for instance
date_of_YMD of 2025,4,31 = |2025-01-01|
because there are only 30 days in
April.
Why are there no strings?
The absence of strings in Catala is a feature, not a bug. Catala is meant to be a domain-specific programming language for computations described in legal texts, that lawyers understand. If you find a legal text that requires actual string manipulation operations to be automated, please tell the Catala team! In absence of such a legal text, the decision was made to not include strings, for several reasons.
First, the common operations present in legal texts that can be done with
strings, can also be done better with other Catala features. For instance,
it is better to represent tags and codes with enumeration
s that can contain
payload and have a built-in exhaustiveness check in pattern matching. We thus
advise to really think your problem through and see whether it really requires
strings as a first-class value type in Catala to be solved.
Second, the preferred way of performing low-level, computation-intensive operations not described by legal text but used in a Catala program is to simply to them outside of Catala and provide their output as inputs of a Catala scope, or define an external module. See the language reference for more details.
Third, including string manipulations in the Catala runtime will heavily increase the size and complexity of the runtime, as it will probably require a fully-fledged regexp library as a dependency. Moreover, this regexp library dependency should be available in every backend programming language that Catala supports, to ensure that the semantics of string operations is absolutely the same whatever the backend. This is a lot of work and later, maintenance, for the Catala team.
How do I add an exception from outside a scope?
Sometimes, the law is quite convoluted. For instance, article 1731
bis of
the French tax code describes how to compute fines for tax fraud or late income
declaration. This article specifies that, when computing the fine amount, the
main tax computation should be tweaked to neutralize certain deductions. If you
were to implement this in Catala, you would have two scopes FinesComputation
and IncomeTaxComputation
; article 1731 bis requires you to call
IncomeTaxComputation
from FinesComputation
while tweaking certain
computation rules inside IncomeTaxComputation
.
This pattern amounts to declaring an exception to a variable of
IncomeTaxComputation
, from the outside of IncomeTaxComputation
. Turns out
there is a specific Catala feature to handle this case, extending the
exception
s in a principled way across scopes : context variables. See the
language reference for more details.
Do I have to repeat every field in a struct when I want to only change one of them?
No! See "Updating structs" in the language reference for more details.
How are dates and durations handled?
What is the result of Jan 31st + 1 month
? Is it Feb 28th, Feb 29th or March 1st?
This question reveals the subtle tricks behind date computations in the Gregorian
calendar. The variable number of days in a month and leap years cause ambiguities
in many date computations specified in the law. The way these ambiguities
are resolved influences the outcome of administrative automated decisions, which
is why we have been very cautious in Catala about this topic. Our motivations
and design choices are outlined in a scientific article;
in summary we had to implement a custom date computation library
that lets the user choose how to round ambiguous dates computations.
Otherwise, dates in Catala are standard dates in the Gregorian calendar, precise to the day (and not more). Durations are a combination of a number of days, months and/or years. See the language reference for more details.
Which programming languages can Catala target?
The Catala compiler natively targets :
- C (standard C89);
- Python;
- Java;
- OCaml;
From the OCaml backend, Javascript can be targeted through
js_of_ocaml
.
The runtimes for these backends have two dependencies outside the standard library of the target programming languages: GMP for multi-precision arithmetic and our custom date computation library.
Why scope declarations cannot be split like scope definitions ?
This section of the Catala book has not yet been written, stay tuned for future updates!
The Catala language
Welcome to the reference of the Catala language! The goal of this chapter is to convey itemized explanations of each feature of the language, with examples and tips.
In this reference, a language feature is often akin to a syntax element as presented in the syntax cheat sheet. But unlike the syntax cheat sheet, this reference contextualises the features, motivates the design choices and give examples of uses.
However, unlike the Catala tutorial, this reference guide is not meant to teach you Catala, as language features are presented in a taxonomic order, and each feature explanation does not always build upon the explanation of features presented earlier.
Rather, the goal of the reference is to offer a place for you to learn about a specific Catala feature when you encounter or need it, after a first learning phase of Catala through the tutorial.
If you have a question but don't know to which language feature it corresponds, check out the Catala-specific FAQ.
Literate programming and basic syntax
Catala source code files should be interpretable as Markdown files. Specifically, Catala's Markdown syntax is aligned on the Pandoc markdown syntax.
Literate programming
Weaving and tangling
Literate programming mixes the source code and its documentation in the same document. A Catala source file thus contains both Catala code and the text of the legal specification for this code.
To execute the code or compile to a target programming language, the Catala compiler simply ignores all legal specification text from the source file and picks only the source code. This is called tangling.
But you might also want to produce a human-readable (or even lawyer-readable) comprehensive document about the program and its specification. The Catala compiler and build system also lets you do that, see the build system reference for more information. This is called weaving.
Free text and paragraphs
If you simply put some text in your Catala source code file, it will be interpreted as free text and ignored as source Catala code. This free text mode is meant for you to copy-paste the legal specifications of your Catala program. These specifications will be the basis of your program, as you will annotate them with Catala code blocks.
Free text follows the classic Markdown syntax: you need to leave a blank line to introduce a new paragraph.
This is a first paragraph of free text.
This sentence will be rendered on the same line as the one before.
This sentence begins a new paragraph after a line jump.
Headers
A Markdown document is organized thanks to a hierarchy of headers, each
introduced by #
. The more #
a header has, the more deep it is into
the plan of the document. Use these headers to replicate the hierarchical
structure of legal documents (sections, articles, etc.); each paragraph
of the legal specification should have a header specifying its title.
# Title of the document
## First section
### Article 1
...
## Second section
### Article 2
...
### Article 3
...
Indeed, the Catala compiler will keep track of these headers to enrich the source code positions used in error messages, debugging and explanations interfaces.
Other Markdown features
For styling (bold, italics), tables, links, etc., you can use all the Markdown features supported by Pandoc.
Basic syntax principles
Catala code blocks
All the Catala source code must be contained inside a Catala code block. A Catala code block looks like this inside your source file:
```catala
<your code goes here>
```
In general, Catala source code does not care about line jumps, tabs and spaces; you can organize your code however you like. However, this syntax for opening and closing Catala code blocks is very strict and should be strictly enforce.
- The opening
```catala
should be at the beginning of a new line (no space before```catala
). - Do not put a space between
```
andcatala
. - Always put a new line after
```catala
. - Always put the closing
```
alone on a new line. - Always put a new line after the closing
```
. - You cannot nest code blocks, i.e. an opening
```catala
should always be closed by a closing```
without opening new code blocks in between.
Comments inside Catala code blocks
Inside Catala code block, you can comment your code by prefixing lines with
#
. Comments will be ignored by the compiler at runtime but weaved into
the documentation like legal specifications in free text mode.
Reserved keywords
Certain words are reserved in Catala for keywords and thus cannot be used a variable names. If you try, you will get a confusing syntax error because Catala will believe that you tried to use the keyword instead of the variable name.
The reserved keywords in Catala are :
scope
consequence
data
depends on
declaration
context
decreasing
increasing
of
list of
contains
enumeration
integer
money
text
decimal
date
duration
boolean
sum
fulfilled
definition
state
label
exception
equals
match
anything
with pattern
under condition
if
then
else
condition
content
structure
assertion
with
for
all
we have
rule
let
exists
in
among
combine
map each
to
such
that
and
or
xor
not
maximum
minimum
is
or if list empty
but replace
initially
number
year
month
day
true
false
input
output
internal
round
get_day
get_month
get_year
first_day_of_month
last_day_of_month
Textual inclusion
While a Catala module should be contained in one file, sometimes legal specifications are very large and it is impossible to break them down into logically-independent modules: the law is a big ball of spaghetti code. In those case, it would be unfair to force you to have gigantic Catala source files to represent one module.
Hence, Catala has a textual inclusion feature. It works like this. If, inside
foo.catala_en
, you put (in free text mode, not inside a Catala code block):
> Include: bar.catala_en
Then, the weaving and tangling will work as if you had copy-pasted the contents
of bar.catala_en
inside foo.catala_en
at the location where the > Include
is.
Typically, you want to implement all the provisions for the computation
of a tax. These provisions are split across a statute, a regulation and
a court case. Each of these specify a part of the computation, so they
need to be inside the same Catala module. However, you can use the textual
inclusion mechanism to split your implementation into four different files
mirroring the different sources of the specification: tax.catala_en
,
statute.catala_en
, regulation.catala_en
and court_case.catala_en
.
tax.catala_en
will be the master file listing the other ones:
# Tax computation
> Include: statute.catala_en
> Include: regulation.catala_en
> Include: court_case.catala_en
Then, you can copy-paste the legal specification into statute.catala_en
,
regulation.catala_en
and court_case.catala_en
, starting with ##
headers
in each of those files because tax.catala_en
already features the toplevel #
header.
Types, values and operations
Each value manipulated by the Catala programs have a well-defined type that is either built-in or declared by the user. This section of the language reference summarizes all the different kind of types, how to declare them and how to build values of this type.
Base types
The following types and values are built-in Catala, relying on keywords of the language.
Booleans
The type boolean
has only two values, true
and false
.
Boolean operations
Symbol | Type of first argument | Type of second argument | Type of result | Semantic |
---|---|---|---|---|
and | boolean | boolean | boolean | Logical and (eager) |
or | boolean | boolean | boolean | Logical or (eager) |
xor | boolean | boolean | boolean | Logical xor (eager) |
not | boolean | boolean | Logical negation |
Comparisons
Symbol | Type of first argument | Type of second argument | Type of result | Semantic |
---|---|---|---|---|
=, != | anything but functions | same as first argument | boolean | Structural (in)equality |
>, >=, <, <= | integer, decimal, money, date | same as first argument | boolean | Usual comparison |
>, >=, <, <= | duration | duration | boolean | Usual comparison if same unit, else runtime error |
Integer operations
The type integer
represents mathematical integers, like 564,614
or -2
.
Note that you can optionally use the number separator ,
to make large integers
more readable.
Symbol | Type of first argument | Type of second argument | Type of result | Semantic |
---|---|---|---|---|
+ | integer | integer | integer | Integer addition |
- | integer | integer | integer | Integer substraction |
- | integer | integer | Integer negation | |
* | integer | integer | integer | Integer multiplication |
/ | integer | integer | decimal | Rational division |
Decimals
The type decimal
represents mathematical decimal numbers (or rationals),
like 0.21
or -988,453.6842541
. Note that you can optionally use the number
separator ,
to make large decimals more readable. Decimal numbers are stored
with infinite precision in Catala, but you can
round them at will.
A percentage is just a decimal value, so in Catala you will have 30% = 0.30
and you can use the %
notation if this makes your code easier to read.
Decimal operations
Symbol | Type of first argument | Type of second argument | Type of result | Semantic |
---|---|---|---|---|
+ | decimal | decimal | decimal | Rational addition |
- | decimal | decimal | decimal | Rational substraction |
- | decimal | decimal | Rational negation | |
* | decimal | decimal | decimal | Rational multiplication |
/ | decimal | decimal | decimal | Rational division |
round of | decimal | decimal | Round to nearest unit |
Money
The type money
represents an amount of money, positive or negative, in a
currency unit (in the English version of Catala, we use the $
currency
symbol), with a precision down to the cent and not below. Money values are
noted like decimal values with maximum two fractional digits and the currency
symbol, like $12.36
or -$871,84.1
.
Money operations
Symbol | Type of first argument | Type of second argument | Type of result | Semantic |
---|---|---|---|---|
+ | money | money | money | Money addition |
- | money | money | money | Money substraction |
- | money | money | Money negation | |
/ | money | money | decimal | Rational division |
round of | money | money | Round to nearest cent |
Dates
The type date
represents dates in the gregorian
calendar. This type
cannot represent invalid dates like 2025-02-31
. Values of this type are
noted following the ISO8601 notation (YYYY-MM-DD
)
enclosed by vertical bars, like |1930-09-11|
or |2012-02-03|
.
Semantic of date addition
During the design of Catala, we noticed that the question "What is Jan 31st + 1 month?" had no consensual answer in Computer Science.
- In Java, it will be Feb 28/29th depending on the leap year.
- In Python, it is impossible to add months with the standard library.
- In
coreutils
, it gives March 3rd (!).
Given how important date computations are in legal implementations, we decided to sort this mess out and described our results in a research paper:
Date addition in Catala is adding a duration to a date, yielding a new date in the past or in the future depending whether the duration is negative or positive. The value of this new date depends on the content of the duration:
- if the duration is a number of days, then Catala will simply add or subtract this amount of days to the day in the original date, wrapping to previous or next months if need be;
- if the duration is a number of years (say,
x
), then Catala will behave as if the duration was12 * x months
; - if the duration is a number of months, Catala will simply add or subtract this amount of months to the month in the original date, wrapping to previous or next year if need be; but this operation might yield an invalid date (like Jan 31st + 1 month -> Feb 31st), which needs to be rounded.
There are three rounding modes in Catala, whose description is below:
Rounding mode | Semantic | Example |
---|---|---|
No rounding | Runtime error if invalid date | Jan 31st + 1 month = AmbiguousDateComputation |
Rounding up | First day of next month | Jan 31st + 1 month = March 1st |
Rounding down | Last day of previous month | Jan 31st + 1 month = Feb 28/29th (depending on leap year) |
By default, Catala is in the "No rounding" mode. To set the rounding mode to either up or down, for all the date operations inside a whole scope, use this syntax:
# Let us suppose you want to set the rounding more for date operations
# inside scope Foo declared elsewhere
scope Foo:
date round decreasing # rounding down
# or
date round increasing # rounding up
Lastly, if the duration to add is comprised of multiple units (like 2 month + 21 day
), then Catala will start by adding
the component with the largest unit (here, month
), then the component with the smallest unit (here, day
).
Date operations
Symbol | Type of first argument | Type of second argument | Type of result | Semantic |
---|---|---|---|---|
+ | date | duration | date | Date addition (see above) |
- | date | date | duration | Number of days between dates |
get_day of | date | integer | Day in month (1..31) | |
get_month of | date | integer | Month in year (1..12) | |
get_year of | date | integer | Year number | |
first_day_of_month of | date | date | First day in the month | |
last_day_of_month of | date | date | Last day in the month |
Durations
The type duration
represents durations in terms of days, months and/or years,
like 254 day
, 4 month
or 1 year
. Durations can be negative and combine
a number of days and months together, like 1 month + 15 day
.
Is it always true that in terms of durations, 1 year = 12 month
. However,
because months have a variable number of days, comparing durations in days
to durations in months is ambiguous and requires legal interpretations.
For this reason, Catala will raise a runtime error when trying to perform such a comparison. Moreover, the difference between two dates will always yield a duration expressed in days.
Duration operations
Symbol | Type of first argument | Type of second argument | Type of result | Semantic |
---|---|---|---|---|
+ | duration | duration | date | Add the number of days, months, years |
- | duration | duration | date | Add the opposed duration |
- | duration | duration | Negate the duration components | |
* | duration | integer | duration | Multiply the number of days, month, years |
Casting base number types
Casting between base type is explicit; the syntax is <name of desired type> of <argument>
.
Type of argument | Type of result | Semantic |
---|---|---|
decimal | integer | Truncation |
integer | decimal | Value conserved |
decimal | money | Round to nearest cent |
money | decimal | Value conserved |
money | integer | Truncation to nearest unit |
integer | money | Value conserved |
decimal | integer | Truncation |
User-declared types
User-declared types have to be declared before being used in the rest of the program. However, the declaration needs not be placed before the use in textual order.
Structures
Structures combine multiple pieces of data together into a single type. Each piece of data is a "field" of the structure. If you have a structure, you can access each of its fields, but you need all the fields to build the structure value.
Structure types are declared by the user, and each structure type has a name
chosen by the user. Structure names begin by a capital letter and should follow
the CamlCase
naming convention. An example of structure declaration is:
declaration structure Individual:
data birth_date content date
data income content money
data number_of_children content integer
The type of each field of the structure is mandatory and introduced by content
.
It is possible to nest structures (declaring the type of a field of a structure
as another structure or enumeration), but not recursively.
Structure values are built with the following syntax:
Individual {
-- birth_date: |1930-09-11|
-- income: $100,000
-- number_of_children: 2
}
To access the field of a structure, simply use the syntax <struct value>.<field name>
, like individual.income
.
Enumerations
Enumerations represent an alternative between different choices, each encapsulating a specific pattern of data. In this sense, enumerations are fully-fledged "sum types" like in functional programming languages, and more powerful than C-like enumerations that just list alternative codes a value can have. Each choice or alternative of the enumeration is called a "case" or a "variant".
Enumeration types are declared by the user, and each enumeration type has a name
chosen by the user. Enumeration names begin by a capital letter and should follow
the CamlCase
naming convention. An example of enumeration declaration is:
declaration enumeration NoTaxCredit:
-- NoTaxCredit
-- TaxCreditForIndividual content Individual
-- TaxCreditAfterDate content date
The type of each case of the enumeration is mandatory and introduced by
content
. It is possible to nest enumerations (declaring the type of a field of
an enumeration as another enumeration or structure), but not recursively.
Enumeration values are built with the following syntax:
# First case
NoTaxCredit
# Second case
TaxCreditForIndividual content (Individual {
-- birth_date: |1930-09-11|
-- income: $100,000
-- number_of_children: 2
})
# Third case
TaxCreditAfterDate content |2000-01-01|
To inspect enumeration values, see in which case you are and use the associated data, use pattern matching.
Lists
The type list of <another type>
represents a fixed-size array of another type.
For instance, list of integer
represents a fixed-size array of integers.
You can build list values using the following syntax:
[1; 6; -4; 846645; 0]
List operations
Syntax | Type of result | Semantic |
---|---|---|
<list> contains <element> | boolean | true if <list> contains <element> , false otherwise |
number of <list> | integer | Length of the list |
exists <var> among <list> such that <expr> | boolean | true if at least one element of list satisfies <expr> |
for all <var> among <list> we have <expr> | boolean | true if all elements of list satisfy <expr> |
map each <var> among <list> to <expr> | list | Element-wise mapping, creating a new list with <expr> |
list of <var> among <list> such that <expr> | list | Creates a new list with only the elements satisfying <expr> |
map each <var> among <list> such that <expr1> to | list | Combines the filter and map (see two last operations) |
<list1> ++ <list2> | list | Concatenate two lists |
sum <type> of <list> | <type> | Aggregates the contents (money , integer , decimal ) of a list |
maximum of <list> [or if list empty then <expr>] | type of elements | Returns the maximum element of the list (or an optional default) |
minimum of <list> [or if list empty then <expr>] | type of elements | Returns the minimum element of the list (or an optional default) |
content of <var> among <list> such that <expr1> is maximum [or if list empty then <expr2>] | type of elements | Returns the arg-maximum element of the list (or an optional default) |
content of <var> among <list> such that <expr1> is minimum [or if list empty then <expr2>] | type of elements | Returns the arg-minimum element of the list (or an optional default) |
combine all <var> among <list> in <acc> initially <expr1> with <expr2> | type of elements | Folds <list> , starting with <expr1> and accumulating with <expr2> |
Note that these operations support multiple lists being iterated upon, like
map each (x, y) among (lst1, lst2) to x + y
, as long as they have the same length.
Tuples
The type (<type 1>, <type 2>)
represents a pair of elements, the first being
of type 1
, the second being type 2
. It is possible to extend the pair type
into a triplet type, or even an n
-uplet for an arbitrary number n
by
repeating elements after ,
.
You can build tuple values with the following syntax:
(|2024-04-01|, $30, 1%) # This values has type (date, money, decimal)
You can acess the n
-th element of a tuple, starting at 1
, with the syntax <tuple>.n
.
Functions
Function types represent function values, i.e values that require being called with some arguments to yield a result. Functions are first-class values because Catala is a functional programming language.
The general syntax for describing a function type is :
<type of result> depends on
<name of argument 1> content <type of argument 1>,
<name of argument 2> content <type of argument 2>,
...
For instance, money depends on income content money, number_of_children content integer
can be the type of a tax-computing function.
However, unlike most programming language, it is not possible to directly build a function as a value; functions are created and passed around with other language mechanisms.
Scopes, functions and constants
Since Catala in a functional language, toplevel declarations in Catala behave like functions. While Catala has true toplevel functions, we advice to limit their use in practice and use scopes instead, as only scopes benefit from the exceptions mechanism that is the killer feature of Catala.
Scope declarations
A scope is a function whose prototype (i.e. signature) is explicitly declared, and whose body can be scattered in little pieces across the literate programming codebase.
For information about the scope body, including definitions of scope variables, check out the reference sections about definition and exceptions or expressions.
Scope name
Scope names begin by a capital letter and follow the CamlCase convention. A
scope declaration is a toplevel item inside a Catala code block. For instance,
you want to name your scope Foo
, then its declaration begins by:
declaration scope Foo:
...
The contents of the scope declaration (inside the ...
) is comprised of:
- scope variable declarations ;
- sub-scopes called in the scope.
These two items are described below.
Scope variable declarations
Scope variables are akin to local variables in a function. Their declaration features:
- their input/output status;
- their name;
- their type;
- optionally, a list of states for the variable.
Variable names begin by a lowercase letter and follow the snake_case convention.
The syntax for the declaration of variable bar
is:
<input/output status> bar content <type> [<variable states list>]
Here is the syntax to declare scope Foo
with local variables
baz
, fizz
and buzz
; baz
is a boolean input, fizz
is an internal
date variable with two states before
and after
, and buzz
is a decimal
that is both input and output to the scope:
declaration scope Foo:
input baz content boolean
internal fizz content date
state before
state after
input output buzz content decimal
Explanations for input/output status and variable states follow below.
Input/output qualifiers
There are six kind of input/output qualifiers :
input
output
internal
context
input output
context output
Input variables
Input variables require a value to be provided when the scope is called, like a function argument. They cannot be redefined inside the scope.
Output variables
Output variables are defined inside the scope, and their value is returned as part of the scope call's result.
Internal variables
When a variable is neither an input nor an output of the scope, it needs to be
qualified as internal
.
Context variables
Context variables are basically optional inputs to the scope with a default
value defined inside the scope. Suppose scope Foo
has a context variable
bar
. If you provide a value x
for bar
when calling Foo
, Foo
's
computation will consider that bar = x
. But if you do not provide a value
for bar
when calling Foo
, Foo
will pick up as the value bar
the existing
definition of bar
inside Foo
.
This curious behavior for context
variable is motivated by use cases where
we want to create an exception for a scope variable from outside the scope.
See the Catala FAQ
for more explanations.
Input and output variables
Sometimes, we want a scope's input to be copied and transmitted as part of the
scope call's result. That is, such a variable would be both input
and
output
, which is why the syntax for this is input output
. In this case,
the variable cannot be defined inside the scope (as it is an input). This also
works when swapping input
by context
.
Variable state declarations
As mentioned in the tutorial, it is possible to distinguish several states during the computation of the final value of the variable. The states behave like a chain of variables, the next one using the value of the previous for its computation.
The ordered list of states the variable assumes during computation is declared
alongside the variable. For instance, if the internal integer variable foo
has states
a
, b
and c
in this order, then foo
shall be declared with:
internal foo content integer
state a
state b
state c
The order of the state
clauses in the declaration determines the computation
order between states for the variable. We can write foo state a
for the value of
variable foo
during state a
. With this order between a
, b
and c
, foo state b
can depend on foo state a
and foo state c
can depend on foo state b
, but not the converse.
Scope output structure
All variables qualified as output
in a scope will be part of the result of
the scope call. This result is materialized as a structure, with each
output variable mapping to a field of the structure. This output structure
of the scope is usable like any other structure type declared by the user.
For instance, if I have the scope declaration:
declaration scope Foo:
output bar content integer
output baz content decimal
internal fizz content boolean
input buzz content date
Then implicitly, an output structure also named Foo
will be usable as if it had the
following declaration:
declaration structure Foo:
data bar content integer
data baz content decimal
This implicit output structure declaration is useful to carry around the result of a scope call in another variable and re-use them later in the code.
Sub-scopes
Scopes are functions, and as such they can be called like functions. Calling a scope can be done inside any expression by simply providing the input variables as arguments.
But sometimes, we know that we will always call perform a single, static sub-scope call from a calling scope. For this situation, Catala has a special declarative syntax making it easier for lawyers to understand what is going on.
Sub-scopes are declared in the scope declaration like scope variables.
For instance, is inside scope Foo
you will call scope Bar
exactly one time,
then you will write:
declaration scope Foo:
bar_call scope Bar
bar_call
is the name (of your choosing) that will designate this specific
instance of calling Bar
inside Foo
. You can have multiple, static calls to the
same scope like:
declaration scope Foo:
first_bar_call scope Bar
second_bar_call scope Bar
You can also prefix by output
the line bar_call scope Bar
, ensuring
that the output structure of the call to Bar
will be present as field
bar_call
of the output structure of the call to Foo
.
As with structures and enumerations, it is not possible for these scope calls to be recursive.
Global constant and functions declarations
While scopes are the workhorse of Catala implementations, there are sometimes
small things that need not be inside a fully-fledged scope. For instance,
declaring small utility functions like excess_of
that computes the excess of the
first argument compared to the second, or declaring a constant for the number of
seconds in a day.
Since scopes are akin to functions, it is possible to use regular function declarations to emulate all the features of scopes. Why do scope exist in the first place then?
Because scopes allow the definition of exceptions for their variables, which is impossible with regular function and constant declarations. Exceptions are the killer feature of Catala, and using Catala without using exceptions makes no sense; use a regular programming language instead.
Hence, constant and functions declaration should only be used for their intended use. The rule of thumb is: if a constant or variable definition takes up more than a few lines of code, it should be turned into a scope!
Constants
Functions
Definitions and exceptions
This section of the Catala book has not yet been written, stay tuned for future updates!
Scope variable definitions
Assertions
Conditional definitions
Exceptions and priorities
Expressions
This section of the Catala book has not yet been written, stay tuned for future updates!
Pattern matching
Direct scope call
Updating structures
Modules
This section of the Catala book has not yet been written, stay tuned for future updates!
Inclusions
Metadata and visibility
Imports
External modules
Extra features
Attributes and extensions
The language can be extended with attributes, enabling a variety of extensions.
An attribute is of the form #[key.of.extension]
or #[key.of.extension = VALUE]
,
where VALUE
can be of the form "STRING"
, or an expression in catala syntax.
An attribute is always bound to the element directly following it: an extension
could for example make use of them to retrieve labels to the input fields of a
scope in order to generate a web form:
declaration scope SomeComputation:
#[form.label = "Enter the number of children satisfying the condition XXX"]
input children_of_age content integer
Built-in attributes
Some attributes affect the Catala compiler itself with built-in support.
Debug print
By adding #[debug.print]
in front of an expression in a Catala program, the
value of that expression will be printed upon computation by the interpreter,
when run with the --debug
option. It is otherwise ignored by the other
backends.
It is also possible to print the value along with a specific tag using
#[debug.print = "some debug tag"]
.
Note that, in some cases, due to how the compiler works, debug prints could
appear duplicated or not at all, especially if optimisations are enabled (with
the -O
flag). If that happens, try to move the attribute to the root of the
definition.