Tutorial 2 — Types and Relations¶
In len, all modelling is built from two primitives: types and relations. This tutorial shows you how to think in those terms, introduce a domain, and wire it together with imports.
Types¶
A type declaration introduces a named, opaque set of values.
Types carry no built-in structure. You cannot ask "does a Product have a name?" without also declaring a relation that captures that fact. This is intentional — it keeps the core language small and forces you to make relationships explicit.
Relations¶
A rel declaration describes a property of, or relationship between, typed values.
type Product
type String
type Money
rel HasName(p: Product, name: String)
rel HasPrice(p: Product, price: Money)
rel InStock(p: Product)
Relations replace what you might reach for methods or fields in an OOP language. HasName(p, name) is a proposition: it is either true or false for a given product and string. You do not define how it is true — only that it exists as a concept.
Relations are not methods¶
In Java you might write:
In len you write:
The difference: HasName makes no claim about uniqueness, about how the name is stored, or about calling conventions. It is a pure logical proposition.
Constants¶
const declares a named value with a type:
Constants are useful for canonical sentinel values and named domain primitives.
A Small Domain Model¶
Let's build a minimal e-commerce domain. Create shop.l1:
# Types
type Product
type Customer
type Order
type OrderItem
type Money
type String
type Nat
# Product catalogue relations
rel ProductName(p: Product, name: String)
rel ProductPrice(p: Product, price: Money)
rel ProductSku(p: Product, sku: String)
rel InStock(p: Product)
# Customer relations
rel CustomerName(c: Customer, name: String)
rel CustomerEmail(c: Customer, email: String)
# Order relations
rel OrderCustomer(o: Order, c: Customer)
rel OrderItem(o: Order, item: OrderItem)
rel ItemProduct(item: OrderItem, p: Product)
rel ItemQuantity(item: OrderItem, qty: Nat)
# Money relations
rel MoneyAmount(m: Money, amount: Nat)
rel MoneyCurrency(m: Money, code: String)
This is a pure structural model. No algorithms, no storage decisions, no SQL yet — just the vocabulary of the domain.
Imports¶
Larger projects split across files and use import to connect them.
Create types.l1:
Create catalogue.l1:
import shop.types
rel ProductName(p: Product, name: String)
rel ProductPrice(p: Product, price: Money)
rel ProductSku(p: Product, sku: String)
rel InStock(p: Product)
The module path shop.types maps to the file shop/types.l1 on disk. Validate the project by pointing at the directory:
Selective imports¶
If you only need a few names from a module:
Core library imports¶
The standard library ships under core:
import core.math.set # Set theory primitives
import core.math.nat # Natural numbers
import core.math.logic # Bool, Formula, Holds
Naming Conventions¶
| Form | Used for |
|---|---|
PascalCase |
Types, relations, constants |
snake_case |
Specs, functions, local variables |
dot.separated |
Module paths |
What You Learned¶
typenames an opaque set of values — it carries no structure by itselfreldeclares a logical proposition about typed values — the workhorse of lenconstnames a fixed value of a typeimportandfrom … importlet you split a model across files- Module paths are dot-separated and map directly to file paths
Next: Writing Specifications →