Algebraic Types in L4
Why L4 builds its data model from algebraic data types — and why those types are a natural fit for legal structure.
What Are Algebraic Types?
Algebraic data types (ADTs) are types built from two fundamental operations:
- Product types: Combine multiple values (AND)
- Sum types: Choose between alternatives (OR)
L4 uses ADTs extensively because they naturally map to legal structures. This article explains the mapping; for the full syntax of declaring and using types, see the Types Reference and DECLARE.
Product Types: Records
A product type combines multiple values together. In L4, these are records:
DECLARE Person
HAS `name` IS A STRING
`age` IS A NUMBER
`email` IS A STRING
The set of possible Person values is the product (cross-product) of all field types:
Person = STRING × NUMBER × STRING
= {all possible names} × {all possible ages} × {all possible emails}
Records model legal entities — the bundle of facts a legal instrument cares about:
DECLARE Contract
HAS parties IS A LIST OF Party
`effective date` IS A DATE
obligations IS A LIST OF Obligation
For record construction and field access syntax (Person "Alice Smith" 30 "alice@example.com", alice's name), see the Types Reference.
Sum Types: Enumerations
A sum type represents a choice between alternatives. In L4, we use IS ONE OF:
DECLARE Status IS ONE OF
Active
Suspended
Terminated
The set of possible Status values is the sum (union) of the variants:
Status = Active ∪ Suspended ∪ Terminated
= 3 possible values
Variants can also carry data of their own:
DECLARE `payment status` IS ONE OF
Pending
Paid HAS `date paid` IS A DATE
amount IS A NUMBER
Rejected HAS reason IS A STRING
A Paid value always has a date and an amount; a Rejected value always has a reason; a Pending value carries nothing. Each alternative carries exactly the data that is relevant to it — there are no unused or "not applicable" fields.
For the full enumeration syntax, see DECLARE and the
-- Example: Enum type declaration
-- Demonstrates sum types with named constructors
DECLARE Color IS ONE OF
Red
Green
Blue
-- Example usage
`my favorite color` MEANS Red
`name of the color` MEANS
CONSIDER `my favorite color`
WHEN Red THEN "red"
WHEN Green THEN "green"
WHEN Blue THEN "blue"
#EVAL `name of the color`
.
Pattern Matching and Exhaustiveness
To work with sum types, use CONSIDER:
GIVEN status IS A `payment status`
GIVETH A STRING
`describe status` MEANS
CONSIDER status
WHEN Pending THEN "Payment pending"
WHEN Paid d a THEN "Paid"
WHEN Rejected r THEN r
Exhaustiveness
L4 checks that a CONSIDER over an algebraic type covers all of its constructors. If a case is missing, the compiler emits a warning:
-- ⚠️ Warning: "The following branches still need to be considered: WHEN Rejected THEN"
CONSIDER status
WHEN Pending THEN "..."
WHEN Paid d a THEN "..."
The checker also warns about redundant branches that can never be reached. Two caveats:
- The check applies to algebraic types with a known, finite set of constructors. Scrutinees of primitive types (
NUMBER,STRING,DATE) are not checked, because their values cannot be enumerated. - A missing case is a warning, not a hard error — the program still compiles and runs.
See Exhaustiveness for why this check matters legally, and why it is a warning rather than an error.
Otherwise
OTHERWISE is a catch-all branch:
CONSIDER status
WHEN Pending THEN "Not yet processed"
OTHERWISE "Already processed"
Note the trade-off: OTHERWISE silences the exhaustiveness check. If a new variant is added to the type later, the catch-all will absorb it silently instead of prompting you to decide how it should be handled.
Why Algebraic Types Map to Legal Structure
This is the conceptual heart of the matter: legal drafting already uses the algebra, just informally.
Closed enumerations are statutory categories
Legal definitions are typically closed lists:
A "person" means— (a) a natural person; or (b) a body corporate; or (c) a partnership.
This maps directly to a sum type:
DECLARE Person IS ONE OF
`natural person` HAS ...
`body corporate` HAS ...
partnership HAS ...
The closure is the point. Nothing is a Person unless it is one of the three listed alternatives — and nothing becomes one without amending the declaration, just as nothing enters the statutory definition without amending the statute. An open-ended encoding (say, a STRING holding "natural person") cannot express this: it admits values the law never defined, and the type checker has no way to notice.
Sum types are alternative legal statuses
A legal status is usually mutually exclusive: a contract is draft or executed or terminated; a payment is pending or paid or rejected — never two at once. A sum type builds that exclusivity into the data model.
Compare the flattened alternative — a record of booleans:
-- ❌ Admits impossible states
DECLARE ContractFlags
HAS `is draft` IS A BOOLEAN
`is executed` IS A BOOLEAN
`is terminated` IS A BOOLEAN
This type has 8 possible values, of which at most 3 describe a legally coherent contract. What does it mean for a contract to be simultaneously draft and terminated? The sum type version has exactly the 3 values the law contemplates. Illegal states are not merely avoided — they are unrepresentable.
Products are required particulars
Where legal text prescribes what an entity must comprise, that is a product type:
A "registered charity" must have—
- a registered name
- a registration number
- at least one charitable purpose
DECLARE `registered charity`
HAS `name` IS A STRING
`registration number` IS A STRING
purposes IS A LIST OF Purpose
A value of this type cannot be constructed with a particular missing — the type checker enforces the completeness that the statute demands.
Variants with data mirror case-specific particulars
Legal consequences often depend on which alternative applies, and each alternative comes with its own relevant facts: a rejected application has grounds for rejection; a granted one has a grant date. Attaching data to variants keeps each case's facts with that case, instead of a wide record where most fields are meaningless most of the time.
Exhaustiveness is complete case analysis
A legal determination over a closed category must say what happens in every case — a tax rule that classifies entities must have an answer for each entity type:
CONSIDER entity
WHEN `natural person` p THEN `personal tax rules` p
WHEN `body corporate` c THEN `corporate tax rules` c
WHEN partnership p THEN `partnership tax rules` p
-- The compiler warns if a case is missing
When the legislature adds a fourth category, adding the variant to the declaration makes the checker flag every determination that has not yet decided how to handle it. The type system turns "we forgot about partnerships" from a silent gap into a visible diagnostic. This property is developed fully in Exhaustiveness.
Combining Product and Sum
Real models nest both freely:
-- Sum type whose variants are products
DECLARE Employment IS ONE OF
Employed HAS employer IS A Company
`start date` IS A DATE
salary IS A NUMBER
`Self-employed` HAS `business name` IS A STRING
Unemployed HAS since IS A DATE
-- Product type containing a sum type
DECLARE Worker
HAS `name` IS A STRING
`employment status` IS AN Employment
And pattern matching reaches inside:
GIVEN worker IS A Worker
GIVETH A BOOLEAN
`is currently employed` MEANS
CONSIDER worker's `employment status`
WHEN Employed e s sal THEN TRUE
OTHERWISE FALSE
Recursive Types
Types can reference themselves, which is how nested, tree-shaped legal structures are modeled — most obviously the structure of a legal document itself:
DECLARE `legal document` IS ONE OF
Section HAS number IS A STRING
title IS A STRING
content IS A LIST OF `legal document`
Paragraph HAS text IS A STRING
Definition HAS term IS A STRING
meaning IS A STRING
A section contains subsections, which contain paragraphs and definitions — arbitrarily deep, out of one three-variant declaration.
Common Patterns
Optional Values: MAYBE
DECLARE Person
HAS `name` IS A STRING
`middle name` IS A MAYBE STRING -- Optional
MAYBE is itself a sum type — conceptually:
MAYBE a = NOTHING | JUST a
Optionality is explicit in the type, and CONSIDER ... WHEN NOTHING ... WHEN JUST x ... forces both possibilities to be handled. There is no null. See the
-- Example: Maybe type usage
-- Demonstrates optional values with JUST and NOTHING
`value is present` MEANS JUST OF 42
`value is absent` MEANS NOTHING
`get the value or default to zero` MEANS
CONSIDER `value is present`
WHEN JUST OF x THEN x
WHEN NOTHING THEN 0
#EVAL `get the value or default to zero`
.
Lists
Lists are also algebraic — conceptually a sum of "empty" and "an element followed by a rest", matched with:
CONSIDER items
WHEN EMPTY THEN "no items"
WHEN x FOLLOWED BY rest THEN "at least one item"
See the
-- Example: List type usage
-- Demonstrates list literals and cons operator
-- List literal syntax
`first list` MEANS LIST 1, 2, 3
-- Cons operator (FOLLOWED BY)
`second list` MEANS 1 FOLLOWED BY 2 FOLLOWED BY 3 FOLLOWED BY EMPTY
#EVAL `first list`
#EVAL `second list`
.
Summary
| Concept | L4 Syntax | Mathematical View | Legal Reading |
|---|---|---|---|
| Product type | DECLARE X HAS field1, field2 |
X = A × B | Required particulars |
| Sum type | DECLARE X IS ONE OF A, B, C |
X = A + B + C | Closed statutory category |
| Sum with data | A HAS field IS A Type |
Tagged union | Case-specific particulars |
| Pattern match | CONSIDER x WHEN ... |
Case analysis | Complete determination |
| Optional | MAYBE Type |
1 + Type | Explicitly optional fact |
| List | LIST OF Type |
1 + (Type × List) | Repeating particulars |
Further Reading
- Exhaustiveness - Totality of determinations as a legal-safety property
- Types Reference - Detailed type reference
- Type Theory - Formal foundations of the type system
- Foundation Course Module 2 - Hands-on with types