DECLARE

Declares a new type in L4. Types can be records (product types), enums (sum types), or type synonyms.

Syntax

DECLARE TypeName IS ...
DECLARE TypeName HAS ...
DECLARE TypeName IS ONE OF ...

Forms

Record Types (Product Types)

Records are types with named fields:

DECLARE Person
  HAS
    name IS A STRING
    age IS A NUMBER

Enum Types (Sum Types)

Enums define a type with multiple alternatives:

DECLARE Colour IS ONE OF red, green, blue

Enum constructors can have fields:

DECLARE Shape IS ONE OF
  Circle HAS radius IS A NUMBER
  Rectangle HAS width IS A NUMBER, height IS A NUMBER

Computed Fields (Methods)

Record fields can have a MEANS clause that defines a derived value — computed automatically from the record's other fields. These are analogous to methods, calculated properties, or derived attributes in other languages.

DECLARE Employee HAS
    -- stored fields (primary attributes)
    `name`             IS A STRING
    `date of birth`    IS A NUMBER
    `current year`     IS A NUMBER
    -- computed fields (derived attributes)
    `age`              IS A NUMBER
        MEANS `current year` - `date of birth`
    `adult`            IS A BOOLEAN
        MEANS `age` >= 18

Key points:

  • Computed fields are accessed with 's just like stored fields: employee's age``
  • Computed fields may depend on other computed fields (chaining)
  • Computed fields may call external functions using OF syntax: MEANS fOFx, y``
  • Computed fields may use WHERE and LET/IN for local bindings
  • When constructing a record with WITH, only stored fields are supplied — computed fields are derived automatically

Referential transparency: Computed fields are pure — they may only reference sibling fields of the same record. There is no way to add a GIVEN parameter to a MEANS clause inside a DECLARE; the only input is the record itself. This is a deliberate design choice rooted in functional programming: a computed field is a total function from the record's stored state to a derived value, with no hidden dependencies on external state or arguments. In the language of the lambda calculus, each computed field is a closed term over the record's own bindings. This purity guarantee means that computed fields are referentially transparent — evaluating employee's \age`` will always yield the same result for the same record, regardless of when or where it is called. It also makes cycle detection decidable: the compiler builds a dependency graph over a finite set of sibling fields and rejects any strongly connected component, ensuring termination.

Style guide: Group stored fields first, then computed fields, so readers see the primary data before the derived logic. Depart from this convention when expository clarity calls for a different ordering — for instance, placing a computed field immediately after the stored fields it depends on.

Example file:

-- Computed Fields Example
-- Demonstrates "methods" on records: fields with MEANS clauses
-- whose values are automatically derived from other fields.

IMPORT prelude

-- ─────────────────────────────────────────────────────────
-- Basic computed fields (methods)
-- ─────────────────────────────────────────────────────────
-- Style: stored fields first, then computed/derived fields.

DECLARE Employee HAS
    -- stored fields (primary attributes)
    `name`               IS A STRING
    `date of birth`      IS A NUMBER
    `date of employment` IS A NUMBER
    `monthly salary`     IS A NUMBER
    `current year`       IS A NUMBER
    -- computed fields (derived attributes / methods)
    `age`                IS A NUMBER
        MEANS `current year` - `date of birth`
    `years of service`   IS A NUMBER
        MEANS `current year` - `date of employment`
    `senior`             IS A BOOLEAN
        MEANS `years of service` >= 10
    `eligible`           IS A BOOLEAN
        MEANS `senior`
          AND `monthly salary` <= 6000

-- Construction: only stored fields are supplied.
-- Computed fields are derived automatically.
veteran MEANS Employee WITH
    `name`               IS "Lim"
    `date of birth`      IS 1960
    `date of employment` IS 1990
    `monthly salary`     IS 3000
    `current year`       IS 2026

-- Access computed fields with 's, just like stored fields.
#EVAL veteran's `age`            -- 66
#EVAL veteran's `years of service` -- 36
#EVAL veteran's `senior`         -- TRUE
#EVAL veteran's `eligible`       -- TRUE

-- ─────────────────────────────────────────────────────────
-- Calling external functions from computed fields
-- ─────────────────────────────────────────────────────────
-- Multi-argument calls can use juxtaposition, OF, or mixfix:
--   `apply rate` `balance` `rate`         -- juxtaposition
--   `apply rate` OF `balance`, `rate`     -- OF (comma-separated)
--   `apply` `balance` `at rate` `rate`    -- mixfix (natural language)
-- All are equivalent. Choose whichever reads best in context.

GIVEN `base` IS A NUMBER
      `rate` IS A NUMBER
GIVETH A NUMBER
`apply rate` MEANS `base` * `rate` / 100

DECLARE Account HAS
    `balance`   IS A NUMBER
    `rate`      IS A NUMBER
    `interest`  IS A NUMBER
        MEANS `apply rate` `balance` `rate`
    `projected` IS A NUMBER
        MEANS `balance` + `interest`

savings MEANS Account WITH
    `balance` IS 10000
    `rate`    IS 5

#EVAL savings's `interest`   -- 500.0
#EVAL savings's `projected`  -- 10500.0

-- ─────────────────────────────────────────────────────────
-- WHERE and LET/IN inside computed fields
-- ─────────────────────────────────────────────────────────

GIVEN `x` IS A NUMBER
GIVETH A NUMBER
`square` MEANS `x` * `x`

DECLARE Shape HAS
    `width`   IS A NUMBER
    `height`  IS A NUMBER
    `area`    IS A NUMBER
        MEANS `width` * `height`
    `diag sq` IS A NUMBER
        MEANS `result`
          WHERE
            `w2` MEANS `square` `width`
            `h2` MEANS `square` `height`
            `result` MEANS `w2` + `h2`
    `scaled`  IS A NUMBER
        MEANS LET `factor` MEANS 2 IN `area` * `factor`

rect MEANS Shape WITH
    `width`  IS 3
    `height` IS 4

#EVAL rect's `area`     -- 12
#EVAL rect's `diag sq`  -- 25
#EVAL rect's `scaled`   -- 24

Type Synonyms

Create an alias for an existing type:

DECLARE Age IS NUMBER
DECLARE PersonName IS STRING

Examples

Example file:

-- DECLARE keyword examples

-- Record Types (Product Types)

-- Basic record with multiple fields
DECLARE Person
  HAS
    `full name` IS A STRING
    age         IS A NUMBER

-- Record with single line syntax
DECLARE Point HAS x IS A NUMBER, y IS A NUMBER

-- Enum Types (Sum Types)

-- Simple enum with value constructors
DECLARE Colour IS ONE OF `bright red`, `forest green`, `ocean blue`

-- Enum with constructors that have fields
DECLARE Shape IS ONE OF
  Circle HAS radius IS A NUMBER
  Rectangle HAS width IS A NUMBER, height IS A NUMBER
  Point

-- Type Synonyms

DECLARE Age IS NUMBER
DECLARE PersonName IS STRING

-- Parameterized Types

GIVEN a IS A TYPE
DECLARE Box HAS contents IS AN a

GIVEN a IS A TYPE, b IS A TYPE
DECLARE MyPair HAS first IS AN a, second IS A b

-- Usage Examples

-- Create record instances
DECIDE `the applicant` IS Person WITH `full name` IS "John", age IS 30
DECIDE `the origin` IS Point WITH x IS 0, y IS 0

-- Use enum constructors
DECIDE `my favourite colour` IS `bright red`
DECIDE `my shape` IS Circle WITH radius IS 5

-- Use parameterized types
DECIDE `box with number` IS Box WITH contents IS 42
DECIDE `box with text` IS Box WITH contents IS "hello"

-- Evaluate examples
#EVAL `the applicant`'s `full name`
#EVAL `the applicant`'s age
#EVAL `my favourite colour`

Basic Record

DECLARE Customer
  HAS
    name IS A STRING
    email IS A STRING
    balance IS A NUMBER

Parameterized Types

Types can have type parameters:

GIVEN a IS A TYPE
DECLARE Box
  HAS
    contents IS AN a

Field Syntax Variations

L4 supports multiple syntaxes for fields:

-- Using IS A
DECLARE Person1 HAS name IS A STRING

-- Using colon
DECLARE Person2 HAS name: STRING

-- Using colon with article
DECLARE Person3 HAS name: A STRING

See Also