Design

Money is exact by default

1234.50 is a Decimal — a scaled integer over BigInt, not a binary float. A language for business systems cannot get money wrong unless you ask it to.

(0.1 + 0.2) printNl.                  "0.3"
(1.00 / 3) printNl.                   "refused: not exact"
((1.00 / 3) roundedTo: 2) printNl.    "0.33, and you wrote where"

Division is exact or it refuses. Rounding is spelled out with its direction, in the words a business uses. An Int is a BigInt too, because an order number past 253 is ordinary and losing digits there is the same mistake as rounding money.

A date is a calendar date

Date has no time and no zone. new Date('2026-08-10').getDate() answering 9 in Tokyo is not a hazard this language has. Arithmetic is civil: a month later than the 31st is the end of the next month. An Instant is the absolute one, and converting between them makes you name the zone.

JSON keeps the digits it arrived with

JSON.parse turns 1234567890123456789.12 into 1234567890123456800, which would undo the Decimal in one round trip. So JSON is read here, and a number stays the text it arrived as until something asks it for a type:

(j at: 'total') asDecimal        "Some(1234567890123456789.12)"

Navigation never throws: at: on something that is not an object answers the null Json, so a path can be walked without a check at every step, and the decision happens once at the leaf, where a Maybe comes back.

Types, and the one gap

Hindley-Milner with levels, no subtyping, no rows. A dynamic send is typed in three tiers: a receiver already known resolves on the spot; a receiver still a variable is deferred and resolved when something pins it down; a receiver never determined is accepted. That third tier is the deliberate soundness gap, and what it admits is code that cannot execute — reaching such a send needs a value whose type was never decided, and constructing one decides it.

Classes are nominal, there is no user-defined inheritance, and sharing is protocol composition. A protocol's signatures come from the classes that implement it, and they all have to agree.

Exhaustiveness, from the other side

There is no case, so there is nothing to forget a branch of. A protocol names what its implementors must answer, and a class that claims it and misses one is a compile error:

Cancelled claims protocol Status but does not implement "colour"

Where a language with sum types counts the branches of a case, this one counts the operations of a class. Adding a phase is where the compiler speaks, rather than adding a use.

What may cross a boundary

A reference is location transparent, so anything travelling over one must be copyable — and a block is not, because it closes over the scope it was made in. That was enforced before there was anything to distribute, because a program written against the looser rule could never be distributed afterwards. When the worker backend arrived, there was nothing to go back and fix.

The same rule refuses a Decimal at the JavaScript boundary: JavaScript has no decimal, and which way to lose it — the digits or the arithmetic — is the caller's choice to make, in writing.

Whole-program checking

Every module is parsed and inferred together. Three of the checks are whole-program by nature: a protocol's signatures come from every implementor, what a class may become: depends on how every reference to it was made, and Sendable reaches through the values a message carries. Separate compilation would weaken all three, and it buys nothing here — a screen is one file and its components, checking 81,000 lines takes a second, and the growth is linear.