Language design · July 29, 2026
50% Declarative, 50% Imperative
Two modes of execution. Same baseline.
Describe the world
Declarative code is wonderful when the shape is the point. You say what should exist and leave the machinery of making it exist to the language. Imperative code is wonderful when the path matters: inspect something, branch on it, transform it, reject it, or make a slightly strange decision.
Range wants both, with a visible border between them.
macro project(): Construct {
#environment {
construct ProjectDefaults {
#environment.system.defaults.map { default in
let #default.identifier: #default.value
}
}
}
}The declarative half
Inside #environment, the macro describes a piece of the
program’s #environment. It says that a ProjectDefaults construct exists and shows the members that belong inside it. There is no
builder to push into, no syntax tree to assemble, and no sequence of
mutation calls pretending to be a language.
The block reads like Range because it is Range. It is the desired program shape, written directly.
The imperative half
The contents do not have to be static. A macro can query the system,
iterate over its defaults, and project each result into the declaration.
The map is compile-time execution: ordinary control and data
flow used to produce declarative code.
The macro’s target is also its access type. A macro targeting a Construct receives the typed declaration and members of that
construct; a macro targeting another kind of syntax receives the surface
appropriate to that code. The type says what written code may be queried.
- Query the code exposed by the macro’s access type.
- Filter, map, branch, and validate with ordinary Range.
- Emit declarations in the same syntax programmers use everywhere else.
macro equatable(): Construct {
let properties: [@property](
#environment.target.Declaration.members.filter(all: @property)
)
let values: [@stored](
properties.filter(all: @stored)
)
#environment {
extension #environment.target.Declaration.identifier {
function equals(_ other: Self): Bool {
#values.map { property in
if self.#property.identifier != other.#property.identifier {
return false
}
}
return true
}
}
}
}The macro performs the complete synthesis. Its broad @property query observes stored, derived, and binding nodes,
so changes to that property graph invalidate and re-identify the generated
implementation. Runtime equality then narrows to @stored values, avoiding redundant derived computation or externally backed
bindings. It emits an equals function and generates one
short-circuiting comparison per stored value. An empty construct naturally
reaches return true.
Case iterable
An Enum access type exposes its cases and each case’s
associated values. A modern @caseIterable macro can validate
that every case is payload-free, then project those written cases into
one generated collection.
macro caseIterable(): Enum {
let cases: [Enum.Case](
#environment.target.Declaration.cases
)
let payloadCases: [Enum.Case](
cases.filter { item in
return item.associatedValues.count != 0
}
)
if payloadCases.count != 0 {
#environment.error(
"@caseIterable requires cases without associated values"
)
}
#environment {
extension #environment.target.Declaration.identifier {
function allCases(): [Self] {
return [
#cases.map { item in
.#item.identifier,
}
]
}
}
}
}This is also a forward-looking derivation example. The important part is that the macro receives typed enum syntax, not an unrestricted view of the whole program.
Outside the boundary, decide what should happen. Inside it, describe what should exist.
The useful middle
“Declarative or imperative” is a false choice. A fully declarative system becomes awkward the moment its vocabulary runs out. A fully imperative metaprogramming system makes simple shapes noisy and hides intent behind construction APIs.
The interesting place is fifty-fifty: declarations for the stable shape, a real programming language for the funky parts, and one explicit boundary where values become code. That is the direction of Range’s project macro. The compiler is being brought up to meet the spelling.