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: `clerk run 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:
clerk run 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: Deceased content fateful_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: Deceased content fateful_date }
We now define a Couple structure that represent a simple
household. This structure has three different entries:
- Two individuals:
person_1andperson_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 content 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 content 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:
clerk run 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
}