Modules
After scopes and toplevel declarations, modules are the second abstraction level in Catala. Precisely, modules are simply a group of scopes and toplevel declarations that form a separate compilation unit for the Catala compiler. As such, they present a public interface or API, whose elements can be referred to or called at runtime by another module.
Module declarations
A Catala source file can be turned into a module by inserting at the top a
module declaration. For instance, if the file is named foo.catala_en
,
the module declaration is simply:
> Module Foo
The name of the file and the name of the module in the module declaration should match, but differences in casing are allowed as module names have to be in CamlCase and file names are usually snake_case.
If you forget to put > Module Foo
at the top of your file, the file will
not be considered as a module by the clerk
build system. In particular,
you will not be able to call functions of this file from other modules.
Imports
Modules can "use" other modules to import their public types, scopes and
constants. If you want to use module Bar
inside module Foo
, the top of
foo.catala_en
should look like:
> Module Foo
> Using Bar
You can then refer to types, scopes and constants like of Bar
like Fizz
with
Bar.Fizz
inside Foo
. If you don't want to type in Bar.
each time, you can
give Bar
an alias inside Foo
with:
> Module Foo
> Using Bar as B
Then, B.Fizz
will refer to Bar.Fizz
.
Inclusions
Sometimes, the legal text and Catala code that have to fit inside a single
module are too big for one file. That is generally the case when the legal
specification for a given scope spans multiple legal sources that have
mutual references between them. In these cases, you want each legal source
to have its own .catala_en
file, while the scope implementation and encompassing
module to span all these files.
To accommodate this practice, Catala has a mechanism of textual inclusion that
lets you include the contents of one file in another. For instance, imagine
the module in the file benefit.catala_en
draws its implementation from the
files benefit_law.catala_en
, benefit_regulation.catala_en
, and benefit_instructions.catala_en
.
Then, the contents of benefit.catala_en
should look like this to include
all the other files:
> Module Benefit
> Include: benefit_law.catala_en
> Include: benefit_regulation.catala_en
> Include: benefit_instructions.catala_en
The order of the > Include
statements in benefit.catala_en
will determine
the order in which the contents of the file are copies over. While this order
has no influence over the semantic of the computation, it will change how things
are printed in the literate programming
backends.
What comes after the Include:
is actually a Unix-style file path, that can
refer to sub-directories or the parent directory (../
).
Public interface and visibility
The Catala team believes that programmers should control precisely which interface they make publicly available for their modules. Indeed, not exposing internal functions is the key the preserve the ability to refactor the code later without breaking the endpoints used by the clients of the module.
This is the reason why in Catala, all the type, scope and constant declarations
inside ```catala
blocks are private: they will not be accessible by other
modules. To make a type, scope or constant declaration public and therefore
accessible by other modules, you need to turn the containing ```catala
block into a ```catala-metadata
block. That's all!
Declaring external modules
Some modules contain logic that cannot be implemented in Catala. This is OK,
since Catala is a Domain-Specific Language (DSL) and not a general-purpose
programming language. This means that the features of Catala are intentionally
limited; for instance you cannot write recursive functions or manipulate strings in Catala. But sometimes, you need to call
from a regular Catala module a function containing this piece of
unimplementable logic. This is the purpose of external module. An external
module in Catala is a Catala source code file containing a module with the
external
annotation, like this:
> Module Foo external
```catala-metadata
declaration structure Period:
data date_begin content date
data date_end content date
declaration months_in_period content list of date
depends on period content Period
```
This external
module declares a type (Period
), as well as a toplevel
function (months_in_period
), but the latter has no implementation! This is by
design as external modules should not contain any Catala code, but merely type
declarations, scope declarations and toplevel constants and function
declarations. These declarations (inside ```catala-metadata
blocks) expose
a public interface for the external module, that can be used by other modules.
However, to make all this run in practice, you will still need to implement the external module in OCaml (for the interpreter) or in your target language. See the relevant reference section.