Troubleshooting L4 Errors
When something goes wrong in L4, the compiler tries to tell you what happened. This guide helps you understand those error messages and fix the underlying problems quickly.
If you already know what error you are looking at, use the table of contents below to jump to it. Otherwise, search this page for a keyword from your error message.
Contents
- Parse Errors
- Indentation Errors
- Type Errors
- Compiler Warnings
- Runtime Errors
- Common Gotchas
- Coming from Other Languages?
- See Also
Parse Errors
Parse errors happen before your code runs. They mean L4 could not understand the structure of what you wrote.
Unexpected THEN
Error message: Parse error: unexpected THEN at line N
What you wrote:
result MEANS
IF a THEN 1
ELSE IF b THEN 2
ELSE IF c THEN 3
ELSE 4
What went wrong: Nested IF/THEN/ELSE chains are fragile because each level adds more indentation, and L4's layout rules can get confused about which ELSE belongs to which IF.
How to fix it: Use BRANCH instead. It handles multiple conditions cleanly without nesting:
result MEANS
BRANCH
IF a THEN 1
IF b THEN 2
IF c THEN 3
OTHERWISE 4
BRANCH evaluates conditions top-to-bottom and returns the result for the first one that is TRUE. The OTHERWISE clause catches everything else. See Control Flow for more on BRANCH.
Missing ELSE branch
Error message: Parse error: expected ELSE after THEN
What you wrote:
result MEANS IF condition THEN value
What went wrong: In L4, IF/THEN/ELSE is an expression, not a statement. Every IF must produce a value regardless of whether the condition is TRUE or FALSE, so an ELSE branch is always required.
How to fix it: Provide an ELSE branch:
result MEANS IF condition THEN value ELSE default
If you genuinely only care about one case, consider using a guard or a BRANCH with an OTHERWISE in the surrounding context.
Pipes in sum types
Error message: Parse error when defining a sum type with |
What you wrote:
DECLARE Color IS ONE OF
Red | Green | Blue
What went wrong: L4 does not use the pipe character | to separate sum type constructors. If you are used to Haskell or similar languages, this is an easy mistake to make.
How to fix it: Use commas or newlines instead:
DECLARE Color IS ONE OF Red, Green, Blue
Or on separate lines:
DECLARE Color IS ONE OF
Red
Green
Blue
See Types for more on declaring sum types.
Bare positional payload in sum types
Error message: expecting HAS when putting a bare type after a constructor
What you wrote:
DECLARE Shape IS ONE OF
Circle NUMBER
What went wrong: Sum type constructors that carry data must use the HAS keyword with named fields. L4 does not support anonymous positional payloads like Haskell or Rust.
How to fix it: Name the field explicitly:
DECLARE Shape IS ONE OF
Circle HAS radius IS A NUMBER
Boolean literal case
Error message: Parse error: unknown identifier 'True'
What you wrote:
result MEANS True
What went wrong: Boolean literals in L4 must be fully uppercase. True, true, False, and false are not recognized.
How to fix it: Use TRUE and FALSE:
result MEANS TRUE
Parentheses required for field access
Error message: Parse error when using field access as a function argument
What you wrote:
all (GIVEN g YIELD g's age >= 18) charity's governors
What went wrong: When a field access expression (using 's) appears as an argument to a function, L4's parser cannot tell where the expression boundaries are. The field access needs to be explicitly grouped.
How to fix it: Wrap the field access in parentheses:
all (GIVEN g YIELD g's age >= 18) (charity's governors)
Indentation Errors
L4 is layout-sensitive, like Python. Indentation determines how code is grouped, replacing the braces and semicolons used in languages like JavaScript or Java. Getting indentation wrong is one of the most common sources of errors.
General rules:
- Use spaces, not tabs.
- Continuation lines must be indented more deeply than the line that introduced them.
- WHERE bindings should align vertically.
- The body of MEANS or DECIDE must be indented relative to the definition line.
Body less indented than definition
Error message: Parse error on the line after MEANS
What you wrote:
result MEANS
x + y
What went wrong: The body (x + y) is less indented than the keyword MEANS that introduces it. L4 expects the body to be indented further in.
How to fix it: Make sure the body is indented relative to the definition:
result MEANS
x + y
Tip: If your definition is already deeply indented, consider refactoring to reduce nesting.
Multi-line function arguments
Error message: Parse error on continuation lines of a function call
What you wrote:
r MEANS triple
100
200
What went wrong: When function arguments appear on continuation lines without the OF keyword, the parser cannot tell whether these lines are arguments to the function or new top-level expressions.
How to fix it: Use OF with commas for multi-argument calls:
r MEANS triple OF 100, 200, 300
Tip: The VS Code extension for L4 highlights layout boundaries, which helps catch these issues as you type.
Type Errors
Type errors mean L4 understood the structure of your code but found a logical inconsistency in how values are used.
Branch type mismatch
Error message: Type error: branch returns T1 but expected T2
What you wrote:
result MEANS
BRANCH
IF x < 0 THEN "negative"
IF x = 0 THEN 0
OTHERWISE "positive"
What went wrong: All branches of a BRANCH (or IF/THEN/ELSE, or CONSIDER) must return the same type. Here, the first and third branches return a STRING, but the second returns a NUMBER.
How to fix it: Make all branches return the same type:
result MEANS
BRANCH
IF x < 0 THEN "negative"
IF x = 0 THEN "zero"
OTHERWISE "positive"
Undefined field access
Error message: Error: record type T has no field 'fieldname'
What you wrote: An expression like person's fullname where the field fullname does not exist on the type.
What went wrong: You are accessing a field that is not defined in the type's DECLARE block. This is usually a typo or a misremembering of the field name.
How to fix it: Check the DECLARE definition for the type and verify the exact field name. Common mistakes include singular vs. plural (name vs. names) and different word choices (fullname vs. full name).
Function arity mismatch
Error message: Error: function expects N arguments, got M
What you wrote: A function call with too many or too few arguments.
What went wrong: The function was defined with a certain number of GIVEN parameters, and you provided a different number of arguments.
How to fix it: Check the function's definition to see how many arguments it expects, and provide exactly that many. If you intentionally want to supply fewer arguments (partial application), make sure the context supports it.
APPEND vs append
Error message: Unexpected type error involving strings or lists
What you wrote:
append "hello" "world"
What went wrong: L4's prelude defines two different functions that look similar but operate on different types:
| Function | Case | Style | Works on | Example |
|---|---|---|---|---|
APPEND |
uppercase | infix | STRING | "hello" APPEND " world" |
append |
lowercase | prefix | LIST | append (LIST 1, 2) (LIST 3, 4) |
Using the wrong one gives a type error because strings are not lists and vice versa.
How to fix it: Use uppercase APPEND (infix) for strings and lowercase append (prefix) for lists:
"hello" APPEND " world"
append (LIST 1, 2) (LIST 3, 4)
For string concatenation, you can also use CONCAT:
CONCAT "hello", " world"
Compiler Warnings
Warnings do not stop compilation, but they flag code that is likely to fail at runtime or that contains dead branches.
Non-exhaustive pattern match
Warning message: pattern matches are missing (the warning lists the uncovered branches)
What you wrote:
CONSIDER status
WHEN Active THEN "running"
WHEN Closed THEN "stopped"
What went wrong: The CONSIDER expression does not cover all possible constructors of the scrutinee's type. If status could be Suspended (or any other constructor you did not list), there is no branch to handle it. This is a compile-time warning (not an error): the program still compiles and runs, but evaluating the match on an uncovered value raises a runtime error (see Non-exhaustive patterns at runtime).
How to fix it: Either cover all constructors explicitly or add an OTHERWISE branch:
CONSIDER status
WHEN Active THEN "running"
WHEN Closed THEN "stopped"
OTHERWISE "unknown"
Note: Exhaustiveness analysis is skipped when the scrutinee has type NUMBER, STRING, or DATE — these types have effectively infinite value sets, so the analysis (designed for algebraic data types with a finite constructor set) does not apply. Matches on such values get no warning even when incomplete; use OTHERWISE to be safe. BOOLEAN is analysed normally, and the analysis reaches CONSIDER expressions inside WHERE- and LET-bound local definitions; the builtin container types MAYBE, EITHER, and LIST are not yet analysed. Warnings never block evaluation — a file with warnings still runs its #EVAL directives.
Redundant pattern match branch
Warning message: pattern match branches are redundant (the warning lists the unreachable branches)
What went wrong: A WHEN branch can never be reached because earlier branches (or an earlier OTHERWISE) already cover every value it could match.
How to fix it: Delete the unreachable branch, or reorder branches if a more specific pattern was accidentally placed after a more general one.
Runtime Errors
Runtime errors occur when the code is well-formed and well-typed but does something problematic during evaluation.
Circular definition
Symptom: Evaluation hangs or produces a stack overflow
What you wrote:
x MEANS x + 1
What went wrong: The definition refers to itself without ever reaching a base case. When L4 tries to evaluate x, it needs x + 1, which needs x, which needs x + 1, and so on forever.
How to fix it: If you intend recursion, make sure there is a base case that stops the cycle:
factorial MEANS
IF n <= 1 THEN 1
ELSE n * (factorial (n - 1))
If you did not intend recursion, you probably meant to reference a different variable. Check your names.
Non-exhaustive patterns at runtime
Error message:
The value
Withdrawn
reached a CONSIDER that has no branch for it.
Add a WHEN branch for this case, or a catch-all OTHERWISE branch.
The typechecker's exhaustiveness warning lists all missing branches.
What went wrong: Evaluation reached a CONSIDER whose branches do not cover the actual value of the scrutinee (shown in the message). Either the compile-time warning was ignored, or the value escaped the analysis — in particular matches on NUMBER, STRING, or DATE scrutinees, for which exhaustiveness checking is skipped (see Non-exhaustive pattern match under Compiler Warnings). A directive that crashes this way makes l4 run exit non-zero.
How to fix it: Add branches for the missing cases, or add an OTHERWISE branch as a catch-all. For matches on NUMBER, STRING, or DATE values, always include OTHERWISE.
DEONTIC rule does not execute
Symptom: You defined a DEONTIC rule (MUST, MAY, SHANT) but nothing happens when you evaluate it.
What went wrong: DEONTIC rules produce effects (obligations, permissions, prohibitions) rather than plain values. They need to be evaluated using the #TRACE directive, which generates a state graph showing the obligations and their consequences.
How to fix it: Use #TRACE to evaluate your rule:
#TRACE ruleFunction context
See Regulative Rules for more on deontic logic in L4.
Common Gotchas
These are not always error messages per se, but they trip up many L4 users.
Equality uses single equals
What you wrote:
result MEANS x == 5
What went wrong: L4 uses a single = for equality comparison. The double == from Python, JavaScript, Java, and many other languages does not exist in L4.
How to fix it:
result MEANS x = 5
This is consistent with mathematical notation and feels natural once you are used to it. L4 does not have variable assignment, so there is no ambiguity.
Missing IMPORT prelude
Error message: Error: undefined function 'map' (or filter, all, any, etc.)
What went wrong: Standard library functions like map, filter, all, any, and append are defined in the prelude. If you are getting "undefined" errors for these common functions, the prelude may not be imported.
How to fix it: Add this line at the top of your file:
IMPORT prelude
See Libraries for the full list of available libraries.
Module not found
Error message: Error: cannot resolve import 'modulename'
What went wrong: L4 could not find the module you are trying to import. L4 searches for modules in this order:
- Virtual filesystem (VFS) provided by the IDE or service
JL4_LIBRARY_PATHenvironment variable (if set)- Project root directory / relative to the importing file
- XDG data directory (
~/.local/share/jl4/libraries/) - Bundled with the VSCode extension (
../../libraries/from executable) - Embedded standard libraries compiled into the binary
When JL4_LIBRARY_PATH is set, embedded libraries (step 6) are skipped. This gives operators full control over which libraries are available.
How to fix it:
- Check the module name for typos.
- For your own modules, place them in the project directory or set
JL4_LIBRARY_PATH. - For third-party libraries, install them to
~/.local/share/jl4/libraries/ - If
JL4_LIBRARY_PATHis set, ensure it contains the standard libraries you need (e.g.,prelude.l4).
WHERE scope issues
Error message: Error: undefined variable in WHERE clause
What went wrong: A variable you referenced in a WHERE binding does not exist. Note that WHERE bindings can see the outer scope and can reference each other, so the issue is usually a typo or a missing definition rather than a scoping limitation.
How to fix it: Double-check that the variable you are referencing is defined either in an enclosing scope or in another binding within the same WHERE block.
Missing type annotations
Symptom: Yellow warning in the IDE, or confusing type error messages
What went wrong: L4 can infer types without annotations, but omitting them sometimes leads to ambiguous inference or unhelpful error messages when something goes wrong downstream.
How to fix it: Add GIVEN and GIVETH (or GIVES) annotations to top-level definitions:
GIVEN person IS A Person
GIVETH A BOOLEAN
DECIDE `the person is eligible` IF
person's age >= 18
Explicit types serve as documentation, produce better error messages, and prevent inference ambiguity.
Coming from Other Languages?
If you have experience with other programming languages, this translation table will help you avoid the most common syntax mistakes.
| What you might write | What L4 uses | Notes |
|---|---|---|
== |
= |
Single equals for equality |
True / true |
TRUE |
Uppercase boolean literals |
False / false |
FALSE |
Uppercase boolean literals |
null / nil / None |
NOTHING |
Used with the MAYBE type |
Nothing / Just |
NOTHING / JUST |
Uppercase constructors |
% |
MODULO |
Keyword, not symbol |
. (dot access) |
's |
Genitive/possessive field access |
++ / + (strings) |
CONCAT or APPEND |
String concatenation |
def / fn / function |
MEANS or DECIDE |
Definition keywords |
return |
(not needed) | L4 is expression-based |
; |
(not needed) | Layout-sensitive, uses indentation |
{ } |
(not needed) | Uses indentation instead of braces |
| (sum types) |
, or newline |
Sum type constructor separators |
Constructor Type |
Constructor HAS field IS A Type |
Named fields, not positional |
See Also
- Syntax Reference -- Layout rules, identifiers, annotations, and other structural syntax
- Types Reference -- Type system, DECLARE, sum types, and polymorphic types
- Control Flow -- IF/THEN/ELSE, BRANCH, CONSIDER
- Operators Reference -- Arithmetic, comparison, logical, and string operators
- Functions Reference -- GIVEN, GIVETH, MEANS, DECIDE, WHERE
- Libraries Reference -- Prelude and other importable libraries
- GLOSSARY -- Master index of all language features