Constants
On this page
A constant names a fixed value at the top of a file. It is the same in
every deployment, worked out while compiling, and never changes.
type Stock = Int { min: 0}constant bulk_from: Stock = 5000constant small_batch: Stock = 250function batch_for(needed: Stock) -> Stock { if (needed >= bulk_from) { return 0 } return small_batch}
- Form.
constant name: Type = value. The name is lower_snake_case, like any value. There is no mutable form:constant mutissyntax.expected_name, whose edit removesmut. - Only at the top of a file. Inside a body a fixed value is always a
var, and at the top of a file always aconstant, so each place has one way to write it.constantin a body issyntax.expected_statement, whose edit writesvar. Avarat the top of a file issyntax.expected_declaration, whosenext:says to writeconstant.constat the top of a file issyntax.variable_keyword, whose edit writesconstant; in a body it is alsosyntax.variable_keyword, whose edit writesvar. - A named type. A constant's type follows the same rule as a
signature's (Signatures use named types):
constant limit: Int = 5istype.primitive_signature, aslimit: Intis in a parameter. A list of a named type, an object, an enum,Email,Urland the other types a signature may use are all fine. - A value the compiler can work out. Literals, enum values, other
constants, values constructed from those (
Stock(5000), an object, a list), and the operators of Expressions and operators on them. No call of any kind (functions, library functions, the standard library), noconfig, noclock, and no constant that depends on itself (constant.value_not_fixed,constant.cycle). Arithmetic gives the built-in type underneath, as it does in a body, soStock(bulk_from * 2)constructs aStockfrom it again. - Checked while compiling. The value is checked against every rule of
its type and of the types it is built on, as a literal is
(
type.invalid_literal, which names the value worked out). So constructing a value in a constant never needsattempt. When the app runs, the constant's value is worked out where it is read, and that can't fail. - Kept together.
jadpo fmtkeeps constants written one after another on consecutive lines, as you grouped them, and puts a blank line between every other pair of declarations. - Unused is an error, as for any other declaration. Beside a file's
primary declaration (the one the file is named after) it is
layout.unused_declaration. In a library, a constant nothing reads is unused, as a function nothing calls is.
Constants built from other constants, a list of them, and both read in functions:
type Stock = Int { min: 0 max: 100000}type LargeOrder = Bool {}type BatchCharged = Bool {}constant small_batch: Stock = 250constant large_batch: Stock = Stock(small_batch * 2)constant batch_sizes: List<Stock> = [small_batch, large_batch]function batch_chosen(large: LargeOrder) -> Stock { if (large) { return large_batch } return small_batch}function charged(batch: Stock) -> BatchCharged { return BatchCharged(batch in batch_sizes)}
Each of these mistakes is one error, with its edit:
type Stock = Int { min: 0}function batch() -> Stock { constant small_batch: Stock = 250 return small_batch}
error: `constant` doesn't start a statement --> main.jadpo:6:5 |6 | constant small_batch: Stock = 250 | ^^^^^^^^ write `var` reason: A constant lives at the top of a file or in a library; inside a body a value that never changes is a `var`. next: Use `var` instead of `constant` (agent) rule: syntax.expected_statement help: https://jadpo.dev/docs/errors/syntax.expected_statementcheck failed: 1 error, 1 file
type Stock = Int { min: 0}constant mut small_batch: Stock = 250
error: A constant can't be `mut` --> main.jadpo:5:10 |5 | constant mut small_batch: Stock = 250 | ^^^ remove `mut` reason: A constant never changes, so it has no mutable form; a value that changes is a `var mut` inside a body. next: Remove `mut` (agent) rule: syntax.expected_name help: https://jadpo.dev/docs/errors/syntax.expected_namecheck failed: 1 error, 1 file
type Stock = Int { min: 0}const small_batch: Stock = 250
error: Jadpo writes `constant`, not `const` --> main.jadpo:5:1 |5 | const small_batch: Stock = 250 | ^^^^^ write `constant` reason: `const` declares a value that never changes in JavaScript, TypeScript, Rust, Go and C#; at the top of a Jadpo file a fixed value is `constant name: Type = value`. next: Use `constant` instead of `const` (agent) rule: syntax.variable_keyword help: https://jadpo.dev/docs/errors/syntax.variable_keywordcheck failed: 1 error, 1 file
constant max_login_attempts: Int = 5error: This constant uses `Int` directly --> main.jadpo:1:30 |1 | constant max_login_attempts: Int = 5 | ^^^ a built-in type reason: A constant's type is named as a signature's is, so its value says what it means and carries its rules; `Int` says neither. next: Declare a named type built on `Int` and use it here (agent) rule: type.primitive_signature help: https://jadpo.dev/docs/errors/type.primitive_signaturecheck failed: 1 error, 1 file
Where a constant lives
A constant goes where a helper function would
(Sharing and promotion).
A constant one file uses lives at the top of that file, beside the
declaration that uses it. It is never a file's primary declaration, so
there is no constants/ folder, and its name never clashes with a file's
name: constant vat_rate: VatRate is fine, because
naming.folded_collision leaves constants out.
When a second file uses it, layout.promotion_required asks you to move
it into a library. Code then reads it through the library, like a library
function but without brackets:
type Stock = Int { min: 0}type HoldbackRate = Decimal { scale: 4 min: 0.0000 max: 1.0000}library stocking { /// The standard share held back. constant holdback_rate: HoldbackRate = 0.2000 function holdback_of(per_pack: Stock) -> Stock { return Stock(per_pack / 5) }}function standard_holdback() -> HoldbackRate { var uk_rate: HoldbackRate = stocking.holdback_rate return uk_rate}function holdback(per_pack: Stock) -> Stock { return stocking.holdback_of(per_pack)}
A library's own functions and constants read its constants by name alone, as they call its functions.
A library a second domain uses moves to shared/libraries/, or, when it
depends on its domain's types, stays and becomes public, as it would for
functions. A private constant's name must be unique only within its
domain, as private functions' and types' names are
(Files and names). Its
identity key, the name Jadpo's records know it by
(Stable identity), carries
its domain: constant billing.free_delivery_from.
Where a constant can be read
Anywhere a value can, functions included, because it never changes. It is also what "constants" means in the places that allow only fixed values:
- A query accepts a constant wherever it accepts a literal; anything
else there is
query.value_unsupported(Named queries). - An update by
idmay use parameters, constants,clock.nowand the stored fields (The operations every entity gets). - A computed backfill may use the record's stored fields, constants and pure functions (Computed backfills).
A setting's default: and test: take a literal, never a constant (see
A constant or a setting).
Two constants holding one value
Agents working at the same time tend to invent the same constant twice,
each writing its own vat_rate. Two constants of the same type holding
the same value are an error,
constant.similar_exists, as two
alike functions are
library.similar_exists. The error
names the other constant and its file, so the agent reads that one and
removes its own. The first in file and source order is the one named, and
the others are reported.
A VAT rate and a fee can share a value by coincidence, so the error is a
judgement call you can decline: a decline constant.similar_exists: "<why>" line above the constant answers it
(Declining a judgement call).
The library catalogue, the list of shared code that jadpo check writes
into build/catalogue/ (Tooling), lists each constant
with its type, value, /// description and the code that reads it.
A constant or a setting
If a deployment could ever need a different value, make it a setting in
config.jadpo (Configuration and time);
otherwise make it a constant. A page size an operator might tune is a
setting. The number of pence in a pound is a constant. The Jadpo agent
skill gives agents the same rule
(Tools for agents).
A value is one or the other, never both, so it has one source and one way
to read it. A constant is fixed and read as
security.max_login_attempts. A setting is chosen per deployment and read
as config.max_login_attempts.
So a setting's default: never names a constant
(configuration.default_literal_required). If it could, code reading the
constant directly would quietly ignore the deployment's value. Say
security.standard_attempts (5) were max_login_attempts's default, and
someone set MAX_LOGIN_ATTEMPTS=10: code reading
security.standard_attempts would still allow 5. You don't need the
constant anyway, because the setting's default: already holds the
default.
From fixed to live, there are three kinds of value:
- a
constantis fixed in the code; - a
configsetting is chosen per deployment; - a flag in
flags { }changes while the app runs (Feature flags).
Making a constant a setting is a job for a tool: jadpo config move <constant>, or the editor's "Make this a setting"
(Tooling). It adds a field of the same name and type to
config.jadpo whose default: is the constant's value, deletes the
constant, and rewrites every read (security.max_login_attempts becomes
config.max_login_attempts). The reviewer then sees each place whose
behaviour can now differ per deployment.
It refuses, editing nothing, a constant:
- whose type a setting can't hold;
- whose name a setting already has;
- read where a setting can't be: in a query, an update by
id, a backfill or another constant's value.
The details are in Making a constant a setting.
Values that change on a known date
Some values change on a date you know in advance, such as the UK's VAT
rate. Jadpo has no setting for this (there is no effective:). Write a
constant holding a dated list, and read it through a library function:
type VatRate = Decimal { scale: 4 min: 0.0000 max: 1.0000}type VatDate = CalendarDate {}type VatBand = Object { valid_from: VatDate band_rate: VatRate}type VatBands = List<VatBand> { min_length: 1}library vat { constant uk_bands: VatBands = [ VatBand { valid_from: "2010-01-01" band_rate: 0.1750 }, VatBand { valid_from: "2011-01-04" band_rate: 0.2000 } ] /// The UK standard rate on a date. function rate_on(day: VatDate) -> VatRate { var mut found: VatRate = 0.0000 for band in uk_bands { if (band.valid_from <= day) { found = band.band_rate } } return found }}function vat_rate_on(day: VatDate) -> VatRate { var vat_due_rate: VatRate = vat.rate_on(day) return vat_due_rate}
- A change of rate is an edit to the list, reviewed as a diff. Test it with the test clock on each side of the date (Testing).
- Make it an entity only when someone who isn't a developer must change it while the app runs.
- A record stores the rate it used (an invoice's
vat_ratefield), so a later change never rewrites what was charged.
Markdown for agents: /docs/constants.md.Built from Jadpo main at a707efb.