Towards L4 Mastery
Functional programming for legal professionals
7.1 The Functional Mindset: "Think More in a Haskell Way"
Critical insight: L4 is fundamentally a functional programming language. Understanding this is essential for writing correct L4 code.
What does "functional" mean for lawyers?
Functions over procedures: Instead of step-by-step instructions, you define what something IS
Immutable data: Legal facts don't change—you create new states rather than modifying existing ones
Composition: Complex legal rules are built by combining simpler ones
Type safety: The computer prevents logical errors by checking that everything "fits together"
Example transformation from procedural to functional thinking:
❌ Procedural mindset:
-- "Step 1: Check if application complete"
-- "Step 2: Check if purposes charitable"
-- "Step 3: If both true, register charity"
✅ Functional mindset:
-- Define what completeness IS
GIVEN applicant IS A RegisterEntry
GIVETH A BOOLEAN
`application is complete` MEANS
`constitution is written` applicant
AND `has at least one purpose` applicant
AND `has valid public benefit statement` applicant
-- Define what charitable purposes ARE
GIVEN charity IS A RegisterEntry
GIVETH A BOOLEAN
`all purposes are charitable` MEANS
all (GIVEN p YIELD `is charitable purpose` p) (charity's purposes)
-- Define registration criteria using the above
GIVEN applicant IS A RegisterEntry
GIVETH A BOOLEAN
`should register` MEANS
`application is complete` applicant
AND `all purposes are charitable` applicant
Why this matters: Legal reasoning is naturally functional—we define criteria and apply them, rather than executing procedures.
7.2 Function Application Precedence - The Critical Pattern
The most important syntax rule in L4: Function application has higher precedence than field access.
This causes systematic errors that look like this:
unexpected 's
expecting &&, (, *, +, -, /, ;, <, <=, =, >, >=, AND, OR...
Problem examples and solutions:
❌ Wrong - Parser confusion:
length applicant's purposes > 0 -- Parser sees: (length applicant)'s purposes > 0
all (GIVEN p YIELD...) charity's purposes -- Parser sees: (all (GIVEN p YIELD...) charity)'s purposes
elem governor charity's governors -- Parser sees: (elem governor charity)'s governors
✅ Correct - Use parentheses:
length (applicant's purposes) > 0
all (GIVEN p YIELD...) (charity's purposes)
elem governor (charity's governors)
The pattern: When passing field access as function arguments, always wrap in parentheses.
Why this happens:
L4 follows Haskell-like precedence:
function application > field access > comparison > boolean operators
length applicant's purposes
is parsed as(length applicant)'s purposes
The parser expects
length
to be applied toapplicant
, then tries to accesspurposes
field of the resultBut
applicant
is not a list, solength applicant
is a type error, and the parser gets confused
7.3 Field Access Patterns - Possessive vs Functional
L4 supports both possessive syntax and function application for field access.
When possessive syntax works:
applicant's purposes -- ✅ Fine in simple contexts
charity's constitution's isWritten -- ✅ Chained access works
governor's convictions -- ✅ Basic field access
When you MUST use function application:
-- In EXISTS expressions (possessive not supported)
❌ EXISTS c IN list SUCH THAT c's isSpent EQUALS FALSE
✅ EXISTS c IN list SUCH THAT isSpent c EQUALS FALSE
-- In nested function calls (precedence issues)
❌ length applicant's purposes
✅ length (applicant's purposes)
-- In quantifier arguments
❌ all (GIVEN p YIELD...) charity's purposes
✅ all (GIVEN p YIELD...) (charity's purposes)
Convert possessive to functional:
object's field
→field object
object's field1's field2
→field2 (field1 object)
For deeply nested access:
-- Possessive (can be problematic in complex expressions)
(lastFinancialRecord charity)'s financialYear's endDate
-- Functional (always works)
endDate (financialYear (lastFinancialRecord charity))
7.4 List Operations and Quantifiers
Key insight: Use prelude functions instead of inventing syntax.
❌ Non-existent syntax:
EXISTS c IN list SUCH THAT condition -- Not valid L4
EVERY x IS A Type WHERE condition -- Not valid L4
FOR ALL x IN list CHECK condition -- Not valid L4
✅ Use prelude functions:
-- For "exists" - use `any`
any (GIVEN c YIELD condition) list
-- For "all" - use `all`
all (GIVEN p YIELD condition) list
-- For "none" - use NOT and any
NOT any (GIVEN x YIELD condition) list
-- For filtering - use `filter`
filter (GIVEN x YIELD condition) list
Pattern for quantified conditions:
-- Template: quantifier (GIVEN variable YIELD condition) list
-- Example: "All governors have no unspent convictions"
all (GIVEN gov YIELD NOT any (GIVEN c YIELD isSpent c EQUALS FALSE) (gov's convictions)) governors
-- Example: "Some purpose is charitable"
any (GIVEN p YIELD `is charitable purpose` p) purposes
-- Example: "No governor is disqualified"
NOT any (GIVEN gov YIELD gov's isDisqualified EQUALS TRUE) governors
7.5 Type Safety and Missing Functions
Problem: L4's prelude is minimal—common functions may be missing.
Systematic approach:
Check if function exists in prelude (like
length
,elem
,filter
)Define missing functions at the top of your file
Use proper types (don't mix STRING and LIST operations)
Essential missing functions to define:
-- String length (often needed, not in prelude)
GIVEN str IS A STRING
GIVETH A NUMBER
stringLength str MEANS
-- Implementation depends on L4 version
0 -- Placeholder, or use string comparison patterns
-- List length (also missing from prelude)
GIVEN a IS A TYPE
list IS A LIST OF a
GIVETH A NUMBER
length list MEANS
CONSIDER list
WHEN EMPTY THEN 0
WHEN x FOLLOWED BY xs THEN 1 + length xs
-- Safe list access
GIVEN a IS A TYPE
list IS A LIST OF a
index IS A NUMBER
GIVETH A MAYBE a
safeAt list index MEANS
IF index < 0 OR index >= length list
THEN NOTHING
ELSE JUST (at list index)
Type checking patterns:
-- ❌ Type error: mixing string and list operations
length someString > 0 -- length expects LIST, gets STRING
-- ✅ Correct: check string non-emptiness
NOT someString EQUALS "" -- STRING comparison
-- ✅ Correct: check list non-emptiness
length someList > 0 -- LIST operation
7.6 Boolean Logic and Comparison Patterns
Prefer direct comparisons over negation:
❌ Unnecessarily complex:
NOT c's isSpent EQUALS TRUE
NOT governor's isDisqualified EQUALS TRUE
✅ Cleaner and clearer:
c's isSpent EQUALS FALSE
governor's isDisqualified EQUALS FALSE
Comparison precedence patterns:
-- ✅ Simple comparisons work naturally
age >= 18
amount > 1000
date EQUALS today
-- ✅ Parentheses for complex expressions
(length purposes) > 0
(governor's age) >= minimumAge
(charity's income's amount) > threshold
Boolean combination patterns:
-- AND/OR precedence (AND binds tighter than OR)
condition1 AND condition2 OR condition3 -- Means: (condition1 AND condition2) OR condition3
condition1 OR condition2 AND condition3 -- Means: condition1 OR (condition2 AND condition3)
-- Use parentheses for clarity
(condition1 OR condition2) AND condition3
condition1 AND (condition2 OR condition3)
7.7 Systematic Debugging Approach
When you see syntax errors, follow this checklist:
Function precedence: Are field accesses in function arguments wrapped in parentheses?
Missing functions: Is the function defined in prelude or your file?
Type mismatches: Are you using STRING operations on LISTs or vice versa?
Possessive syntax: Can you convert to function application form?
Boolean logic: Are comparisons and boolean operations correctly parenthesized?
Error patterns and fixes:
Error: "unexpected 's"
→ Fix: Add parentheses around field access in function arguments
Error: "could not find definition for LENGTH"
→ Fix: Define length function at top of file
Error: "expecting BOOLEAN, got STRING"
→ Fix: Use correct comparison (EQUALS "", not length > 0)
Error: "could not find definition for EXISTS"
→ Fix: Use any (GIVEN x YIELD condition) list
Success Check: You now understand L4's functional nature, can handle precedence correctly, use proper quantifiers, and systematically debug type and syntax errors.
Last updated