Ajisai

Reference
MCP Playground

Introduction

Ajisai is an AI-first, vector-oriented dataflow language for auditable, exact vector computation with machine-readable contracts. A program is a sequence of elements, most of them calls to Words. Values live on a single stack: each Word takes (consumes) its operands from the stack and pushes its result back onto it. Fractions and the Vector as a data structure play the central roles.

When a well-formed operation cannot produce a value, the result is NIL, which carries its reason and is handed on to downstream computation as it is. When the operation or the shape of its input is itself wrong, the result is an error and evaluation stops on the spot. Even a well-formed program is an error once it exceeds the limits the host can support; those limits are covered in 1.2.2 The cost of evaluation.

Why I made Ajisai

I had two reasons for developing Ajisai. One is that I had given up on learning every programming language I tried. The other is that I never wanted to build something with the tool called a language — I wanted to build the tool itself.

The turning point came when I met Forth. Stack orientation: minimalism pared down to the limit. The way it strips away syntactic noise and lays the act of computation bare taught me that a language is allowed to be this small.

But in my everyday working life I am the kind of person who values manuals and established method, and I could not bring myself to give up the robustness that "types" bring to programming. Out of that came a fierce dilemma: "I want to take types seriously, but I do not want to write about types."

The answer to that contradiction was to carry rigor not as annotations attached to values but as machine-readable contracts on the side of the Words. Every Core Word carries a contract stating what it takes and how many, whether it is pure, and what it returns when it cannot produce a value. User-defined Words can declare the same kind of contract, and it is checked against the definition before anything runs. There is nowhere in the language for the programmer to write a type, yet the checking stays with the Words.

Numbers follow the same policy. Whether you write an integer or a decimal, every value is an exact rational, and SQRT extends the number field from there to algebraic irrationals. Nothing is ever rounded. The question of choosing between float, double and decimal simply does not exist.

What turned this uncompromising, extreme design into something real was meeting AI. In an age when AI interprets intent and becomes a powerful development partner, there is no need to dress a language up with sweet syntax for humans (syntactic sugar). If you take "AI-first" as the premise — a new kind of intelligence spinning code alongside you — then a design that lays down only strict, machine-readable rules and leaves the rest to pure dataflow becomes viable. Of that I became convinced.

Exact numbers, and a stack that carries them. When that form settled, the image that came to mind was "water poured into a vessel". What fills the vessel is only water that is never rounded. Yet there is no limit to the shapes of the ripples you can spread across its surface together with AI.

As it happens, there is a flower whose scientific name derives from "water vessel": the hydrangea, ajisai in Japanese. I started development in June in Japan, during tsuyu, the rainy season, when the rain goes on and on. Ajisai is a language made with that scene in mind.

Numeric exactness

Every number is handled exactly. Values are held as exact rationals and as the algebraic irrationals that arise when those are closed under SQRT — the multi-quadratic normal form. Arithmetic and comparison both run without rounding error. The stack display sqrt(2) or 1/1+sqrt(2) writes that normal form as a single token; nothing is truncated or approximated. The Playground's Stack area shows the same token as it is.

What this Reference offers

This Reference explains each feature together with a short, verified sample. Every example has an "Open in Playground" button: one click loads the code into the app's editor, so you can see the actual behavior immediately.

This Reference divides a program into its elements, and divides each element further for as long as it can be divided. What lies at the end of that division is the individual Word, and that is where the Reference ends.

Where this Reference stands

This Reference is for people who use and learn Ajisai. If you are implementing or porting the language itself, consult the Specification, the single design authority. This Reference reorganizes its content for everyday use and learning.

How to read the examples

The Expected value column of each section shows the final state of the stack after the program runs, rendered according to the language's display rules. These are the display rules to know in advance:

1. Basic display rules

  • Numbers: shown as exact fractions of the form numerator/denominator. The integer 3 is rendered as 3/1.
  • Booleans: shown as TRUE or FALSE.
  • Absence of a value: shown as NIL.

These are the standard renderings in the Stack area, and they match what the Expected value column of this Reference states. If the screen shows 3 rather than 3/1, the LaTeX display mode described next is switched on. The setting is remembered by the browser and survives a reload, so it may still be on from an earlier visit. When comparing against the Expected value column, take the unchecked state as the baseline.

2. LaTeX display mode (typeset mathematics)

The Stack area has a LaTeX checkbox, off by default. Turning it on redraws the same values as typeset mathematics.

  • A fraction is shown with its numerator stacked over its denominator.
  • A numeric Vector is rendered as a matrix.
  • A fraction whose denominator is 1 is treated as an integer: 3/1 is rendered as 3.

This is another rendering of the same value; the value itself does not change. In either display mode it stays an exact rational, with no rounding and no silent reduction. Unchecking the box returns to the canonical form. Values with no mathematically unique typeset form — Strings, NIL, Vectors of uneven shape — keep the standard rendering even with the box checked.

3. Abbreviated display of large collections

When a collection has very many elements, the Stack area draws only the leading part and appends a marker such as … 199900 more. The marker is a note about on-screen rendering; the data itself is not truncated. The collection on the stack is held complete, and every Word sees the whole of it. LENGTH returns the real element count.

4. How symbols and tokens are written

Text with a gray background in the body of this Reference is always an Ajisai token. The gray background says the text is part of program code, not prose. A token spelled only with symbols is an ordinary name like any other.

1 Program

An Ajisai program is a sequence of elements.

A program is zero or more elements, written one after another and separated by whitespace.

Elements are written separated by whitespace and evaluated one at a time, left to right. Branching, iteration and definition are Words, written in the sequence like any other element. [ 2 MUL ] 'DOUBLE' DEF is a program of three elements: a Vector, a string and a name.

Two things are said about a program: how its text is divided into elements (1.1 Separating elements), and how the elements in sequence are evaluated (1.2 Evaluation). The elements themselves are the subject of 2 Elements.

1.1 Separating elements (whitespace, #)

Elements are separated by whitespace. The text is first divided into a sequence of tokens, and each pair of square brackets [ ] gathers its tokens into a Vector, giving the sequence of elements. The rules that turn text into tokens are defined once, in spec/grammar.json, and every text becomes either exactly one token sequence or exactly one named source error.

How the lexer works

The lexer reads the text left to right. At each position where a token may start, it tries the four rules below in order, and the first rule that applies consumes the characters there.

1. Scan — at each token position, rules are tried top to bottom whitespace skip one character (emits nothing) # discard to the end of the line (comment) ' read to a closing ' followed by whitespace → string literal anything else cut one word up to the next whitespace → step 2 2. Classify the word — by the whole word, left to right exactly [ or ] delimiter contains [ or ] source error zero denominator source error matches number number literal anything else name 3. Check [ ] pairing over the tokens — a stray ] or an unclosed [ is a source error
  1. Whitespace (space, tab, newline and the other Unicode whitespace characters): skip one character. Nothing is emitted.
  2. #: a comment. Discard everything up to the end of the line.
  3. ': a string literal. Read up to a closing quote ' followed by whitespace or the end of input.
  4. Anything else: cut out one word, up to the next whitespace (or the end of input). No character along the way is checked.

A word cut out this way is then classified, in this order. The whole word decides, so there is no rule for individual characters.

  1. Exactly [ or ]: a delimiter (the start or end of a Vector).
  2. Otherwise containing [ or ]: a source error (bracketMustStandAlone).
  3. The shape of a fraction with a zero denominator (1/0): a source error (zeroDenominator).
  4. The whole word matches the number grammar: a number literal.
  5. Everything else: a name.

Finally, bracket pairing is checked over the whole token sequence: a ] with nothing open and a [ never closed are source errors. Source that fails at any of these stages runs not a single token.

How tokens are separated

Tokens are separated by whitespace (space, tab, newline) and nothing else. No other character splits a token. Symbols and punctuation are all ordinary characters of a name.

The rule has no exceptions, and neither of the following is one.

  • [ and ] must stand alone, like any other Word. [ 1 2 3 ] is correct; [1 2 3] is a source error that points out the missing whitespace.
  • # starts a comment only at the start of a token. A # attached to the preceding token is part of that name, so ADD# note is not ADD with a comment but an undefined Word named ADD#.

The only brackets Ajisai gives a meaning to are the one pair [ ]: the Vector brackets, which code blocks use too. { } and ( ) have no syntactic role. They are just characters of a name, so writing one alone answers "there is no such Word". See Exact numbers for more.

Operator Words need whitespace

An operator Word is a name too, so it needs whitespace on both sides. 2 ADD is correctly read as the two tokens 2 and ADD. 2ADD is taken as the single name "2ADD" and is an undefined-Word error, not an addition.

The square brackets [ ] are the same. Each is a Word of its own and needs whitespace around it. [ 1 2 ]LENGTH is rejected because ]LENGTH is an invalid token; write [ 1 2 ] LENGTH.

The Playground's Format button fills in this missing whitespace (the same formatting also runs before execution, so in the Playground [1 2 3] runs as [ 1 2 3 ]). Only the source being edited is reformatted; the language's rule is not relaxed. Handing the same source unchanged to the CLI or MCP is an error.

Line breaks

A line break is whitespace just like a space and means nothing beyond separating tokens. A program may span as many lines as you like; written on one line or split across several, it is the same program. The same holds for a Word definition's body: two definitions that differ only in where the lines break are the same Word, and their DIGEST matches. The only thing a line break ends is a comment: from a # at the start of a token to the end of the line.

Comments

# starts a comment that runs to the end of the line. The characters from # to the end of that line are discarded before tokenization. A # inside a string literal is ordinary text, so 'a # b' is a single string.

Examples

SourceTokensWhy
3 4 ADD3 tokensWhitespace separates each token.
3 4ADD2 tokens4ADD is taken as one name: an undefined-Word error, not an addition.
[ 1 2 ] LENGTH5 tokensWhitespace separates each token. Without the whitespace, [ 1 2 ]LENGTH is an error because ]LENGTH is an invalid token.

Kinds of token

Lexical analysis produces only five kinds of token. Whitespace and comments produce none.

KindExamplesCarries
Number literal3 -1/2 0.25 1e5The lexeme itself, read as an exact number
String literal'hello'The text between the quotes
NameADD NIL? DOUBLEThe lexeme as written. It names a Word or a binding (a Symbol inside [ ])
Vector start[Nothing
Vector end]Nothing

Source errors

These five are the only errors lexical analysis can raise, all classified malformedSource.

ConditionExampleMeaning
unclosedLiteral'fooA string literal reached the end of input without a closing quote followed by whitespace.
zeroDenominator1/0A fraction literal's denominator is zero.
bracketMustStandAlone[1A word contains [ or ] without being exactly that delimiter.
unexpectedCloseBracket]] appeared with no [ open.
unclosedBracket[Input ended with a [ still open.

1.2 Evaluation

A program is evaluated element by element, left to right. What happens depends on the kind of element:

  • A number or string literal: its value is pushed onto the stack.
  • A Vector [ ... ]: pushed as a Vector without evaluating its contents. Names inside stay as Symbols, except TRUE, FALSE and NIL.
  • A name: if it is bound, its value is pushed; otherwise the Word of that name in the dictionary is called. If it is neither, it is an error (Unknown word).

Values on the stack

Every value evaluation pushes onto the stack belongs to exactly one of seven domains. The domains are disjoint, with no implicit conversion between them.

Consumption

An Ajisai Word takes its operands from the stack and pushes its results back. A value that is read is always consumed on the spot and removed from the stack. No modifier changes this. Words that push no result, such as BIND, DEF and DEL, are no exception: they consume their operands too.

Using the same value twice: BIND

To use one value more than once, name it with BIND and write the name as many times as you need. Each time the name is written, its value is pushed onto the stack. 5 'N' BIND N N 1 ADD is 5/1 6/1. Because values are read by name, you can see from the text alone which value is used where.

A Word can be replaced by its body

Every call consumes exactly the operands it reads, so writing a Word you defined as its body instead does not change which operands are consumed. Given [ 2 MUL ] 'DOUBLE' DEF, both 5 DOUBLE and 5 2 MUL are 10/1. This property is what lets you check a program that uses User Words by expanding it into Core Words alone.

Sample codeExpected valueNotes
3 4 ADD 7/1 Both operands are consumed and their sum is pushed.
Sample codeExpected valueNotes
5 'N' BIND N N 1 ADD 5/1 6/1 A named value is pushed every time its name is written.

1.2.1 Outcomes (values, NIL, errors)

Evaluating one element ends in one of three ways: it succeeds and pushes the outputs its contract registers (some Words have none), it pushes a NIL carrying a reason, or it stops the whole evaluation with an error.

The NIL rule

The failure model fits in one sentence: if a value could not be produced, NIL; if the usage itself is wrong, an error. A well-formed operation that cannot produce a result maps to NIL with a structured reason. Misuse — a wrong type, missing operands — raises an ordinary error.

NIL propagates

Arithmetic and comparison both pass NIL through. A NIL operand does not crash the pipeline; the result simply becomes NIL. EQ is no exception: NIL 1 EQ is NIL, not FALSE.

Sample codeExpected valueNotes
1 0 DIV NIL Division by zero is not a trap; it becomes a reasoned NIL.
Sample codeExpected valueNotes
NIL 1 ADD NIL NIL passes straight through arithmetic, and its reason stays traceable.
Sample codeExpected valueNotes
[ 10 20 30 ] 5 GET NIL An out-of-range index into a valid Vector is NIL (reason: indexOutOfBounds).
Sample codeExpected valueNotes
[ 1 2 3 ] 5 TAKE NIL Asking for more than there is asks the same "past the end" question, and gets the same answer as GET (reason: indexOutOfBounds).
Sample codeExpected valueNotes
[ 1 2 3 ] 9 5 PUT NIL PUT likewise. An out-of-range index is a fact about the data, not a mistake in the program, so it is NIL.

One condition has one answer. "Past the end" is NIL for GET, TAKE and PUT alike, the same across the whole vocabulary. A count or index that is not even an integer ([ 1 2 3 ] [ 'x' ] TAKE) is still an error — that one is wrong usage.

Sample codeExpected valueNotes
'ABC' NUM NIL Text that cannot be parsed as a number is NIL (reason: invalidEncoding).

1.2.2 The cost of evaluation

The cost of iteration

Exact arithmetic changes what a loop costs. Every other section of this Reference is about what a program means; this one is about what repeating it costs, because it is the one place where exactness asks for something in return.

Where the cost comes from

An exact rational is stored as a pair of numerator and denominator, and the product of two fractions has the product of their denominators. Multiply once and nothing happens. But keep multiplying in a loop, and the denominator grows step after step. That is exactly what gradient descent, Newton's method, k-means, power iteration and every other iterative method do. Nothing is lost and nothing is approximated; the number stays entirely correct. But its digits record how the value was computed, and that history grows longer every round.

Here is a relaxation step in which x moves a tenth of the way toward 2 each time. DELTA is the correction and STEP applies it. The loop is a FOLD over a range whose elements are mere counters; the block discards the element and keeps only the accumulator.

Sample codeExpected valueNotes
[ 2 SUB 1/10 MUL ] 'DELTA' DEF
[ 'X' BIND X X DELTA SUB ] 'STEP' DEF
0 6 RANGE 10 [ 2 COLLECT 0 GET STEP ] FOLD
7282969/1250000 Seven steps from 10 (0 6 RANGE is the seven elements 0 to 6). The denominator already has seven digits. The value is about 5.83.

The problem is not the size of any one number but the shape of the growth. Run the same loop for 21 steps and you get 359418989131512359209/125000000000000000000: 21 digits over 21 digits, a value of about 2.875. Each further step multiplies the denominator by ten again, so the answer barely moves while the cost per step climbs without limit. A loop that finishes in an instant at 30 iterations can crawl at 300.

Naming the resolution

x d MUL 1/2 ADD FLOOR d DIV rounds a value to the nearest multiple of 1/d (an exact midpoint goes to the larger side). The result's denominator divides d and never grows beyond it. Applied once per step, it throws away the derivation history and keeps only the value.

Sample codeExpected valueNotes
[ 2 SUB 1/10 MUL ] 'DELTA' DEF
[ 'X' BIND X X DELTA SUB 1000 MUL 1/2 ADD FLOOR 1000 DIV ] 'STEP' DEF
0 20 RANGE 10 [ 2 COLLECT 0 GET STEP ] FOLD
2877/1000 After 21 steps the denominator is still 1000. 0 20 RANGE is the 21 elements 0 to 20. The unrounded run gives 2.875351…, the same number to the resolution asked for.

This is an approximation, and that is exactly the point: it is an approximation you wrote down yourself. The resolution is a literal in the source, at a place you chose, and every value after it is exactly a multiple of 1/d. Nothing is rounded behind your back, and nothing decides on its own how much precision the answer deserves.

MUL, ADD, FLOOR and DIV, which make up this rounding, all lift over Vectors, so a whole parameter Vector is rounded by the same run of Words.

Sample codeExpected valueNotes
[ 119/125 32/125 ] 10 MUL 1/2 ADD FLOOR 10 DIV [ 1/1 3/10 ] One call per step, whatever the shape of the state.

A closed form beats a loop

Before reaching for an iterative method, check whether the problem has a closed form. In exact arithmetic a closed form is not only faster; it is a different kind of answer. Least squares, ridge regression with a rational penalty, k-nearest neighbours, naive Bayes over rational probabilities, decision trees with Gini splits, the perceptron and the simplex method all have exact solutions without iteration. Ajisai returns the coefficients as exactly those rationals: an R² that comes back as 529/532 is the exact answer.

Transcendental functions — exponentials, logarithms, trigonometric functions, arbitrary powers — have no rational answer, and Ajisai does not handle them. SQRT is the only irrational the language constructs exactly, as an algebraic number, and POW with an integer or p/2 exponent stays inside that field. The number domain is this field in its entirety, so every comparison is decided as TRUE or FALSE in finite time.

The other cost: the host's safety controls

The costs so far accumulate naturally from exact arithmetic itself, even in a correctly written program. Separately, the host sets several limits to protect its computing resources. Each is a safety control on the host side, not language semantics, and the concrete numbers are set by the implementation. The same source may pass on a host that declares larger limits. What happens on overrun is fixed, however: if producing a single value exceeds a limit, the result is a reasoned NIL; if the run itself exceeds a limit so far that it cannot continue, it is an error and evaluation stops.

Run-time errors: one result with several reasons

Even a well-formed operation or input is an error once it exceeds what the host can support, and evaluation stops on the spot. There is more than one limit: overruns are reported under at least three names, depending on what was exceeded. The names differ because the fixes differ.

Reported nameWhat was exceededTypical fix
executionLimitExceededThe total number of work steps the whole program may spendRewrite with bulk operations (SORT, GET); review the termination condition
resourceLimitExceededThe size of a single value (coefficient bit width, number of algebraic terms and so on)Round to a 1/d grid to prune the history; look for a closed form
recursionLimitExceededThe nesting depth of a call chain (not cyclic, but an extremely long chain of Words calling Words)Make the call chain shallower, or rewrite with bulk operations

executionLimitExceeded can be set explicitly with the CLI's ajisai run --step-limit <N>. The default is an implementation choice made by the host's runtime, not a fixed number this Reference promises.

The three above are limits the interpreter counts, but the Playground has one more: a wall-clock cut-off. When a single run exceeds 5 seconds, it is stopped together with its worker and reported as Execution timed out after 5000 ms. This is neither language semantics nor an interpreter budget but a host-side guard that only this screen has. That is why it carries none of the names above; instead it comes with a diagnosis that says which guard stopped the run and suggests rewriting with bulk operations or pruning by rounding to a grid. Hover over the resource limits: badge next to the build indicator at the top of the screen to list the host's limits (this guard is listed there too). The MCP host declares a different, stricter materialization limit. That is why the same 0 100001 RANGE succeeds in the Playground but is NIL (spaceExhausted) on the MCP host.

The work done by the whole program and the size of a single value are measured separately. Some Words, such as 0 99999 RANGE UNIQUE, count as one step yet perform as many comparisons internally as there are elements, so the step budget alone does not show how heavy a value is. Recall the fraction whose denominator kept growing in the section on the cost of iteration: grow it without pruning to a grid, and exactly this resourceLimitExceeded is where it ends. recursionLimitExceeded is different again. A Word cannot call itself, so this is not a recursion depth but the depth of a non-cyclic call chain that passes through many different Words, A→B→C→….

Materialization limits: a reasoned NIL

RANGE and FILL have a limit on the size of the Vector one call produces. Even when the request itself is well-formed, trying to materialize beyond that limit gives a reasoned NIL, not an error. The reason is spaceExhausted. A dimension or shape that is itself wrong, such as a negative or non-integer number, is a different matter and is an error, as usual.

Sample codeExpected valueNotes
0 999999999999 RANGE NIL-REASON 'spaceExhausted' The request is well-formed but too large, so it is a reasoned NIL, not an error. FILL behaves the same way for the same reason.

Being NIL, it can be replaced with a fallback value so the program moves on: 0 999999999999 RANGE 'S' BIND [ ] S S NIL? SELECT recovers to the empty Vector. An error has no such recovery.

2 Elements

There are four kinds of element.

An element is one of four things: a number, a string, a name or a Vector. A Vector is zero or more elements between [ and ], and it is the only element that contains elements.

ElementExamplesEvaluated, it
2.1 Number3 -1/2 0.25 1e5pushes the number
2.2 String'hello'pushes the string
2.3 Vector[ 1 2 3 ]pushes the Vector, without evaluating its contents
2.4 NameADD N DOUBLEpushes the bound value if the name is bound; otherwise calls the Word of that name

Whitespace and comments are not elements (1.1 Separating elements).

Display forms are not elements

What the stack shows is, as a rule, a form that gives the same value when written as elements. The exception is an irrational's sqrt(2) (2.1 Numbers): that is how the value looks, and written as an element sqrt(2) is a plain name. An irrational is built with SQRT. A Record has no look of its own; it is displayed as the program that builds it, such as [ 'x' ] [ 1/1 ] RECORD (2.4.2.3 Keyed data).

2.1 Numbers

Number literals

A decimal literal needs at least one digit on both sides of the point. 0.5 and 5.0 are numbers; .5 and 5. are not. Because of this rule the point is never the first or last character of a number, so a lone . can safely be an ordinary name rather than an incomplete literal.

A number is written in one of four forms. A "run of digits" is one or more of the ASCII digits 0 to 9.

FormWritten asExamples
Integera run of digits, optionally preceded by - or +5 -5 +5 007
Fractionan integer, /, a run of digits1/2 -1/2
Decimalan integer, ., a run of digits0.5 5.0
With exponentan integer or decimal, then e or E, an optional sign, a run of digits1e5 1E5 1e-5 1.5e+3

A fraction takes neither a decimal part nor an exponent (1/2.5 and 1/2e3 are names). These forms apply to the whole word. A word that starts like a number but does not end as one is a name, not a malformed number. A full-width digit is a name character.

A numberA name
5 -5 +5 007- + -x
1/2 -1/2/ 1/ 1/2/3
0.5 5.0.5 5. 1.2.3
1e5 1e-5 1.5e+31e 1e+ 5.e3

Exact numbers

Every number in Ajisai is an exact number.

How numbers are written, and their domain

Integer, fraction, decimal and exponent literals are only different notations for the same exact value. 0.5 is exactly 1/2.

The number domain consists of the exact rationals and the algebraic irrationals that arise when those are closed under SQRT — the multi-quadratic normal form. Every number you can build is one or the other.

The stack display of an irrational (sqrt(2), 1/1+sqrt(2)) writes its normal form as a single token. The Playground's Stack area shows the same token. See How irrationals are displayed, and the syntax rule for more.

Sample codeExpected valueNotes
0.5 0.25 ADD 3/4 Decimal literals are computed as exact rationals.
Sample codeExpected valueNotes
1 3 DIV 1/3 Division returns an exact rational.
Sample codeExpected valueNotes
2 3 DIV 1 3 DIV ADD 1/1 \(\frac{2}{3} + \frac{1}{3}\) is exactly \(1\).
Sample codeExpected valueNotes
1000000000000 1000000000000 MUL 1000000000000000000000000/1 Arbitrary-precision integers stay exact, however many digits they grow.
Sample codeExpected valueNotes
9 SQRT 3/1 The square root of a perfect square simplifies to an exact rational.
Sample codeExpected valueNotes
2 SQRT 2 LT TRUE An irrational such as \(\sqrt{2}\) is a lazily evaluated continued fraction, and comparisons against it are computed exactly.

How irrationals are displayed, and the syntax rule

Irrationals have one display form: the value's normal form written as a single token with no whitespace. 2 SQRT is sqrt(2), 1 2 SQRT ADD is 1/1+sqrt(2), 2 SQRT 3 SQRT SUB is sqrt(2)-sqrt(3), and 8 SQRT is 2/1*sqrt(2) (the radicand is normalized to be square-free). Nothing is truncated or approximated, and inside a Vector it reads as a single element. The stackDisplay of the CLI and MCP hosts and the Playground's Stack area all show this same token. The CLI and MCP also list the same terms, structured, under semantics.exactTerms.

This is display-only output, not source syntax. Writing sqrt(2) in a program gives an unknown name. ( ) are name characters, not syntax, so written in a program they do not act as parentheses.

2.2 Strings (' ')

String literals

A string literal is closed by a quote (') at a token boundary. Here too only whitespace separates tokens, so the closing ' must be followed by whitespace (or the end of input). An apostrophe inside the string does not end it unless whitespace follows. This lets you write a string such as 'It's fine' as a single literal, with no escape character. Conversely, a quote followed by anything other than whitespace stays inside the text, so both 'abc'CHARS and [ 'abc'] are an unterminated string, not two tokens.

Strings (' ')

Strings are quoted with ' and shown with their quotes on the stack. The quotes are a display convenience, not part of the value. PRINT writes the raw text, so 'TEST' is output as TEST; see Output for more. A string nested inside a collection keeps its quotes, though: [ 'AB' 'CD' ] is shown just as written. Conversions between strings and numbers or characters are collected under String operations.

A value domain of its own

A string is one of the seven value domains, not a Vector of code points. 'A' [ 65 ] EQ is FALSE, and arithmetic on a string is an error, not arithmetic on codes. The empty string '' is an ordinary string, not NIL.

Sample codeExpected valueNotes
'hello' 'hello' A string literal evaluates to itself.

2.3 Vectors ([ ])

Vectors ([ ])

A Vector is written by enclosing its element values in square brackets [ ], separated by whitespace. A Vector literal evaluates to that Vector value as it is, and literals nest freely.

Sample codeExpected valueNotes
[ 1 2 3 ] [ 1/1 2/1 3/1 ] A Vector pushed on the stack. Integers are shown as exact fractions with denominator 1.
Sample codeExpected valueNotes
[ [ 1 2 ] [ 3 4 ] ] [ [ 1/1 2/1 ] [ 3/1 4/1 ] ] Nesting is kept as it is, which is how grouped data and ragged data are expressed.
Sample codeExpected valueNotes
[ 7/3 ] [ 7/3 ] Fractions are first-class literals and stay exact.

Bare names inside a literal

A Vector literal is a literal all the way through. A bare name written inside [ ] denotes a Symbol; it is neither run as a Word call nor an undefined-Word error. So [ FOO BAR ] is the Vector [ FOO BAR ] of two Symbols. If you want text, quote it — [ 'FOO' 'BAR' ] is a Vector of Strings. This is an easy point to trip over once you start naming values. To put the value of a variable, or the result of running a Word, into a Vector, use a run-time collection builder such as COLLECT rather than a literal. See A name inside [ ] is a Symbol for more.

[ ] versus NIL

[ ] is the empty Vector. It is a collection holding no values, but it is an ordinary value you can write, nest and measure: [ ] LENGTH is 0. It is not NIL. NIL is the absence of a value, which is a different thing from a collection that happens to hold nothing. An operation whose result is empty returns [ ], not NIL: [ 1 2 3 ] [ 5 GT ] FILTER is [ ].

Code as data

A code block and a Vector are the same kind of value. [ ] is the only bracket for both; a block is just a Vector holding source to be evaluated later, not a domain separate from data. It is read as something executable only when a Word's contract asks for it as code, so a block on the stack is displayed just like any Vector: [ 2 MUL ] is shown as [ 2/1 MUL ], and [ ] as [ ]. [ 1 2 ADD ] [ 1 2 ADD ] EQ is TRUE — how a value was built (its construction history) is not part of the value, so whether a Vector is "code" or "data" is not decided before it is used. A bare name inside a block — MUL above — is a Symbol and is not looked up when the literal is built. EXEC and the Words that ask for evaluation run it; nothing else does.

Sample codeExpected valueNotes
[ 1 2 ADD ] [ 1 2 ADD ] EQ TRUE How it was built is not part of the value: EQ cannot tell them apart.
Sample codeExpected valueNotes
[ 1 2 ADD ] EXEC 3/1 EXEC does not care how its operand was built.

Putting a run-time value into a block

A block is source, so values computed while the program runs are not inside it. MAP and FILTER take only a block, which raises a practical question: how do you filter by a threshold known only at run time? [ 5 LT ] compares against the literal 5, but when the number is something the program computed, there is no 5 to write.

A block is an ordinary Vector, so it can be built with ordinary Vector Words. CONCAT joins a one-element Vector holding the run-time value with a one-element Vector holding the Symbol the block needs to run — [ 5 ] [ LT ] becomes [ 5 LT ], and the result is no different from any other block. Even though it was not written with [ ], every Word that takes a block accepts it. No separate bridge is needed; this is the very construction the sample above already showed.

Sample codeExpected valueNotes
[ 3 1 4 1 5 9 2 6 ] [ 5 ] [ LT ] CONCAT FILTER [ 3/1 1/1 4/1 1/1 2/1 ] The 5 here is an ordinary stack value. Replace it with something the program computed, and the predicate follows.

That is all there is to partial application in Ajisai. There is no separate Word for it, and none is needed, because a block is a value whose contents can be computed like any other Vector's.

Blocks and evaluation frames ([ ])

A block has no stack discipline of its own. The Word that evaluates a block decides what the block can reach and what it may leave behind. Read with the wrong rule, an ADD inside a block either finds two operands or finds none. There are five rules, and the block's own text does not say which one applies. The Word that receives the block decides.

Evaluating WordWhat the block seesWhat the block leaves
A Word's body (DEF)The whole stackEverything it pushed, any number of values
EXECThe whole stackEverything it pushed, any number of values
MAP FILTERThe current element onlyExactly one: the mapped element, or a Boolean for FILTER. Leaving nothing is an error; anything below the top is discarded
FOLD SCANTwo: the accumulator and the elementExactly one next accumulator. Leaving nothing is an error; anything below the top is discarded

So [ ADD ] 'ADDW' DEF followed by 3 4 ADDW gives 7, and so does 3 4 [ ADD ] EXEC, because both see the whole stack. But 100 [ 1 2 ] [ ADD ] MAP is a stack underflow: the block sees only one element, and 100 is not in its frame.

Reread "anything below the top is discarded", because it happens silently. A predicate that leaves two values passes straight through: [ 1 2 3 ] [ 1 GT TRUE ] FILTER keeps everything, because the TRUE on top is the answer and the comparison result below it is discarded. It is an error only when nothing is left. If a higher-order Word's block gives an answer you cannot explain, count how many values it leaves.

When something fails inside a block, the diagnosis names the Word that failed, not the Word that ran the block. [ 1 2 ] [ 'x' 1 ADD ] MAP reports ADD, with the position of the statement that ran and inside MAP attached. This drilling down stops at a Word you defined yourself: that call reports the Word itself, because from the caller's side that is what failed.

A block written inside another block is data where it is written. It is evaluated only when the Word that receives it runs it, and then under that receiving Word's own rule, not the outer block's. That is about evaluation, though, not reference. Even when the name of the Word being defined appears only inside a [ ] passed to MAP or EXEC, DEF's acyclicity rule detects it and rejects the definition itself.

2.4 Names

A name is every word that is not a number, a string or a square bracket. Any character other than whitespace and the square brackets can be part of a name; but a word beginning with # is a comment and one beginning with ' is a string, so a name never begins with either (1.1 Separating elements).

Evaluating a name pushes its value if the name is bound (2.4.1 Bindings), and otherwise calls the Word of that name (2.4.2 Words). A name that is neither is an error (Unknown word). A name written inside [ ] is not evaluated; except for TRUE, FALSE and NIL, it becomes a value called a Symbol.

Symbols and how tokens are recognized

A symbol in Ajisai is simply a name spelled with symbol characters. Every Word is called by its one English name, and no Word has a symbol spelling. + and < are ordinary names too, undefined unless you define them. Because a symbol is just one way of spelling a name, even a multi-character symbol does not complicate the rule above: a token ends only at whitespace, never partway through. A token's meaning is decided by the lexeme of the whole token, not by its first character. For example, 1/2 is parsed as a whole fraction literal, while a lone / is parsed as a plain name.

A name is a Word or a binding, never both

You cannot bind a name that a Core or User Word already has, and you cannot define a Word under a name that is currently bound. Both directions are rejected, so you never have to think about which of the two a name resolved through. Short throwaway names such as W, DX and LIMIT are exactly right here, and unlike Word names they are not subject to naming-convention warnings: they are local, and their scope fits on the screen in front of you.

Name resolution and redefinition

A name resolves in either Core or User, and User never shadows Core. So a Word's name is the whole of its address: there is no qualified form, no dictionary to choose, and no room for a bare name to be ambiguous. Names are case-insensitive, so add10 and ADD10 are the same Word.

Sample codeExpected valueNotes
[ 10 ADD ] 'ADD10' DEF 5 ADD10 15/1 A defined Word is referred to by its name.
[ 1 ] 'ADD' DEF error Core is sealed, so a Core name cannot be taken.
[ 10 ADD ] 'ADD10' DEF [ 20 ADD ] 'ADD10' DEF 5 ADD10 25/1 Redefinition rebinds the single User entry — unless other Words depend on it, in which case DEF refuses.

2.4.1 Bindings (BIND)

Giving names with BIND

The stack hands you one value at a time, but most expressions worth writing use a value more than once. A residual is used in both the squared error and the gradient; a threshold in both the comparison and the report; a pair of coordinates in both their sum and their difference. An Ajisai Word always consumes the values it reads, so by its second use a value is already gone from the stack.

So BIND gives a value a name. It takes a value and a name, consumes both, and from then on the name is that value. Use it as often as you like, in any order.

Sample codeExpected valueNotes
[ 4 9 ] [ 'A' 'B' ] BIND A B ADD A B SUB 2 COLLECT [ 13/1 -5/1 ] Two names from one Vector, each read twice. Written with the stack alone, this would need a matrix and a fold.

With one name, it receives the whole value. With several, it destructures a Vector of the same length position by position. The lengths must match exactly, or BIND raises an error. Letting it through silently would drop the tail when the Vector is longer, and leave some name with nothing when it is shorter — not what the writer meant.

Sample codeExpected valueNotes
5 'T' BIND T T MUL 25/1 The one-name case. As with a GET index, writing [ 'T' ] means the same.

A name inside [ ] is a Symbol, not a value

This is the one place where binding trips people up; read it before you get stuck in your own program. Inside a Vector literal a bare name denotes a Symbol — data until something runs it — and is never looked up. Bound names are no exception. So after 3 'K' BIND, [ K ] is not [ 3/1 ] but the one-element Vector [ K ] holding the Symbol K.

It catches people because this Reference's idioms are written like 0 GET and 3 TAKE, so the natural next move is XS [ K ] TAKE. And that fails, because a Symbol is not the integer TAKE expects. There are two ways to say what you mean.

Sample codeExpected valueNotes
3 'K' BIND [ 1 2 3 4 5 ] K TAKE [ 1/1 2/1 3/1 ] Write the name bare. A count or index can be a plain number, so nothing needs wrapping.
3 'K' BIND K 1 COLLECT [ 3/1 ] When you really want a one-element Vector, build it. COLLECT reads values; [ ] reads Symbols.

How far a name reaches

A binding belongs to the frame that made it and lives exactly as long as that frame. A frame is a Word's body, or, for direct input, the run itself. Two rules follow, and those two are the whole story.

A name reaches blocks written inside its frame. The blocks that MAP, FILTER, FOLD and EXEC run are not a new frame for names, so they can read the surrounding names as they are. The isolation these Words enforce is about the stack, and names are not on the stack; see Blocks and evaluation frames for the details of that isolation. This is what lets a predicate test against a value the program could only know at run time.

Sample codeExpected valueNotes
3 'LIMIT' BIND [ 1 5 2 9 ] [ LIMIT LT ] FILTER [ 1/1 2/1 ] The block is written where LIMIT is bound, so it can write LIMIT.

A name does not reach into the Words you call. A Word's meaning is decided by its operands and the dictionary alone, and by nothing else. That is what makes a Word readable on its own. A body reads only its own bindings, never its caller's. If a value is needed inside, pass it as an operand — that is exactly what operands are for. Get this wrong and you are told so plainly, rather than seeing it reported as an unknown Word.

A Word that binds leaves no names behind for its caller, and a run's bindings disappear when the run ends. This is deliberate: a name that outlived its frame would become a second, editable namespace next to the dictionary. There is only one namespace here. For data you want to keep, define a Word, as in [ [ 1 2 3 ] ] 'XS' DEF. That is exactly the job the dictionary is for.

2.4.2 Words (DEF)

A Word is a named operation registered in the dictionary. Called by its name, it consumes operands from the stack and pushes the outputs its contract registers, if any. The built-in Words are grouped by what they do into families, and each family page ends with the contract of each of its Words. The individual Word is the last unit of this Reference.

Every operation is a Word

Ajisai's lexical structure tells apart only numbers, strings, names and the one pair [ ]. There are no reserved words. Arithmetic and comparison, branching, iteration, definition and binding — even TRUE, FALSE and NIL, which push one value — are ordinary Words, each with one English name, held in the dictionary and called by name.

RoleWords
ArithmeticADD SUB MUL DIV
ComparisonEQ LT GT
LogicAND NOT
Truth values and absenceTRUE FALSE NIL
ChoiceSELECT
IterationMAP FILTER FOLD SCAN
Definition and bindingDEF BIND

Every Word is listed under Built-in Words.

Dictionaries and Word identity

Two tiers: Core and User

The dictionary has two tiers, and that is all. Core holds the 78 canonical Words and is sealed: a Core name cannot be redefined or deleted. User holds everything you defined with DEF.

These 78 Words divide into the 48-Word Semantic Kernel and 30 Standard Words. A Kernel Word either builds or observes some kind of value, or is the one explicit way to do something — running a code block, recovering from NIL, changing the dictionary, output. A Standard Word, such as SUB, MIN, SORT or TRIM, gives a frequent idea one fixed name and contract. The division only tells you where a Word sits in the design; it changes nothing about how you use it. All 78 are ordinary Core Words in the same single flat dictionary, reached by their plain names, with the same contracts, examples and error conditions. Each Word's tier is listed with the built-in Words and in each page's contracts, and it is also shown when you look the Word up.

Making a new Word with DEF

A new Word is defined from a code block and a name: [ body ] 'NAME' DEF. The body runs every time the Word is called.

Sample codeExpected valueNotes
[ [ 10 ] ADD ] 'ADD10' DEF 5 ADD10 [ 15/1 ] Defines ADD10 and calls it.
Sample codeExpected valueNotes
[ 2 MUL ] 'DBL' DEF [ 1 2 3 ] [ DBL ] MAP [ 2/1 4/1 6/1 ] A user-defined Word combines with higher-order Words through its quoted name.

What DEF checks, and what it does not

DEF only stores the body; it does not resolve the names inside it. A definition whose body calls a Word that does not exist is accepted and reported as defined. The failure surfaces the first time the Word is called: [ TOTALL 1 ] 'FOO' DEF succeeds, and only running FOO reports Unknown word: TOTALL.

This is a design choice, not a gap in checking. Names are resolved at call time. This lets a Word call another Word defined after it, so you never have to reorder definitions to match their dependencies.

The price is that a typo in a body lies quietly in wait until something calls it. The headless CLI can catch it in advance: ajisai check resolves every name in a file without running anything and reports the unknown ones, and ajisai check --contract also verifies declared Word contracts. The Playground has no equivalent; inside the app, calling a Word is itself the check.

A Word cannot call itself

The reference graph of the User dictionary has no cycles. If a body contains the name of the very Word being defined, as in [ REC ] 'REC' DEF, DEF rejects it with an error. It does not matter where the name is written: directly in the body, or only inside a [ ] block passed to MAP or EXEC, it is the same.

This extends to indirect cycles. Define A to call B, then try to define B to call A, and the second DEF is rejected: A's body already contains the name B (even though it does not exist yet), and adding a reference from B to A would close a cycle back to A. A new definition is free to call Words that already exist. The only thing forbidden is for the chain to come back to the Word being defined right now.

Iteration cannot be expressed by a Word calling itself. MAP FILTER FOLD SCAN over an already finite Vector are the only means of iteration. With no cycles in the User dictionary no Word can call itself, so every evaluation is structurally finite. This is a stronger property than the run-time step budget (covered under Run-time errors): the budget still limits the cost of a single evaluation, but there is no such thing as a program that would not stop however large the budget.

Managing Words

DEL deletes a user-defined Word by its quoted name. Dependencies are tracked, and a Word that other Words depend on cannot be deleted at all, so dangling references cannot happen. Delete the dependent side first.

Word names are case-insensitive, so add10 and ADD10 are the same Word. By convention they follow a verb-object form such as APPLY-GAIN. How a name is matched to a definition is covered in Dictionaries and Word identity.

Looking up a Word

Put the cursor on a Word and press Ctrl+Alt+L, and the Playground answers from the dictionary in the Output area: a Core Word's Reference entry, or the DEF of a Word you defined, both shown the same way, as text to read — not in a form to put back into the editor. This is something the editor does, not the program. Nothing is pushed, nothing is consumed, and the answer is text rather than a value, so there is no Word a program could call here.

Three other operations exist only as shortcuts: Reset Ctrl+Alt+Enter, Stack clear Ctrl+Alt+S and Editor clear Ctrl+Alt+E, which discard the whole session, the stack, and the text of this input area respectively. None of these four can be typed and run or called from a program. A Word named RESET or LOOKUP, if there were one, would be an ordinary Word like any other name.

Word identity

Every Word has a content identity: a digest over its normalized definition and the identities of the Words it calls. Two definitions with the same identity are the same Word whatever their names, and changing a definition changes the identity of everything that depends on it. This is why DEF and DEL refuse to disturb a Word something else still refers to: the dependency is on the Word, not on its spelling.

Reading the dictionary and contracts from inside the language: DIGEST and CONTRACT

Two Words read, from inside the language, what the machine already knows about the dictionary. The operand that names a Word is a Symbol — a bare name written inside [ ] — not a string (CONTRACT also accepts a code block). 'ADD' CONTRACT is a notASymbol error. A Word that could look a name up from text would break the fact that "no Word turns text into a Symbol", which the cycle check at DEF time relies on.

DIGEST answers, as text, the content identity of a Symbol that names a Word, and the digest of the meaning of any other value. The same digest means the same thing; different digests mean nothing. CONTRACT answers a Word's contract as a Record: the registered contract for a Core Word (inputs, outputs, nil, projection, errors, purity, cost and so on), or the contract inferred from the body for a Word you defined. Pass a code block as it is, and its contract is inferred without running it — the pre-execution check (ajisai check --contract) called from inside the language, with confidence and gaps carrying "what could and could not be verified" as values. This is where the ability to ask about the cost class before running comes from.

Whether a Symbol resolves as a Core or User Word shows in whether CONTRACT answers a contract. A name that is not a Word returns a reasoned NIL, so sym CONTRACT NIL? NOT is that question. NIL? consumes its operand, so only the Boolean remains.

Sample codeExpected valueNotes
[ TWICE ] 0 GET CONTRACT NIL? NOT FALSE An undefined name has no contract, so FALSE.
'ADD' CONTRACT error A string is not a name (notASymbol).
[ 2 MUL ] 'TWICE' DEF [ 2 MUL ] 'DOUBLE' DEF [ TWICE ] 0 GET DIGEST [ DOUBLE ] 0 GET DIGEST EQ TRUE Identity is over content, not over spelling.
8 SQRT DIGEST 2 SQRT 2 SQRT ADD DIGEST EQ TRUE A value is digested by its meaning.
[ DIV ] 0 GET CONTRACT 'projection' GET [ 'divisionByZero' ] Reads the registered contract without running anything.
[ 42 PRINT ] CONTRACT 'effects' GET [ 'consoleWrite' ] Given a block, it infers the contract without running it. Nothing is output.
[ NOPE ] 0 GET CONTRACT NIL-REASON 'notFound' A name that is not a Word is a reasoned NIL.

Built-in Words

The 78 Words of the canonical vocabulary, by family, generated from spec/words.json. 48 of them form the Semantic Kernel and 30 are Standard Words; every one is an ordinary Core Word reached by its plain name, and the tier only says where it sits in the design (Dictionaries and Word identity). Choose a Word for its contract — syntax, stack effect, NIL policy, ERROR conditions — or a page for the explanation with samples.

Contracts: Dictionary and names

CONTRACT Semantic Kernel

The contract of a Word or of a block, as a Record: [ DIV ] 0 GET CONTRACT 'partiality' GET is 'projecting'. For a Symbol naming a Core Word it is the registered record of spec/words.json (LANG.CONTRACT.REGISTRY), keyed by the registry's own field names — name vocabularyTier inputs outputs nilPolicy projection errorWhen partiality purity determinism cost effects, so [ DIV ] 0 GET CONTRACT 'cost' GET asks a Word's cost class before running it. For a Symbol naming a User Word, or for a block of code, it is the contract inferred without running anything — the same inference ajisai check --contract runs from outside the language — keyed inputs outputs partiality purity determinism cost effects — the same keys, in the same vocabulary, so the two compare directly — and confidence gaps, where confidence and gaps carry the check's own trichotomy (LANG.CONTRACT.CHECK) as data: an unresolved dependency is a gap in the answer, not an ERROR. A block is never evaluated, so [ 42 PRINT ] CONTRACT reports consoleWrite under effects without printing. A Symbol that names no Word projects notFound; an operand that is neither a Symbol nor a block is an ERROR (notASymbol).

Syntax
[ ADD ] 0 GET CONTRACT
Stack effect
[ symbol | code ] -> [ record ]
Stack
1 input(s) → 1 output(s)
Operands
control (LANG.FAILURE.PASSTHROUGH)
NIL policy
createsNil; projection: symbolNamesNoWord → notFound
Partiality
projecting
Purity / determinism
pure / stateRelative
Effects
none
ERROR conditions
notASymbol
Clauses
LANG.CONTRACT.REGISTRY, LANG.CONTRACT.CHECK, LANG.DICTIONARY.RESOLUTION, LANG.SOURCE.CODE, LANG.FAILURE.PROJECT

BIND Semantic Kernel

Name a value for the rest of the frame that made it: 5 'N' BIND N N ADD is 10, and [ 1 2 ] [ 'A' 'B' ] BIND B A SUB is 1. One name takes the whole value; several destructure a vector of the same length, position by position. Both operands are consumed and the name pushes the value wherever it is written afterwards, however many times. A binding reaches the blocks written in its frame and never a Word called from it, and it ends when the frame does. A name already held by a Core or User Word is refused, so a name is a Word or a binding and never both. A value that names its own binding, directly or through other bindings, is refused as selfReferentialDefinition, the same rule DEF applies (LANG.DICTIONARY.ACYCLIC).

Syntax
[ 1 2 3 ] 'XS' BIND
Stack effect
[ x ] [ name... ] -> [ ]
Stack
2 input(s) → 0 output(s)
Operands
element, control (LANG.FAILURE.PASSTHROUGH)
NIL policy
consumeNil; projection: none
Partiality
partial
Purity / determinism
pure / stateRelative
Effects
none
ERROR conditions
nonText, nameConflict, shapeMismatch, invalidName, selfReferentialDefinition
Clauses
LANG.SOURCE.FRAME, LANG.DICTIONARY.RESOLUTION

DEF Semantic Kernel

A User Word defined from a body and a name: [ 2 MUL ] 'DOUBLE' DEF 5 DOUBLE is 10. The body may call Core and User Words but never, directly or through others, the Word being defined (selfReferentialDefinition, LANG.DICTIONARY.ACYCLIC). Redefining a User Word replaces it unless others still call it (definitionConflict); a Core Word's name is protectedWord, a name held by a binding is nameConflict, and a name that cannot be written as one token is invalidName.

Syntax
[ 2 MUL ] 'DOUBLE' DEF
Stack effect
[ body ] [ name ] -> [ ]
Stack
2 input(s) → 0 output(s)
Operands
control, control (LANG.FAILURE.PASSTHROUGH)
NIL policy
rejectNil; projection: none
Partiality
partial
Purity / determinism
effectful / stateRelative
Effects
dictionaryWrite
ERROR conditions
invalidName, protectedWord, nameConflict, definitionConflict, selfReferentialDefinition, nonText, invalidDefinitionBody
Clauses
LANG.DICTIONARY.RESOLUTION, LANG.DICTIONARY.MUTATION, LANG.DICTIONARY.ACYCLIC

DEL Semantic Kernel

Delete a User Word from the dictionary: [ 1 ] 'W' DEF 'W' DEL [ W ] 0 GET CONTRACT NIL? is TRUE, since the name then names no Word. A Core Word is refused (protectedWord), a name no User Word holds is wordNotFound, and a Word other User Words still call is definitionConflict until they are deleted first.

Syntax
[ 1 ] 'W' DEF 'W' DEL
Stack effect
[ name ] -> [ ]
Stack
1 input(s) → 0 output(s)
Operands
control (LANG.FAILURE.PASSTHROUGH)
NIL policy
rejectNil; projection: none
Partiality
partial
Purity / determinism
effectful / stateRelative
Effects
dictionaryDelete
ERROR conditions
wordNotFound, protectedWord, nonText, definitionConflict
Clauses
LANG.DICTIONARY.RESOLUTION, LANG.DICTIONARY.MUTATION

DIGEST Semantic Kernel

The content identity of a Word, or the digest of a value's denotation, as text: 8 SQRT DIGEST 2 SQRT 2 SQRT ADD DIGEST EQ is TRUE. A Symbol naming a User Word answers that Word's content identity — the digest over its normalized definition and the identities of the Words it calls that the dictionary already keeps (LANG.DICTIONARY.MUTATION) — and a Symbol naming a Core Word answers the fixed identity of that sealed Word. Any other value, a Symbol naming nothing included, answers the digest of its denotation: two values that EQ calls one value digest alike, however each was built, so 8 SQRT DIGEST equals 2 SQRT 2 SQRT ADD DIGEST, and a NIL digests by its reason. Equal digests mean one thing; unequal digests mean nothing.

Syntax
[ ADD ] 0 GET DIGEST
Stack effect
[ x ] -> [ digest ]
Stack
1 input(s) → 1 output(s)
Operands
element (LANG.FAILURE.PASSTHROUGH)
NIL policy
consumeNil; projection: none
Partiality
total
Purity / determinism
pure / stateRelative
Effects
none
Clauses
LANG.DICTIONARY.MUTATION, LANG.VALUES.DENOTATION, LANG.VALUES.EXACT, LANG.DICTIONARY.RESOLUTION

2.4.2.1 Logic, comparison and arithmetic (AND, EQ, ADD)

Arithmetic

The arithmetic Words work element-wise between Vectors. Each is written with its one English name and has no symbol spelling.

Arithmetic Words

WordMeaning
ADDAddition
SUBSubtraction
MULMultiplication
DIVExact division

Broadcasting rules

Broadcasting is not merely a rule for stretching one-element Vectors; it is one consistent rule applied across the whole set of axes of multi-dimensional Vectors.

  • Axis alignment: two Vectors are aligned axis by axis, starting from the innermost.
  • Stretching length-1 axes: an axis the shorter Vector does not reach is taken to have length 1, and a length-1 axis is reused to match the other side's length. [ 1 2 3 ] [ 10 ] MUL is [ 10/1 20/1 30/1 ], and [ [ 1 2 ] [ 3 4 ] ] [ 10 20 ] MUL is [ [ 10/1 40/1 ] [ 30/1 80/1 ] ].
  • Shape mismatch: when axis lengths differ and neither is 1, it is an error. [ 1 2 3 ] [ 10 20 ] MUL cannot align lengths 3 and 2, so it is an error.
  • Scalars: a scalar needs no axes. It combines with every element, however the Vector is nested, ragged ones included.
  • Rectangularity: two Vectors can broadcast against each other only when both are rectangular, that is, when every axis has a uniform length.
Sample codeExpected valueNotes
[ 1 2 3 ] [ 4 5 6 ] ADD [ 5/1 7/1 9/1 ] Element-wise addition.
Sample codeExpected valueNotes
[ 1 2 3 ] [ 10 ] MUL [ 10/1 20/1 30/1 ] A one-element Vector broadcasts across the whole of the other operand.
Sample codeExpected valueNotes
7 'A' BIND 3 'B' BIND A A B DIV FLOOR B MUL SUB 1/1 The remainder of 7 divided by 3. Remainder has no Word of its own; write a - floor(a/b)·b as it stands.

Other numeric Words

Beyond the four basic operations, the following numeric Words are provided.

  • Sign: negation is written x -1 MUL, and absolute value x 'X' BIND X X MUL SQRT.
  • Extremes: MIN (minimum), MAX (maximum)
  • Square root: SQRT
  • Rounding: FLOOR (round down), ROUND (round half up). Ceiling is written x -1 MUL FLOOR -1 MUL.
  • Rounding to a grid: x d MUL 1/2 ADD FLOOR d DIV rounds a value to a multiple of 1/d. Use it to stop denominators from growing in loops and iterative computations. See The cost of iteration for more.

Division by zero does not crash the program; only the affected elements become NIL. See The NIL rule for more.

Powers, and reading integers and rationals: POW, GCD, RATIO

x y POW answers within the exact field. An integer exponent stays in the base's layer (2 10 POW is 1024, 2 SQRT 2 POW is 2), and an exponent p/2 on a non-negative rational base stays in the field as a power of a square root (2 1/2 POW is exactly the same value as 2 SQRT). Any other exponent — a denominator other than 1 or 2, p/2 on an irrational base, an irrational exponent — leaves the field, so it projects domainMiss, even when the answer would be rational (8 1/3 POW). A negative power of 0 projects divisionByZero; a p/2 power of a negative base has no real answer and projects domainMiss; an exponent too large for the machine to hold projects spaceExhausted.

GCD answers the non-negative greatest common divisor of two integers. Euclid's algorithm is an input-dependent iteration, which cannot be written by definition in this language, where iteration runs only over a Vector that already exists — the machine performs this computation all the time to keep every rational in lowest terms, and this Word merely makes it visible. RATIO opens a rational into a two-element Vector of its reduced numerator and denominator. Since the answer is a Vector, arithmetic lifts over it directly. A non-integer argument (GCD) or an irrational (RATIO) projects domainMiss.

Sample codeExpected valueNotes
2 10 POW 1024/1 An integer exponent stays in the base's layer.
2 1/2 POW 2 SQRT EQ TRUE An exponent p/2 stays in the field: the same value as SQRT.
8 1/3 POW NIL-REASON 'domainMiss' A cube root is outside the field. It projects even when the answer is rational.
-2 1/2 POW NIL-REASON 'domainMiss' A p/2 power of a negative base has no real answer.
12 18 GCD 6/1 Greatest common divisor.
6/4 RATIO [ 3/1 2/1 ] The reduced numerator and denominator.
2 SQRT RATIO NIL-REASON 'domainMiss' An irrational has no numerator.

Automatic lifting over Vectors

Every numeric Word lifts over Vectors automatically and broadcasts by the same rule as ADD. A rectifier that clamps negatives to 0 is written [ -1 2 -3 ] 0 MAX and gives [ 0/1 2/1 0/1 ]; per-feature standard deviation is a single SQRT over the Vector of variances; and the element-wise smaller of two Vectors is [ 1 5 3 ] [ 4 2 6 ] MIN. None of these needs MAP or a block — reach for those only when the operation itself is not a numeric Word. A lane outside SQRT's domain becomes NIL on its own, so [ 4 -1 ] SQRT is [ 2/1 NIL ] rather than a failure of the whole Vector. This lifting is not limited to numeric operations. A Word whose operand is read as a single value — a string, a Boolean — applies element-wise by the same rule when given a Vector or a Record there ([ 'ab' 'c' ] UPPER is [ 'AB' 'C' ]). A one-element Vector is always treated as a Vector, never as its element.

Comparison and Booleans

There are three comparison Words: EQ, LT and GT. Their result is a Boolean, a kind of value of its own, not a number. The remaining three relations are written with NOT: "less than or equal" is a b GT NOT, "greater than or equal" is a b LT NOT, and "not equal" is a b EQ NOT.

Comparison Words

WordMeaning
EQEqual
LTLess than
GTGreater than
Sample codeExpected valueNotes
5 3 GT TRUE A comparison that is decided returns a Boolean.
Sample codeExpected valueNotes
1 1 GT NOT TRUE "1 is less than or equal to 1". A comparison's result is a decided Boolean, so negating it with NOT gives the remaining relation.
Sample codeExpected valueNotes
TRUE 1 EQ FALSE A Boolean is not the number 1.
Sample codeExpected valueNotes
2 SQRT 2 SQRT SUB 0 EQ TRUE \(\sqrt{2} - \sqrt{2}\) is exactly 0. Square roots and their sums, differences, products and quotients compare exactly, so a comparison is decided without relying on a budget.

Exact comparison

Comparison decides exactly across every value the Words can build, rationals and square roots included. So a comparison always returns TRUE or FALSE in finite time.

Logic

Ajisai has two logical Words, AND and NOT, which operate on the Booleans TRUE and FALSE. Like any other Word, each is written by its spelling. Logical OR is written by De Morgan's law as a NOT b NOT AND NOT.

NIL passes through

In a truth position, NIL reads as "unknown" and combines under strong Kleene logic. When the other operand alone decides the answer, that is the answer (an AND with a FALSE is FALSE); when it does not, NIL stays in the result with its reason intact.

Sample codeExpected valueNotes
TRUE FALSE AND FALSE Logical AND of two decided values.
Sample codeExpected valueNotes
TRUE NOT FALSE NOT AND NOT TRUE Logical OR of two decided values, written as a combination of AND and NOT.
Sample codeExpected valueNotes
TRUE NOT FALSE Logical NOT of a decided value.
Sample codeExpected valueNotes
1 0 DIV TRUE AND NIL The left operand became NIL, so its absence reaches the result. Recover where needed by choosing a fallback value.

Contracts: Logic

TRUE Semantic Kernel

The truth value TRUE: TRUE is 1 1 EQ. It is a Boolean, not the number one (LANG.VALUES.DISJOINT), so TRUE 1 EQ is FALSE.

Syntax
TRUE
Stack effect
-> [ TRUE ]
Stack
0 input(s) → 1 output(s)
NIL policy
preserveReason; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
Clauses
LANG.VALUES.TRUTH

FALSE Semantic Kernel

The truth value FALSE: FALSE is 1 2 EQ. It is a Boolean, not the number zero (LANG.VALUES.DISJOINT), so FALSE 0 EQ is FALSE.

Syntax
FALSE
Stack effect
-> [ FALSE ]
Stack
0 input(s) → 1 output(s)
NIL policy
preserveReason; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
Clauses
LANG.VALUES.TRUTH

AND Semantic Kernel

Conjunction under the strong Kleene table (LANG.VALUES.TRUTH): TRUE FALSE AND is FALSE, and element-wise over Vectors, [ TRUE TRUE ] [ TRUE FALSE ] AND is [ TRUE FALSE ]. A NIL read here is UNKNOWN: FALSE settles the answer against it, NIL FALSE AND is FALSE, and anything else leaves it UNKNOWN with its reason kept. A disjunction is a NOT b NOT AND NOT. An operand that is neither a truth value nor a NIL is nonTruthValue.

Syntax
TRUE FALSE AND
Stack effect
[ a ] [ b ] -> [ a AND b ]
Stack
2 input(s) → 1 output(s)
Operands
truth, truth (LANG.FAILURE.PASSTHROUGH)
NIL policy
kleeneAbsorbing; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonTruthValue, shapeMismatch
Clauses
LANG.VALUES.TRUTH, LANG.COLLECTIONS.LIFT

NOT Semantic Kernel

Negation under the strong Kleene table (LANG.VALUES.TRUTH): TRUE NOT is FALSE, and element-wise over Vectors, [ TRUE FALSE ] NOT is [ FALSE TRUE ]. UNKNOWN — a NIL read here — stays UNKNOWN with its reason kept. An operand that is neither a truth value nor a NIL is nonTruthValue.

Syntax
TRUE NOT
Stack effect
[ a ] -> [ NOT a ]
Stack
1 input(s) → 1 output(s)
Operands
truth (LANG.FAILURE.PASSTHROUGH)
NIL policy
kleeneAbsorbing; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonTruthValue
Clauses
LANG.VALUES.TRUTH, LANG.COLLECTIONS.LIFT

SELECT Semantic Kernel

Choose between two already-computed values by a truth value: 'yes' 'no' TRUE SELECT is 'yes' and 'yes' 'no' FALSE SELECT is 'no'. The choice is element-wise (LANG.COLLECTIONS.LIFT), so a Vector of truths weaves two Vectors lane by lane and a one-lane operand is reused across the other's length. An UNKNOWN lane — a NIL read in truth position, whatever its reason — chooses neither and answers that same absence, so the reason survives the choice. Both operands are values the program already built: SELECT evaluates nothing, and whatever computed them ran before it, exactly once.

Syntax
[ 'yes' ] [ 'no' ] TRUE SELECT
Stack effect
[ whenTrue ] [ whenFalse ] [ mask ] -> [ chosen ]
Stack
3 input(s) → 1 output(s)
Operands
element, element, truth (LANG.FAILURE.PASSTHROUGH)
NIL policy
kleeneAbsorbing; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonTruthValue, shapeMismatch
Clauses
LANG.VALUES.TRUTH, LANG.COLLECTIONS.LIFT

Contracts: Comparison

EQ Semantic Kernel

Whether two values are one value (LANG.VALUES.DENOTATION), whatever built each: 1 1/1 EQ is TRUE, 2 SQRT 2 SQRT MUL 2 EQ is TRUE, and [ 1 2 ] [ 1 2 ] EQ is TRUE — EQ compares its operands whole and does not lift. It reads both operands, so an absent one is the answer, as it is for every data operand (LANG.FAILURE.PASSTHROUGH): 1 0 DIV 1 EQ is the UNKNOWN that absence already was, since there is nothing to compare. Inside a Vector a NIL is an element like any other and is compared by its reason, so [ NIL ] [ NIL ] EQ is TRUE; that is the equality MEMBER?, INDEX-OF and UNIQUE use, and a program that wants two possibly absent values compared rather than passed through wraps each in 1 COLLECT first.

Syntax
1 1 EQ
Stack effect
[ a ] [ b ] -> [ TRUE | FALSE ]
Stack
2 input(s) → 1 output(s)
Operands
data, data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
Clauses
LANG.VALUES.TRUTH, LANG.VALUES.EXACT, LANG.VALUES.DENOTATION

LT Semantic Kernel

Whether the left number is less than the right: 1 2 LT is TRUE, element-wise over Vectors, [ 1 5 ] 3 LT is [ TRUE FALSE ]. Order over the exact field always decides (LANG.VALUES.EXACT), irrationals included: 2 SQRT 3/2 LT is TRUE. Only numbers are ordered; anything else is nonNumeric.

Syntax
1 2 LT
Stack effect
[ a ] [ b ] -> [ TRUE | FALSE ]
Stack
2 input(s) → 1 output(s)
Operands
leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric, shapeMismatch
Clauses
LANG.VALUES.TRUTH, LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT

GT Semantic Kernel

Whether the left number is greater than the right: 2 1 GT is TRUE, element-wise over Vectors, [ 1 5 ] 3 GT is [ FALSE TRUE ]. Order over the exact field always decides (LANG.VALUES.EXACT). Only numbers are ordered; anything else is nonNumeric.

Syntax
2 1 GT
Stack effect
[ a ] [ b ] -> [ TRUE | FALSE ]
Stack
2 input(s) → 1 output(s)
Operands
leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric, shapeMismatch
Clauses
LANG.VALUES.TRUTH, LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT

Contracts: Exact arithmetic

ADD Semantic Kernel

The exact sum: 1/3 1/6 ADD is 1/2, and element-wise over Vectors with broadcasting (LANG.COLLECTIONS.LIFT), [ 1 2 ] 10 ADD is [ 11 12 ]. Nothing is rounded: 2 SQRT 2 SQRT ADD is 8 SQRT. A non-number is nonNumeric.

Syntax
1 2 ADD
Stack effect
[ a ] [ b ] -> [ a + b ]
Stack
2 input(s) → 1 output(s)
Operands
leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric, shapeMismatch
Clauses
LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT, LANG.FAILURE.TRICHOTOMY

SUB Standard · shorthand

The exact difference, left minus right: 5 3 SUB is 2, and element-wise over Vectors with broadcasting (LANG.COLLECTIONS.LIFT), 2 [ 1 2 3 ] SUB is [ 1 0 -1 ]. A non-number is nonNumeric.

Syntax
5 3 SUB
Stack effect
[ a ] [ b ] -> [ a - b ]
Stack
2 input(s) → 1 output(s)
Operands
leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric, shapeMismatch
Clauses
LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT, LANG.FAILURE.TRICHOTOMY

MUL Semantic Kernel

The exact product: 2/3 3/4 MUL is 1/2, and element-wise over Vectors with broadcasting (LANG.COLLECTIONS.LIFT), [ 1 2 3 ] [ 10 ] MUL is [ 10 20 30 ]. A non-number is nonNumeric.

Syntax
2 4 MUL
Stack effect
[ a ] [ b ] -> [ a * b ]
Stack
2 input(s) → 1 output(s)
Operands
leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric, shapeMismatch
Clauses
LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT, LANG.FAILURE.TRICHOTOMY

DIV Semantic Kernel

The exact quotient, left over right: 1 3 DIV is exactly 1/3, and element-wise over Vectors with broadcasting (LANG.COLLECTIONS.LIFT), [ 2 4 ] 2 DIV is [ 1 2 ]. A zero divisor projects NIL(divisionByZero), lane by lane. A non-number is nonNumeric.

Syntax
10 2 DIV
Stack effect
[ a ] [ b ] -> [ a / b ]
Stack
2 input(s) → 1 output(s)
Operands
leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: divisorEqualsZero → divisionByZero
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric, shapeMismatch
Clauses
LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT, LANG.FAILURE.TRICHOTOMY, LANG.FAILURE.PROJECT

FLOOR Semantic Kernel

The greatest integer not above the number: 7/3 FLOOR is 2 and -7/3 FLOOR is -3, element-wise over Vectors. Irrationals floor exactly: 2 SQRT FLOOR is 1. A non-number is nonNumeric.

Syntax
7/3 FLOOR
Stack effect
[ x ] -> [ floor x ]
Stack
1 input(s) → 1 output(s)
Operands
leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric
Clauses
LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT, LANG.FAILURE.TRICHOTOMY

ROUND Standard · algorithm

The nearest integer, a tie going away from zero: 5/2 ROUND is 3 and -5/2 ROUND is -3, element-wise over Vectors. FORMAT rounds its last digit the same way. A non-number is nonNumeric.

Syntax
5/2 ROUND
Stack effect
[ x ] -> [ round x ]
Stack
1 input(s) → 1 output(s)
Operands
leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric
Clauses
LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT, LANG.FAILURE.TRICHOTOMY

MIN Standard · namedPattern

The smaller of two numbers: 1 2 MIN is 1, and element-wise over Vectors with broadcasting, [ 3 1 ] 2 MIN is [ 2 1 ]. A non-number is nonNumeric.

Syntax
1 2 MIN
Stack effect
[ a ] [ b ] -> [ min ]
Stack
2 input(s) → 1 output(s)
Operands
leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric, shapeMismatch
Clauses
LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT, LANG.FAILURE.TRICHOTOMY

MAX Standard · namedPattern

The larger of two numbers: 1 2 MAX is 2, and element-wise over Vectors with broadcasting, [ -1 2 -3 ] 0 MAX is [ 0 2 0 ]. A non-number is nonNumeric.

Syntax
1 2 MAX
Stack effect
[ a ] [ b ] -> [ max ]
Stack
2 input(s) → 1 output(s)
Operands
leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric, shapeMismatch
Clauses
LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT, LANG.FAILURE.TRICHOTOMY

SQRT Semantic Kernel

The exact square root of a non-negative number: 4 SQRT is 2, and 2 SQRT is the irrational itself, carried in multiquadratic normal form and compared with no rounding (LANG.VALUES.EXACT), so 2 SQRT 2 SQRT MUL is 2. The radicand is reduced to its square-free part, so one number has one form however it was built: 8 SQRT is 2 SQRT 2 MUL. That reduction factors the radicand and is charged to the run's numeric work; a radicand the remaining work cannot factor is resourceLimitExceeded. Element-wise over Vectors, and a negative radicand projects NIL(domainMiss): [ 4 -1 ] SQRT is [ 2 NIL ] with the second lane absent for that reason. A non-number is nonNumeric.

Syntax
2 SQRT
Stack effect
[ x ] -> [ sqrt(x) ]
Stack
1 input(s) → 1 output(s)
Operands
leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: negativeScalar → domainMiss
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric
Clauses
LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT, LANG.FAILURE.TRICHOTOMY, LANG.FAILURE.PROJECT

POW Semantic Kernel

Exact power x y POW, element-wise over Vectors, answered inside the exact field. An integer exponent keeps the result in the base's own tier: 2 10 POW is 1024, 2 SQRT 2 POW is 2, 2 -1 POW is 1/2. An exponent p/2 over a non-negative rational base stays in the field too — 2 1/2 POW is exactly what 2 SQRT answers, and 2 3/2 POW is 2 2 SQRT MUL. 0 y POW with a negative y projects divisionByZero; a negative base under p/2 has no real value and projects domainMiss; every other exponent — a denominator other than 1 or 2, p/2 over an irrational base, an irrational exponent — leaves the field and projects domainMiss as well (8 1/3 POW, 2 2 SQRT POW); an exponent past what the machine will materialize projects spaceExhausted. SQRT remains the Word that builds the field; POW is not its sugar.

Syntax
2 10 POW
Stack effect
[ x ] [ y ] -> [ x^y ]
Stack
2 input(s) → 1 output(s)
Operands
leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: zeroBaseNegativeExponent,negativeBaseFractionalExponent,exponentOutsideTheField,exponentTooLargeToMaterialize → divisionByZero, domainMiss, spaceExhausted
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric, shapeMismatch
Clauses
LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT, LANG.FAILURE.TRICHOTOMY, LANG.FAILURE.PROJECT

GCD Standard · operational

The greatest common divisor of two integers, non-negative, element-wise over Vectors: 12 18 GCD is 6, 0 0 GCD is 0. Euclid's algorithm is input-dependent repetition, which a definition cannot write in a language that repeats only over a Vector that already exists; the machine already runs it to keep every rational reduced, so the Word only exposes it. A non-integer operand — a fraction or an irrational — projects domainMiss.

Syntax
12 18 GCD
Stack effect
[ a ] [ b ] -> [ gcd ]
Stack
2 input(s) → 1 output(s)
Operands
leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: nonIntegerOperand → domainMiss
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric, shapeMismatch
Clauses
LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT, LANG.FAILURE.TRICHOTOMY, LANG.FAILURE.PROJECT

RATIO Standard · operational

A rational opened into its reduced numerator and denominator, as a two-element Vector with the denominator positive: 6/4 RATIO is [ 3 2 ], -3 RATIO is [ -3 1 ], element-wise over Vectors. The language advertises exact rationals; this is the Word that reads their two parts back, and because the answer is a Vector, arithmetic lifts over it as it does over any other. An irrational (2 SQRT) has no numerator and projects domainMiss.

Syntax
6/4 RATIO
Stack effect
[ q ] -> [ [ numerator denominator ] ]
Stack
1 input(s) → 1 output(s)
Operands
leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: irrationalOperand → domainMiss
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric
Clauses
LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT, LANG.FAILURE.TRICHOTOMY, LANG.FAILURE.PROJECT

2.4.2.2 Vector operations (GET, LENGTH)

Vector indices start at 0. A negative index counts from the end. Reading Words such as GET and LENGTH consume the Vector they read, like every other Word. If you want to use the original Vector later, name it with BIND before reading it.

Reading with GET

Sample codeExpected valueNotes
[ 10 20 30 ] 0 GET 10/1 Index 0 is the first element. Both the Vector and the index are consumed.
Sample codeExpected valueNotes
[ 10 20 30 ] -1 GET 30/1 A negative index counts from the end.

The answer takes the shape of the index

Given a bare integer, GET returns the element itself; given a Vector of indices, it lifts element-wise and returns a Vector of the selections, in the order the index gives. So picking out several positions, reordering and reversing are each a single call, and an index that points at nothing leaves NIL in its position instead of ruining the rest.

The answer takes the shape of the index. A Vector of indices stays a Vector even with one element, so the answer is always a Vector too. Even when a computed selection happens to narrow down to one position — the left side of a split, a class with a single member — LENGTH or FOLD written for a Vector keeps working.

Sample codeExpected valueNotes
[ 10 20 30 40 ] [ 2 ] GET [ 30/1 ] One index, but passed as a Vector, so the result is a Vector. With [ 2 0 ] it is [ 30/1 10/1 ], and the code that follows never has to handle two shapes.
Sample codeExpected valueNotes
[ 10 20 30 40 ] [ 2 0 ] GET [ 30/1 10/1 ] The answer follows the index, not the Vector. This is a gather, and the same call shape also reorders and permutes.

Other basic operations

Sample codeExpected valueNotes
[ 1 2 3 ] LENGTH 3/1 The element count. The Vector is consumed.
Sample codeExpected valueNotes
[ 1 2 3 ] 'V' BIND V V LENGTH [ 1/1 2/1 3/1 ] 3/1 Once named, the original Vector survives being read.
Sample codeExpected valueNotes
[ 1 2 ] [ 3 4 ] CONCAT [ 1/1 2/1 3/1 4/1 ] Concatenates two Vectors.
Sample codeExpected valueNotes
[ 1 2 3 ] REVERSE [ 3/1 2/1 1/1 ] Reverses the order of the elements.
Sample codeExpected valueNotes
1 5 RANGE [ 1/1 2/1 3/1 4/1 5/1 ] The integers from start to end, inclusive.
Sample codeExpected valueNotes
0 5 RANGE 2 MUL [ 0/1 2/1 4/1 6/1 8/1 10/1 ] A step is made with multiplication. If the end is below the start, the sequence descends.
Sample codeExpected valueNotes
[ 1 2 3 4 5 ] 3 TAKE [ 1/1 2/1 3/1 ] Keeps the first N elements.
[ 1 2 3 4 5 ] -2 TAKE [ 4/1 5/1 ] A negative count takes from the end.
Sample codeExpected valueNotes
[ 3 1 2 ] SORT [ 1/1 2/1 3/1 ] Sorts the whole Vector in ascending order.

More structural Words: TAKE DROP REVERSE COLLECT FILL PUT. INDEX-OF searches instead of indexing: [ 10 20 30 ] 20 INDEX-OF is 1/1, and a value that is not found is NIL, not an error. To ask only whether a value is present, use MEMBER? ([ 1 2 3 ] 2 MEMBER? is TRUE); for an ascending Vector use BSEARCH ([ 1 3 5 7 ] [ 5 ] BSEARCH is [ 2/1 ] — binary search: the iteration of halving an interval again and again is not something you can write yourself). BSEARCH raises an error when given a Vector that is not ascending.

Ordering, counting and shape

The operations that need to see a whole Vector at once are covered by seven Words. Each of them could be written out using only the rest of the vocabulary, but written out it costs asymptotically more. That alone is why they exist as Words.

WordExampleResult
ORDER[ 30 10 20 ] ORDER[ 1/1 2/1 0/1 ] — the positions in ascending order. Ties keep their original positions
UNIQUE[ 'b' 'a' 'b' ] UNIQUE[ 'b' 'a' ] — the distinct values, in order of first appearance
TALLY[ 'b' 'a' 'b' ] TALLY[ 'b' 'a' ] [ 2/1 1/1 ] RECORD — the count of each element, as a Record. The keys follow UNIQUE's order; take VALUES for a Vector of just the counts
ZIP[ [ 1 2 ] [ 3 4 ] ] ZIP[ [ 1/1 3/1 ] [ 2/1 4/1 ] ] — regrouped by position. This is also a transpose
PUT[ 1 2 3 ] 1 9 PUT[ 1/1 9/1 3/1 ] — replaces one position. A negative index counts from the end
GROUP[ 'a' 'b' 'a' ] [ 1 2 3 ] GROUP[ 'a' 'b' ] [ [ 1/1 3/1 ] [ 2/1 ] ] RECORD — the values grouped by key, as a Record. The keys follow UNIQUE's order, and 'a' GET takes out one group

A Vector's shape — the length of each axis, listed from the outside in — is the notion element-wise lifting uses to line operands up. The following four Words read and rewrite it from a program. FLATTEN and DEPTH cannot be written yourself: the nesting depth is not known in advance, and a language with neither recursion nor unbounded loops cannot walk a structure of unknown depth.

WordExampleResult
SHAPE[ [ 1 2 ] [ 3 4 ] [ 5 6 ] ] SHAPE[ 3/1 2/1 ] — the length of each axis of a rectangular nesting. A ragged Vector such as [ [ 1 2 ] [ 3 ] ] has no shape, so it is NIL (domainMiss)
RESHAPE[ 1 2 3 4 5 6 ] [ 2 3 ] RESHAPE[ [ 1/1 2/1 3/1 ] [ 4/1 5/1 6/1 ] ] — rebuilds the leaves, in order, into a new shape. An error if the product of the shape differs from the number of leaves; a shape that is too large is NIL (spaceExhausted)
FLATTEN[ [ 1 [ 2 3 ] ] [ 4 ] ] FLATTEN[ 1/1 2/1 3/1 4/1 ] — folds every axis, however deep, into one. CONCAT goes only one level
DEPTH[ 1 [ 2 [ 3 ] ] ] DEPTH3/1 — the nesting depth. A number or text is 0; a flat Vector is 1

To apply a block to each value at a known depth, nest MAP that many times. At depth 2 that is [ [ 1 2 ] [ 3 4 ] ] [ [ 10 MUL ] MAP ] MAP, and the result is [ [ 10/1 20/1 ] [ 30/1 40/1 ] ].

The one to reach for first is ORDER. SORT returns what the sorted values are and throws away where they came from; ORDER keeps the positions. So anything that has to carry a payload alongside its key — k nearest neighbours, top-k scores, the position of the median, rankings — takes the form of ORDER followed by GET.

Sample codeExpected valueNotes
[ 18 13 1 1 13 2 ] ORDER 3 TAKE 'P' BIND
[ 'f' 'e' 'd' 'c' 'b' 'a' ] P GET
[ 'd' 'c' 'a' ] The labels of the three smallest distances. Ties are resolved by original position.

UNIQUE and TALLY work on any value, not only numbers. Labels are usually text, so this is a practical difference. ORDER and SORT, on the other hand, need a flat Vector of numbers, because that is where an exact ordering is defined. GROUP pairs values with keys by position, so its two operands must have the same length.

Contracts: Vectors

GET Semantic Kernel

Read a container: the element of a Vector at an index, or the value of a Record under a key — [ 10 20 30 ] 1 GET is 20, R 'x' GET is what R holds under 'x'. A negative index counts from the end. The key is a leaf, so a Vector of indices or keys lifts to a Vector of answers in the order they were named: [ 10 20 30 ] [ 2 0 ] GET is [ 30 10 ], a permutation or a gather in one call. What names nothing is a well-formed question with no answer, so it projects where it stands rather than raising: an index past either end is NIL(indexOutOfBounds), a key the Record does not hold is NIL(notFound), and [ 10 20 30 ] [ 0 9 ] GET is [ 10/1 NIL ]. HAS? asks presence alone, so a stored NIL is told apart from an absent key. PUT is the writing half. A first operand that is neither a Vector nor a Record is an ERROR (nonContainer); an index that is not an integer is invalidInteger.

Syntax
[ 10 20 30 ] 1 GET
Stack effect
[ container ] [ key ] -> [ value ]
Stack
2 input(s) → 1 output(s)
Operands
data, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: indexOutOfBounds,notFound → indexOutOfBounds, notFound
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonContainer, invalidInteger
Clauses
LANG.VALUES.VECTOR, LANG.RECORDS.STRUCTURE, LANG.COLLECTIONS.LIFT, LANG.FAILURE.PROJECT, LANG.MACHINE.LIMITS

LENGTH Semantic Kernel

How many elements a Vector holds: [ 1 2 3 ] LENGTH is 3 and [ ] LENGTH is 0. Only the outermost axis is counted — [ [ 1 2 ] [ 3 ] ] LENGTH is 2; SHAPE answers every axis. A non-Vector is nonVector.

Syntax
[ 1 2 3 ] LENGTH
Stack effect
[ vec ] -> [ count ]
Stack
1 input(s) → 1 output(s)
Operands
data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector
Clauses
LANG.VALUES.VECTOR, LANG.MACHINE.LIMITS

TAKE Standard · namedPattern

Take the first N or last -N elements of a vector: [ 1 2 3 ] 2 TAKE is [ 1 2 ] and [ 1 2 3 ] -2 TAKE is [ 2 3 ]. A count larger than the vector projects to NIL(indexOutOfBounds): asking for more than there is names a position past the end, which is the same question GET answers past the end and is answered the same way — well-formed data that did not work out, not a malformed program (LANG.FAILURE.PROJECT). So [ 1 2 3 ] 9 TAKE is NIL, and a caller who wants something else writes it: [ 1 2 3 ] 9 TAKE 'S' BIND [ 1 2 3 ] S S NIL? SELECT answers the whole vector instead. A count that is not an integer at all is still invalidInteger, because that is the program being wrong.

Syntax
[ 1 2 3 4 5 ] 3 TAKE
Stack effect
[ vec ] [ n ] -> [ prefix ]
Stack
2 input(s) → 1 output(s)
Operands
data, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: indexOutOfBounds → indexOutOfBounds
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector, invalidInteger
Clauses
LANG.VALUES.VECTOR, LANG.COLLECTIONS.LIFT, LANG.MACHINE.LIMITS, LANG.FAILURE.PROJECT

DROP Standard · namedPattern

Drop the first N or last -N elements of a vector and answer the rest. TAKE's counterpart: [ 1 2 3 4 5 ] 2 DROP is [ 3 4 5 ] and [ 1 2 3 4 5 ] -2 DROP is [ 1 2 3 ], so [ n ] TAKE and [ n ] DROP split one vector into two halves that CONCAT joins back. A count larger than the vector projects to NIL(indexOutOfBounds), exactly as TAKE's does: it names a position past the end, which is well-formed data that did not work out (LANG.FAILURE.PROJECT). A count that is not an integer at all is still invalidInteger, because that is the program being wrong.

Syntax
[ 1 2 3 4 5 ] 2 DROP
Stack effect
[ vec ] [ n ] -> [ rest ]
Stack
2 input(s) → 1 output(s)
Operands
data, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: indexOutOfBounds → indexOutOfBounds
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector, invalidInteger
Clauses
LANG.VALUES.VECTOR, LANG.COLLECTIONS.LIFT, LANG.MACHINE.LIMITS, LANG.FAILURE.PROJECT

CONCAT Semantic Kernel

Join two vectors end to end: [ 1 2 ] [ 3 ] CONCAT is [ 1 2 3 ]. Elements are kept as they are — a nested Vector stays nested. Both operands must be Vectors (nonVector).

Syntax
[ 1 2 ] [ 3 4 ] CONCAT
Stack effect
[ a ] [ b ] -> [ a ++ b ]
Stack
2 input(s) → 1 output(s)
Operands
data, data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector
Clauses
LANG.VALUES.VECTOR, LANG.MACHINE.LIMITS

REVERSE Standard · namedPattern

The elements of a Vector in the opposite order: [ 1 2 3 ] REVERSE is [ 3 2 1 ]. Only the outermost axis is reversed: [ [ 1 2 ] 3 ] REVERSE is [ 3 [ 1 2 ] ]. A non-Vector is nonVector.

Syntax
[ 1 2 3 ] REVERSE
Stack effect
[ vec ] -> [ reversed ]
Stack
1 input(s) → 1 output(s)
Operands
data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector
Clauses
LANG.VALUES.VECTOR, LANG.MACHINE.LIMITS

COLLECT Semantic Kernel

Take N values off the stack and answer them as one Vector, first-pushed first: 1 2 3 3 COLLECT is [ 1 2 3 ]. N must be a non-negative integer (invalidInteger), and a stack holding fewer than N values is stackUnderflow.

Syntax
1 2 3 3 COLLECT
Stack effect
[ v1 ] ... [ vn ] [ n ] -> [ [ v1 ... vn ] ]
Stack
variable input(s) → 1 output(s)
NIL policy
rejectNil; projection: none
Partiality
partial
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
invalidInteger, stackUnderflow
Clauses
LANG.VALUES.VECTOR, LANG.MACHINE.LIMITS

RANGE Semantic Kernel

Every integer from a start to an end, both included: 0 3 RANGE is [ 0 1 2 3 ], and 3 0 RANGE counts down, [ 3 2 1 0 ]. There is no step operand — a stride is a multiplication of the sequence, 0 3 RANGE 3 MUL is [ 0 3 6 9 ] — so the bounds alone decide the direction and no pair of bounds describes an infinite sequence. A bound that is not an integer is an ERROR (invalidInteger); a sequence longer than the machine materializes projects NIL(spaceExhausted). Both bounds are leaves, so a Vector of bounds lifts to one sequence per lane.

Syntax
0 5 RANGE
Stack effect
[ start ] [ end ] -> [ seq ]
Stack
2 input(s) → 1 output(s)
Operands
leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: materializationBudgetExceeded → spaceExhausted
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
invalidInteger, shapeMismatch
Clauses
LANG.VALUES.VECTOR, LANG.COLLECTIONS.LIFT, LANG.MACHINE.LIMITS, LANG.FAILURE.PROJECT

FILL Standard · operational

A Vector of a given shape with every leaf one value: [ 2 3 ] 0 FILL is [ [ 0 0 0 ] [ 0 0 0 ] ], and [ 2 ] 'a' FILL is [ 'a' 'a' ]. The shape comes first, as in RESHAPE, and is what SHAPE answers — a Vector of non-negative integers, so [ 0 ] 0 FILL is [ ] and the empty shape [ ] answers the value itself, rank 0 — and anything else is invalidShape. The value is a leaf of any domain, and a Vector of values lifts to one filled Vector each. A shape too large to materialize — too many elements, or more axes than the nesting ceiling — projects NIL(spaceExhausted).

Syntax
[ 2 2 ] 0 FILL
Stack effect
[ shape ] [ value ] -> [ filled ]
Stack
2 input(s) → 1 output(s)
Operands
data, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: materializationBudgetExceeded → spaceExhausted
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
invalidShape
Clauses
LANG.VALUES.VECTOR, LANG.COLLECTIONS.LIFT, LANG.MACHINE.LIMITS, LANG.FAILURE.PROJECT

SHAPE Semantic Kernel

The lengths of a rectangular vector's axes, outermost first: [ [ 1 2 ] [ 3 4 ] ] SHAPE is [ 2 2 ] and [ 1 2 3 ] SHAPE is [ 3 ], and a value that is not a Vector has the empty shape: 5 SHAPE is [ ], rank 0, as 5 DEPTH is 0. This is the shape LANG.COLLECTIONS.LIFT already aligns operands by, made observable. A ragged vector has no shape, so it projects to NIL(domainMiss): [ [ 1 2 ] [ 3 ] ] SHAPE is a reasoned absence, not an error, because the vector is well-formed data that the question does not fit. LENGTH answers the outermost axis alone.

Syntax
[ [ 1 2 ] [ 3 4 ] ] SHAPE
Stack effect
[ x ] -> [ shape ]
Stack
1 input(s) → 1 output(s)
Operands
data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: raggedNesting → domainMiss
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
Clauses
LANG.VALUES.VECTOR, LANG.FAILURE.PROJECT

RESHAPE Semantic Kernel

Regroup a vector's leaves, in order, under a new shape: [ 1 2 3 4 5 6 ] [ 2 3 ] RESHAPE is [ [ 1 2 3 ] [ 4 5 6 ] ], and SHAPE RESHAPE on a rectangular vector gives it back. The leaves are everything FLATTEN would answer, however deeply they were nested. The shape is what SHAPE answers — a Vector of non-negative integers, the empty one included, whose product must equal the leaf count, so [ 5 ] [ ] RESHAPE is 5 and [ ] [ 2 0 ] RESHAPE is [ [ ] [ ] ]; any other shape is ERROR(invalidShape), because nothing is padded or repeated to make it fit. A well-formed shape too large to materialize — too many elements, or more axes than the nesting ceiling — projects to NIL(spaceExhausted), as RANGE and FILL do (LANG.COLLECTIONS.BUDGET).

Syntax
[ 1 2 3 4 5 6 ] [ 2 3 ] RESHAPE
Stack effect
[ vec ] [ shape ] -> [ reshaped ]
Stack
2 input(s) → 1 output(s)
Operands
data, data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: materializationBudgetExceeded → spaceExhausted
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector, invalidShape
Clauses
LANG.VALUES.VECTOR, LANG.COLLECTIONS.BUDGET, LANG.MACHINE.LIMITS, LANG.FAILURE.PROJECT

FLATTEN Semantic Kernel

Collapse every axis into one: [ [ 1 [ 2 3 ] ] [ 4 ] ] FLATTEN is [ 1 2 3 4 ], the leaves in index order however deeply they were nested. CONCAT joins two vectors and flattens one level; FLATTEN takes one vector and flattens all of them. It cannot be written as a user definition: the depth is not known in advance, and a language with no recursion and no unbounded loop cannot walk a structure of unknown depth (LANG.DICTIONARY.ACYCLIC).

Syntax
[ [ 1 [ 2 3 ] ] [ 4 ] ] FLATTEN
Stack effect
[ vec ] -> [ flat ]
Stack
1 input(s) → 1 output(s)
Operands
data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector
Clauses
LANG.VALUES.VECTOR, LANG.DICTIONARY.ACYCLIC

DEPTH Semantic Kernel

How deeply Vectors nest: anything that is not a Vector — a number, a text, a truth, a Symbol, a Record — is 0, a flat vector is 1, and a vector is one more than its deepest element, so [ 1 [ 2 [ 3 ] ] ] DEPTH is 3 and [ ] DEPTH is 1. Like FLATTEN it cannot be written as a user definition, because the very thing it measures is what a non-recursive program cannot walk.

Syntax
[ 1 [ 2 [ 3 ] ] ] DEPTH
Stack effect
[ x ] -> [ n ]
Stack
1 input(s) → 1 output(s)
Operands
data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
Clauses
LANG.VALUES.VECTOR, LANG.DICTIONARY.ACYCLIC

SORT Standard · operational

A copy of a Vector of numbers in ascending order: [ 3 1 2 ] SORT is [ 1 2 3 ]. Order is exact, irrationals included. Only numbers are ordered (nonNumeric), and a non-Vector is nonVector. ORDER answers the permutation instead.

Syntax
[ 3 1 2 ] SORT
Stack effect
[ vec ] -> [ sorted ]
Stack
1 input(s) → 1 output(s)
Operands
data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector, nonNumeric
Clauses
LANG.VALUES.VECTOR, LANG.MACHINE.LIMITS

ORDER Standard · operational

The indices that would sort a Vector of numbers ascending: [ 30 10 20 ] ORDER is [ 1 2 0 ], so gathering by it sorts: [ 30 10 20 ] 'V' BIND V V ORDER GET is [ 10 20 30 ]. Ties keep their original order: [ 2 1 2 1 ] ORDER is [ 1 3 0 2 ]. Only numbers are ordered (nonNumeric), and a non-Vector is nonVector.

Syntax
[ 30 10 20 ] ORDER
Stack effect
[ vec ] -> [ permutation ]
Stack
1 input(s) → 1 output(s)
Operands
data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector, nonNumeric
Clauses
LANG.VALUES.VECTOR, LANG.MACHINE.LIMITS

UNIQUE Standard · operational

The distinct elements of a Vector, each at its first occurrence: [ 3 1 3 ] UNIQUE is [ 3 1 ]. Distinct means not one value (LANG.VALUES.DENOTATION), so it works on texts, nested Vectors and NILs alike, and [ 2 4/2 ] UNIQUE is [ 2 ]. A non-Vector is nonVector.

Syntax
[ 'a' 'b' 'a' ] UNIQUE
Stack effect
[ vec ] -> [ distinct ]
Stack
1 input(s) → 1 output(s)
Operands
data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector
Clauses
LANG.VALUES.VECTOR, LANG.MACHINE.LIMITS

ZIP Standard · operational

Vectors of equal length bundled position by position: [ [ 1 2 ] [ 3 4 ] [ 5 6 ] ] ZIP is [ [ 1 3 5 ] [ 2 4 6 ] ], so a matrix transposes and ZIP ZIP gives it back. Rows of different lengths are shapeMismatch; an operand that is not a Vector of Vectors is nonVector.

Syntax
[ [ 1 2 ] [ 3 4 ] ] ZIP
Stack effect
[ [ vec... ] ] -> [ [ tuple... ] ]
Stack
1 input(s) → 1 output(s)
Operands
data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector, shapeMismatch
Clauses
LANG.VALUES.VECTOR, LANG.MACHINE.LIMITS

PUT Semantic Kernel

Write a container: a copy of a Vector with the element at an index replaced, or of a Record with a key set — [ 1 2 3 ] 1 9 PUT is [ 1 9 3 ], R 'z' 3 PUT is R with 'z' set to 3. A Record key already present keeps its position and takes the new value; an absent key is appended, so key order records the order keys arrived in. A Vector's positions are fixed by its length, so an index past either end names no slot and projects NIL(indexOutOfBounds), exactly as GET does — the Vector the caller wanted preserved is the one they wrote. The value is carried, so it may be anything, a NIL included (a stored absence). Neither operand is changed: containers are values. GET is the reading half. A first operand that is neither a Vector nor a Record is an ERROR (nonContainer); an index that is not an integer is invalidInteger.

Syntax
[ 1 2 3 ] 1 9 PUT
Stack effect
[ container ] [ key ] [ value ] -> [ container ]
Stack
3 input(s) → 1 output(s)
Operands
data, leaf, element (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: indexOutOfBounds → indexOutOfBounds
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonContainer, invalidInteger
Clauses
LANG.VALUES.VECTOR, LANG.RECORDS.STRUCTURE, LANG.COLLECTIONS.LIFT, LANG.FAILURE.PROJECT, LANG.MACHINE.LIMITS

INDEX-OF Standard · namedPattern

The index of the first element equal to the value: [ 10 20 30 ] 20 INDEX-OF is 1. The value is an element, compared whole, so a Vector needle looks for an equal Vector. A value the Vector does not contain projects NIL(notFound); MEMBER? asks the same question as a truth value.

Syntax
[ 1 2 ] 2 INDEX-OF
Stack effect
[ vec ] [ x ] -> [ idx ]
Stack
2 input(s) → 1 output(s)
Operands
data, element (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: notFound → notFound
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector
Clauses
LANG.VALUES.VECTOR, LANG.MACHINE.LIMITS, LANG.FAILURE.PROJECT

MEMBER? Standard · namedPattern

Whether the value occurs in the vector: [ 1 2 3 ] 2 MEMBER? is TRUE. Membership is value equality, the equality UNIQUE and INDEX-OF use, so it works on texts, nested vectors and NILs as well as numbers. The needle is one value, compared rather than read, so a Vector needle is looked for as an element: [ [ 1 ] 2 ] [ 1 ] MEMBER? is TRUE. It is INDEX-OF NIL? NOT.

Syntax
[ 1 2 3 ] 2 MEMBER?
Stack effect
[ vec ] [ x ] -> [ TRUE | FALSE ]
Stack
2 input(s) → 1 output(s)
Operands
data, element (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector
Clauses
LANG.VALUES.VECTOR, LANG.MACHINE.LIMITS

BSEARCH Standard · algorithm

The index of each key in an ascending vector, found by halving: [ 1 3 5 7 ] [ 5 ] BSEARCH is [ 2 ], a single key answers a single index, and a key that is not there is a NIL(notFound) lane. The vector must be in ascending order; one that is not raises unsortedInput, since a binary search over unordered data would answer something rather than nothing. Checking the order is one pass over the vector, and each key then costs O(log n), so m keys cost O(n + m log n) against INDEX-OF's O(m·n) — and halving a range until it is empty is a loop whose length depends on the data, which a language with no unbounded loop cannot write.

Syntax
[ 1 3 5 7 ] [ 5 ] BSEARCH
Stack effect
[ sorted ] [ keys ] -> [ indices ]
Stack
2 input(s) → 1 output(s)
Operands
data, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: notFound → notFound
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector, unsortedInput, nonNumeric
Clauses
LANG.VALUES.VECTOR, LANG.COLLECTIONS.LIFT, LANG.MACHINE.LIMITS, LANG.FAILURE.PROJECT

2.4.2.3 Keyed data (RECORD, GET)

Named data — a price list, a configuration, one row of a table — is held in Ajisai by the Record, the seventh value domain. A Record pairs a key column with a value column by position; the keys keep the order they were given in, and no key appears twice. Any value can be a key (usually a string). A Record is not a Vector, so Vector Words do not accept a Record and Record Words do not accept a Vector. There is no implicit conversion in either direction.

A Record is made with RECORD, which takes a key column and a value column and pairs them by position. A key with no value to pair is a length-mismatch error, and a key given twice is a duplicateKey error.

It is displayed in the form [ 'x' 'y' ] [ 1/1 2/1 ] RECORD: the very program that builds the Record, which gives the same value when written as it is. A Record inside a Vector is displayed as the elements gathered with COLLECT, as in [ 'a' ] [ 1/1 ] RECORD 1 COLLECT, because a name inside [ ] is a Symbol, so RECORD written there would not build a Record.

Sample codeExpected valueNotes
[ 'x' 'y' ] [ 1 2 ] RECORD [ 'x' 'y' ] [ 1/1 2/1 ] RECORD Pairs a key column with a value column by position.
[ 'a' ] [ 1 ] RECORD [ 'b' ] [ 2 ] RECORD 2 COLLECT LENGTH 2/1 A Record becomes one element of a Vector.
[ 'v' 'r' ] [ 1 2 ] [ 'k' ] [ 3 ] RECORD 2 COLLECT RECORD [ 'v' 'r' ] [ 1/1 2/1 ] [ 'k' ] [ 3/1 ] RECORD 2 COLLECT RECORD Put a Record in the value column and it nests.
WordExampleResult
RECORD[ 'x' 'y' ] [ 1 2 ] RECORD[ 'x' 'y' ] [ 1/1 2/1 ] RECORD — pairs a key column with a value column by position. Different lengths are an error, and a key given twice is a duplicateKey error (the later one is not silently kept)
KEYS[ 'x' 'y' ] [ 1 2 ] RECORD KEYS[ 'x' 'y' ] — the key column, in the order given
VALUES[ 'x' 'y' ] [ 1 2 ] RECORD VALUES[ 1/1 2/1 ] — the value column, lined up by position with KEYS. From here on the Vector vocabulary applies as it is
GET[ 'x' 'y' ] [ 1 2 ] RECORD 'y' GET2/1 — the value under a key. GET, which reads a Vector by position, reads a Record by key. A missing key is NIL (notFound)
PUT[ 'x' ] [ 1 ] RECORD 'y' 2 PUT[ 'x' 'y' ] [ 1/1 2/1 ] RECORD — a new Record with a value given to one key (the same Word as PUT, which rewrites a Vector position). An existing key keeps its position and only its value changes; a new key is added at the end
WITHOUT[ 'x' 'y' ] [ 1 2 ] RECORD 'x' WITHOUT[ 'y' ] [ 2/1 ] RECORD — a new Record without one key. Removing a key that is not there is NIL (notFound)
HAS?[ 'x' ] [ 1 ] RECORD 'x' HAS?TRUE — whether a key is present. It answers without reading the value, so it tells "no such key" apart from "the value is NIL"
MERGE[ 'x' 'y' ] [ 1 2 ] RECORD [ 'y' 'z' ] [ 9 3 ] RECORD MERGE[ 'x' 'y' 'z' ] [ 1/1 9/1 3/1 ] RECORD — the union of two Records. On a shared key the right wins; the left's order is kept and keys only on the right are added at the end. This is the shape of layering overrides on defaults

Record identity and order

A Record is told apart by its key column and value column alone (identity model). Records with the same keys in the same order and equal corresponding values are the same value, however they were built. Order is observable structure, so [ 'x' 'y' ] [ 1 2 ] RECORD and [ 'y' 'x' ] [ 2 1 ] RECORD are different values. EQ answers this identity, and UNIQUE and TALLY can count Records as elements.

Sample codeExpected valueNotes
[ 'x' 'y' ] [ 1 2 ] RECORD [ 'x' 'y' ] [ 1 2 ] RECORD EQ TRUE Built twice, it is still one value.
[ 'x' 'y' ] [ 1 2 ] RECORD [ 'y' 'x' ] [ 2 1 ] RECORD EQ FALSE Different key order, different value.

Only arithmetic and comparison lift onto the values

The only existing Words that act on a Record are arithmetic and comparison. Combining a number or a Vector with a Record leaves the keys as they are and applies the operation to each value. Two Records combine value by value only when their key columns are equal; otherwise it is a shapeMismatch error. Every other Word — LENGTH, MAP, AND, CHARS and so on — raises its own operand error when given a Record. To treat a Record as a Vector, cross over explicitly with VALUES.

Sample codeExpected valueNotes
[ 'x' 'y' ] [ 1 2 ] RECORD 10 MUL [ 'x' 'y' ] [ 10/1 20/1 ] RECORD The keys are unchanged; only the values are multiplied by 10.
[ 'x' 'y' ] [ 1 2 ] RECORD 1 GT [ 'x' 'y' ] [ FALSE TRUE ] RECORD Comparison follows the same rule and gives a Record of Booleans.
[ 'x' 'y' ] [ 1 2 ] RECORD [ 'x' 'y' ] [ 1 2 ] RECORD ADD [ 'x' 'y' ] [ 2/1 4/1 ] RECORD Two Records with equal key columns add value by value.

Missing keys

Reading a missing key with GET is a well-formed question with no answer. So it is not an error: it returns a NIL with reason notFound, and the recovery phrase works on it unchanged. To ask about presence itself, use HAS?. WITHOUT follows the same discipline and returns NIL when asked to remove a missing key. Just as GET, TAKE and PUT project an out-of-range position, a misspelled key never slips through silently.

Sample codeExpected valueNotes
[ 'x' 'y' ] [ 1 2 ] RECORD 'z' GET 'S' BIND 0 S S NIL? SELECT 0/1 The fallback 0 is chosen for the missing key.
[ 'x' 'y' ] [ 1 2 ] RECORD 'z' GET NIL-REASON 'notFound' The reason is a protocol string, so you can branch on it.

TALLY and GROUP return Records: TALLY maps "element → count", and GROUP maps "key → the Vector of that key's values", both with keys in order of first appearance. Taking KEYS gives exactly UNIQUE's answer, and taking VALUES gives the Vector of counts or of groups, so no power is lost. Hold a table as a Record of "column name → column Vector", and 'price' GET takes out one column while arithmetic computes column by column.

Contracts: Records

TALLY Standard · operational

How many times each distinct element occurs, as a Record from element to count: [ 'b' 'a' 'b' ] TALLY is [ 'b' 'a' ] [ 2/1 1/1 ] RECORD, keys in order of first appearance. KEYS is exactly what UNIQUE answers and VALUES is the aligned count Vector, so no separate UNIQUE call is needed to learn what each count counts. Works for every value, not only numbers. A non-Vector operand is an ERROR.

Syntax
[ 'a' 'b' 'a' ] TALLY
Stack effect
[ vec ] -> [ record ]
Stack
1 input(s) → 1 output(s)
Operands
data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector
Clauses
LANG.RECORDS.STRUCTURE, LANG.VALUES.VECTOR, LANG.MACHINE.LIMITS

GROUP Standard · operational

Bundle values by the key at the same position, as a Record from key to the Vector of its values: [ 'a' 'b' 'a' ] [ 1 2 3 ] GROUP is [ 'a' 'b' ] [ [ 1/1 3/1 ] [ 2/1 ] ] RECORD, keys in order of first appearance and every value kept exactly once. The core of a per-class tally, a centroid update or a stratified partition; R 'a' GET reads one group by name, with no UNIQUE or INDEX-OF to find it. Keys come first, as they do for RECORD; both operands must be Vectors of the same length.

Syntax
[ 'a' 'b' 'a' ] [ 1 2 3 ] GROUP
Stack effect
[ keys ] [ values ] -> [ record ]
Stack
2 input(s) → 1 output(s)
Operands
data, data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector, shapeMismatch
Clauses
LANG.RECORDS.STRUCTURE, LANG.VALUES.VECTOR, LANG.MACHINE.LIMITS

RECORD Semantic Kernel

Build a Record — a keyed correspondence, the seventh value domain — from a Vector of keys and a Vector of values paired position by position: [ 'x' 'y' ] [ 1 2 ] RECORD 'y' GET is 2. Keys keep the order they were given, which KEYS and VALUES read back. Two lengths that differ, or a key that appears twice, is the program being wrong, so both are ERRORs rather than a silent last-one-wins.

Syntax
[ 'x' 'y' ] [ 1 2 ] RECORD
Stack effect
[ keys ] [ values ] -> [ record ]
Stack
2 input(s) → 1 output(s)
Operands
data, data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector, shapeMismatch, duplicateKey
Clauses
LANG.RECORDS.STRUCTURE, LANG.VALUES.DISJOINT, LANG.VALUES.VECTOR

KEYS Semantic Kernel

The keys of a Record as a Vector, in the Record's own order, so that KEYS and VALUES line up position by position: [ 'x' 'y' ] [ 1 2 ] RECORD KEYS is [ 'x' 'y' ]. Key order is part of a Record's observable structure, so this Vector is one exact thing, not a set in some arbitrary order. A Vector or any other non-Record operand is an ERROR: a Record is not a Vector and nothing converts between them implicitly.

Syntax
[ 'x' 'y' ] [ 1 2 ] RECORD KEYS
Stack effect
[ record ] -> [ keys ]
Stack
1 input(s) → 1 output(s)
Operands
data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonRecord
Clauses
LANG.RECORDS.STRUCTURE, LANG.VALUES.DISJOINT

VALUES Semantic Kernel

The values of a Record as a Vector, aligned with KEYS: [ 'x' 'y' ] [ 1 2 ] RECORD VALUES is [ 1/1 2/1 ]. This is the bridge from the Record domain back to the Vector domain — from here every Vector Word applies — and RECORD is the bridge the other way, so R KEYS R VALUES RECORD rebuilds R. A non-Record operand is an ERROR.

Syntax
[ 'x' 'y' ] [ 1 2 ] RECORD VALUES
Stack effect
[ record ] -> [ values ]
Stack
1 input(s) → 1 output(s)
Operands
data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonRecord
Clauses
LANG.RECORDS.STRUCTURE, LANG.VALUES.DISJOINT

WITHOUT Semantic Kernel

A copy of a Record with one key removed: [ 'x' 'y' ] [ 1 2 ] RECORD 'x' WITHOUT KEYS is [ 'y' ]. Removing a key the Record does not hold is not an identity but the absence notFound — the same discipline GET, TAKE and PUT keep for a position outside the Vector, so a misspelled key cannot pass silently. The other keys keep their order. A non-Record first operand is an ERROR.

Syntax
[ 'x' 'y' ] [ 1 2 ] RECORD 'x' WITHOUT
Stack effect
[ record ] [ key ] -> [ record ]
Stack
2 input(s) → 1 output(s)
Operands
data, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: notFound → notFound
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonRecord
Clauses
LANG.RECORDS.STRUCTURE, LANG.VALUES.DISJOINT, LANG.FAILURE.PROJECT, LANG.COLLECTIONS.LIFT

HAS? Semantic Kernel

Whether a Record holds a key: [ 'x' ] [ 1 ] RECORD 'x' HAS? is TRUE and [ 'x' ] [ 1 ] RECORD 'y' HAS? is FALSE. It asks about presence without touching the value, so a program can tell a key that is absent from a key whose stored value is NIL — GET alone answers NIL for both. Like NIL?, it is a predicate and ends in ?. A non-Record first operand is an ERROR.

Syntax
[ 'x' ] [ 1 ] RECORD 'x' HAS?
Stack effect
[ record ] [ key ] -> [ TRUE | FALSE ]
Stack
2 input(s) → 1 output(s)
Operands
data, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonRecord
Clauses
LANG.RECORDS.STRUCTURE, LANG.VALUES.DISJOINT, LANG.COLLECTIONS.LIFT

MERGE Semantic Kernel

The union of two Records, the right one winning: [ 'x' 'y' ] [ 1 2 ] RECORD [ 'y' 'z' ] [ 9 3 ] RECORD MERGE VALUES is [ 1 9 3 ]. The left Record's keys keep their order and take the right Record's value wherever both hold the key; keys only the right holds are appended in the right's order. Layering overrides on defaults is the shape this Word is for; swap the operands for the left to win. Either operand not a Record is an ERROR.

Syntax
[ 'x' 'y' ] [ 1 2 ] RECORD [ 'y' 'z' ] [ 9 3 ] RECORD MERGE
Stack effect
[ record ] [ record ] -> [ record ]
Stack
2 input(s) → 1 output(s)
Operands
data, data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonRecord
Clauses
LANG.RECORDS.STRUCTURE, LANG.VALUES.DISJOINT

2.4.2.4 String operations (CHARS, JOIN)

Crossing over to Vectors: CHARS and JOIN

A string is not a Vector, so the collection Words do not accept strings. LENGTH, GET, REVERSE and CONCAT all raise an error on a string. Cross over to the Vector domain with CHARS first: 'abc' CHARS LENGTH is 3. Concatenation is done by JOIN, the Word that turns a Vector of strings into one string: [ 'ab' 'cd' ] JOIN is 'abcd'. To concatenate computed values rather than written ones, collect them with COLLECT first, as in 'a' 'b' 2 COLLECT JOIN.

Sample codeExpected valueNotes
'hello' CHARS [ 'h' 'e' 'l' 'l' 'o' ] CHARS crosses from the string domain to the Vector domain.
Sample codeExpected valueNotes
[ 'hel' 'lo' ] JOIN 'hello' Concatenates a Vector of strings.
Sample codeExpected valueNotes
' hi ' TRIM 'hi' Removes whitespace from both ends.
Sample codeExpected valueNotes
'a,b,c' ',' TOKENIZE [ 'a' 'b' 'c' ] Splits on a delimiter. JOIN puts the pieces back together.
Sample codeExpected valueNotes
'hello world' 'world' SEARCH 6/1 The position of the first occurrence, counted in characters as CHARS counts them. NIL (notFound) if absent.
'a-b-c' '-' '+' REPLACE 'a+b+c' Replaces every occurrence, left to right, without overlap.

CHARS splits a string into its characters, and TOKENIZE splits it on a delimiter. Both produce a Vector of strings, and JOIN assembles one back into text: 'hello' CHARS REVERSE JOIN is 'olleh'.

Converting to and from numbers: STR and NUM

Sample codeExpected valueNotes
42 STR '42' Number to string.
Sample codeExpected valueNotes
'1/3' NUM 1/3 String to exact number. Text that cannot be parsed is NIL.

Upper and lower case: UPPER and LOWER

These convert a whole string to upper or lower case using Unicode's default case mappings. Locale-dependent rules (Turkish dotless i, Greek final sigma) are not applied, so the same text gives the same answer wherever it runs. The mapping table is Unicode's and cannot be written as your own definition with CHARS and JOIN, so it is held as Words. Mappings that change length, such as ß, are applied as they are.

Sample codeExpected valueNotes
'Ajisai' UPPER 'AJISAI' To upper case.
'Ajisai' LOWER 'ajisai' To lower case.
'straße' UPPER 'STRASSE' Mappings that change length follow Unicode too.
42 UPPER error Anything other than a string is nonText.

Drawing with a chosen number of digits: FORMAT

Arithmetic never rounds anywhere, and STR declines a number that has no exact lexeme. Still, sometimes you want to see 1/3 to three decimal places. FORMAT is the one place for that. It takes a value and a digit count and returns decimal text rounded by the same rule as ROUND (an exact half goes away from 0) — the language has a single rounding rule. What comes out is text, not a number, so a rounded quantity never flows back into arithmetic. The rounding mode is fixed; making it selectable would only add Words. The digit count must be a non-negative integer (invalidInteger), and the value must be a scalar (nonNumeric). Even for an irrational, the field's order decides every digit through the last, so no digit is guessed.

Sample codeExpected valueNotes
1/3 5 FORMAT '0.33333' Rounding happens only at the display boundary.
5/2 0 FORMAT '3' An exact half goes away from 0 — the same single rounding rule as ROUND.
2 SQRT 3 FORMAT '1.414' For an irrational too, the field's total order decides the last digit.
2 SQRT 3 SQRT ADD 4 FORMAT '3.1463' Even with several irrational terms, the last digit is decided exactly.
1/3 2 FORMAT 1 ADD error The answer is text. To get a number back, go through NUM.

Converting to and from JSON: JSON-DECODE and JSON-ENCODE

Structured data from the host arrives as JSON. JSON-DECODE turns JSON text into a value: an object becomes a Record keyed by its member names in order, an array a Vector, a string a string, a number the exact rational it spells (0.1 is exactly 1/10), true/false a Boolean, and null NIL. Text that is not a single JSON value (broken, empty, trailing garbage, a member name given twice) projects a NIL with invalidEncoding. The nesting depth is decided by the text alone, so this Word cannot be written as a user definition — the language's iteration runs only over a Vector that already exists.

JSON-ENCODE is the reverse. A Record keyed by strings becomes an object, a Vector an array, and NIL null. Only a number with a finite decimal expansion (a denominator made of 2s and 5s) is written as a JSON number; any other rational is written as its Ajisai lexeme inside a string, such as "1/3", because writing 1/3 as 0.333 would be a different number. A value with no image in JSON — a Symbol, an irrational, a Record with a non-string key — projects a NIL with domainMiss.

Sample codeExpected valueNotes
'{"a": 1, "b": [true, null]}' JSON-DECODE [ 'a' 'b' ] [ 1/1 [ TRUE NIL ] ] RECORD An object is a Record; an array is a Vector.
'0.1' JSON-DECODE 10 MUL 1/1 Numbers are read as exact rationals.
[ 'a' ] [ 1/4 ] RECORD JSON-ENCODE '{"a":0.25}' A number with a finite decimal expansion is written as a JSON number.
1/3 JSON-ENCODE '"1/3"' Any other number is written as its lexeme inside a string. Nothing is rounded.
2 SQRT JSON-ENCODE NIL-REASON 'domainMiss' A value with no image in JSON is a reasoned NIL.
[ 'a' ] [ 1/4 ] RECORD 'R' BIND R R JSON-ENCODE JSON-DECODE EQ TRUE A value that has an image makes the round trip unchanged.

Contracts: Text

CHARS Semantic Kernel

A text split into its characters, each a one-character text: 'héllo' CHARS is [ 'h' 'é' 'l' 'l' 'o' ], and '' CHARS is [ ]. JOIN puts them back. A Vector of texts lifts; a non-text is nonText.

Syntax
'hi' CHARS
Stack effect
[ text ] -> [ chars ]
Stack
1 input(s) → 1 output(s)
Operands
leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonText
Clauses
LANG.VALUES.DISJOINT, LANG.FAILURE.TRICHOTOMY, LANG.COLLECTIONS.LIFT

JOIN Semantic Kernel

A Vector of texts joined end to end into one text: [ 'h' 'i' ] JOIN is 'hi', and [ ] JOIN is '', so CHARS JOIN gives a text back. Every element must be a text (nonText), and a non-Vector is nonVector.

Syntax
[ 'h' 'i' ] JOIN
Stack effect
[ texts ] -> [ text ]
Stack
1 input(s) → 1 output(s)
Operands
data (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonVector, nonText
Clauses
LANG.VALUES.DISJOINT, LANG.FAILURE.TRICHOTOMY

TRIM Standard · algorithm

A text with the whitespace at both ends removed: ' a b ' TRIM is 'a b'; whitespace inside is kept. A Vector of texts lifts; a non-text is nonText.

Syntax
' hi ' TRIM
Stack effect
[ text ] -> [ text' ]
Stack
1 input(s) → 1 output(s)
Operands
leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonText
Clauses
LANG.VALUES.DISJOINT, LANG.FAILURE.TRICHOTOMY, LANG.COLLECTIONS.LIFT

UPPER Standard · algorithm

The String with every character mapped to its upper form under Unicode's default, locale-independent case mapping: 'Ajisai' UPPER is 'AJISAI', 'straße' UPPER is 'STRASSE'. A character with no upper-case form is kept as it is, so the answer may be longer than the operand but never shorter. A non-String operand is an ERROR (nonText). The mapping table is Unicode's, which no definition over CHARS and JOIN could carry, so the Word is native.

Syntax
'Ajisai' UPPER
Stack effect
[ text ] -> [ text' ]
Stack
1 input(s) → 1 output(s)
Operands
leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonText
Clauses
LANG.VALUES.DISJOINT, LANG.FAILURE.TRICHOTOMY, LANG.COLLECTIONS.LIFT

LOWER Standard · algorithm

The String with every character mapped to its lower form under Unicode's default, locale-independent case mapping: 'Ajisai' LOWER is 'ajisai', 'ΣΑΣ' LOWER is 'σασ'. The final-sigma rule and every other language-specific rule are not applied: the same text lowers the same way wherever it is run. A non-String operand is an ERROR (nonText). The mapping table is Unicode's, which no definition over CHARS and JOIN could carry, so the Word is native.

Syntax
'Ajisai' LOWER
Stack effect
[ text ] -> [ text' ]
Stack
1 input(s) → 1 output(s)
Operands
leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonText
Clauses
LANG.VALUES.DISJOINT, LANG.FAILURE.TRICHOTOMY, LANG.COLLECTIONS.LIFT

TOKENIZE Standard · algorithm

Split a string into a vector of substrings at every occurrence of a separator: 'a,b,c' ',' TOKENIZE is [ 'a' 'b' 'c' ]. The empty separator splits between every character, as CHARS does. JOIN is the inverse for the empty separator.

Syntax
'a,b,c' ',' TOKENIZE
Stack effect
[ text ] [ sep ] -> [ parts ]
Stack
2 input(s) → 1 output(s)
Operands
leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonText, shapeMismatch
Clauses
LANG.VALUES.DISJOINT, LANG.FAILURE.TRICHOTOMY, LANG.COLLECTIONS.LIFT

SEARCH Standard · algorithm

The position, in characters, at which a text first occurs in another: 'hello world' 'world' SEARCH is 6, counted the way CHARS counts, and 'hello' 'z' SEARCH is NIL(notFound). An empty needle is found at 0. This is INDEX-OF for text: spelled over CHARS it compares a window at every position, and the Word does it in one pass.

Syntax
'hello world' 'world' SEARCH
Stack effect
[ text ] [ needle ] -> [ index ]
Stack
2 input(s) → 1 output(s)
Operands
leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: notFound → notFound
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonText, shapeMismatch
Clauses
LANG.VALUES.DISJOINT, LANG.FAILURE.TRICHOTOMY, LANG.COLLECTIONS.LIFT, LANG.FAILURE.PROJECT

REPLACE Standard · algorithm

Every occurrence of one text replaced by another: 'a-b-c' '-' '+' REPLACE is 'a+b+c'. Occurrences are found left to right and do not overlap, and an empty from matches nothing, so the text comes back unchanged rather than growing without bound. Spelled over CHARS and JOIN this is a scan with a window at every position; the Word is the one pass.

Syntax
'a-b-c' '-' '+' REPLACE
Stack effect
[ text ] [ from ] [ to ] -> [ text' ]
Stack
3 input(s) → 1 output(s)
Operands
leaf, leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonText, shapeMismatch
Clauses
LANG.VALUES.DISJOINT, LANG.FAILURE.TRICHOTOMY, LANG.COLLECTIONS.LIFT

NUM Semantic Kernel

Parse text as a number, by the same grammar a source literal is read with: '3/4' NUM is 3/4, '0.25' NUM is 1/4. Text that spells no number — 'abc', '.5', '1_000', '1/0' — projects NIL(invalidEncoding), and text spelling a number of more digits than the numeric-literal ceiling allows a source literal, its exponent counted ('1e99999999'), projects NIL(spaceExhausted). A non-String operand is an ERROR (nonText); a Vector of Strings lifts.

Syntax
'42' NUM
Stack effect
[ text ] -> [ n ]
Stack
1 input(s) → 1 output(s)
Operands
leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: parseFailure,materializationBudgetExceeded → invalidEncoding, spaceExhausted
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonText
Clauses
LANG.VALUES.DISJOINT, LANG.FAILURE.TRICHOTOMY, LANG.COLLECTIONS.LIFT, LANG.FAILURE.PROJECT

STR Semantic Kernel

Write a Scalar, String or Boolean as the text that reads back as it: 42 STR is '42', 1/3 STR is '1/3', TRUE STR is 'TRUE'. The operand is a leaf, so a Vector or Record lifts — [ 1 2 ] STR is [ '1' '2' ] — and STR is NUM's inverse element by element: 1/3 STR NUM is 1/3, as x STR NUM gives back every number with a lexeme. Text is the sealed numeric grammar's alphabet, so a number with no lexeme in it — an exact irrational such as 2 SQRT — has no faithful text and projects NIL(domainMiss), the reason JSON-ENCODE projects for a value with no JSON image, rather than answering with a rational look-alike. FORMAT renders a stated approximation when one is wanted.

Syntax
42 STR
Stack effect
[ x ] -> [ text ]
Stack
1 input(s) → 1 output(s)
Operands
leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: noExactLexemeForValue → domainMiss
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
Clauses
LANG.VALUES.DISJOINT, LANG.FAILURE.TRICHOTOMY, LANG.COLLECTIONS.LIFT, LANG.FAILURE.PROJECT

FORMAT Standard · operational

Render an exact scalar as decimal text with a stated number of digits after the point, rounding a tie away from zero exactly as ROUND does: 1/3 5 FORMAT is '0.33333', 5/2 0 FORMAT is '3', 2 SQRT 3 FORMAT is '1.414'. This is the one place a value is rounded, and it is text that leaves it, never a number: arithmetic performs no rounding and STR refuses a number with no exact lexeme, so a program that wants a decimal approximation names its precision here, at the display boundary. The digit count is a non-negative integer (invalidInteger otherwise) and the value a scalar (nonNumeric otherwise). Every digit is decided exactly, an irrational's included, since the field's order never ties.

Syntax
1/3 5 FORMAT
Stack effect
[ x ] [ digits ] -> [ text ]
Stack
2 input(s) → 1 output(s)
Operands
leaf, leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonNumeric, invalidInteger, shapeMismatch
Clauses
LANG.VALUES.EXACT, LANG.VALUES.DISJOINT, LANG.FAILURE.TRICHOTOMY, LANG.COLLECTIONS.LIFT

JSON-DECODE Standard · algorithm

Read JSON text into a value: '[1, 2]' JSON-DECODE is [ 1 2 ]. An object becomes a Record keyed by its member names in order, an array a Vector, a string a String, a number the exact rational it spells ('0.1' JSON-DECODE is exactly 1/10), true/false Booleans and null a NIL. Text that is not one JSON value — malformed, empty, trailing content, or an object naming one member twice — projects invalidEncoding, the reason NUM projects for text that spells no number. Nesting is bounded by the text rather than by any Word, so this Word cannot be written in the language, whose repetition is over a Vector that already exists; a value nested past the nesting ceiling, or a number of more digits than the numeric-literal ceiling, projects spaceExhausted, the outcome of every materialization past a ceiling (LANG.MACHINE.LIMITS). A non-String operand is an ERROR (nonText).

Syntax
'{"a": 1, "b": [true, null]}' JSON-DECODE
Stack effect
[ text ] -> [ value ]
Stack
1 input(s) → 1 output(s)
Operands
leaf (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthroughThenProject; projection: textIsNotJson,materializationBudgetExceeded → invalidEncoding, spaceExhausted
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonText
Clauses
LANG.VALUES.DISJOINT, LANG.RECORDS.STRUCTURE, LANG.VALUES.EXACT, LANG.COLLECTIONS.LIFT, LANG.FAILURE.PROJECT

JSON-ENCODE Standard · algorithm

Write a value as JSON text, the inverse of JSON-DECODE: a Record with String keys becomes an object in key order, a Vector an array, a String a string, a Boolean true/false, a NIL null. A rational with a finite decimal spelling (a denominator of the form 2^a·5^b) is written as a JSON number exactly — 1/4 is 0.25 — and every other rational is written as its Ajisai lexeme inside a string, 1/3 as "1/3", so no digit is ever rounded away: the encoder is not a place a value silently loses precision. A value with no JSON image — a Symbol, an irrational, a Record with a non-String key — projects domainMiss. Decoding what this Word writes gives back the value it was given, and a rational written as a lexeme comes back as that String, from which NUM recovers the number.

Syntax
[ 'a' ] [ 1 ] RECORD JSON-ENCODE
Stack effect
[ value ] -> [ text ]
Stack
1 input(s) → 1 output(s)
Operands
element (LANG.FAILURE.PASSTHROUGH)
NIL policy
consumeNil; projection: valueHasNoJsonImage → domainMiss
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
Clauses
LANG.VALUES.DISJOINT, LANG.RECORDS.STRUCTURE, LANG.VALUES.EXACT, LANG.FAILURE.PROJECT

2.4.2.5 Choice and iteration (SELECT, MAP)

Conditional choice (SELECT)

SELECT takes three arguments: the value when true, the value when false, and the Boolean that decides which. Line them up as [ whenTrue ] [ whenFalse ] [ mask ] SELECT. The Boolean comes last because, as with other Words, the operand that decides what to do goes last.

SELECT evaluates nothing. The two candidates are values the program has already built. That is, each candidate has been computed once, in the order written, before it reaches SELECT. It is not that the side not chosen "does not run"; by the time the choice is made, both are already values. This is the fundamental difference from Words that take a block and run its contents (MAP, EXEC).

So an effect such as PRINT cannot go inside a candidate — if you put it there, both would print. Choose the answer first, then apply the effect once, placing it after SELECT, as in [ 'big' ] [ 'small' ] [ 5 ] [ 3 ] GT SELECT PRINT.

Sample codeExpected valueNotes
[ 'negative' ] [ 'positive' ] [ -5 ] [ 0 ] LT SELECT [ 'negative' ] Push the two candidates, then the Boolean that chooses.

Choosing element by element

If the Boolean is a Vector, the choice happens element by element too. Comparison Words also lift element-wise, so [ 1 2 3 ] [ 2 ] GT answers the Vector of Booleans [ FALSE FALSE TRUE ]. Hand that straight to SELECT and a branch over a whole Vector is a single expression. A length-1 candidate is reused to match the other's length.

Sample codeExpected valueNotes
[ 0 ] [ -3 5 -1 ] [ -3 5 -1 ] [ 0 ] LT SELECT [ 0/1 5/1 0/1 ] Replaces only the negative elements with 0. The length-1 [ 0 ] reaches all three elements.

To combine several conditions, join the Vectors of Booleans with AND and NOT, which also lift element-wise: [ 3 6 9 ] [ 5 ] GT [ 3 6 9 ] [ 8 ] LT AND is [ FALSE TRUE FALSE ].

Splitting three or more ways

SELECT always chooses one of two. To split three or more ways, nest SELECT. There is no "try from the top and the first match wins" reading; which case takes precedence is visible directly as the shape of the nesting.

Sample codeExpected valueNotes
[ 'zero' ] [ 'negative' ] [ 'positive' ] [ 0 ] [ 0 ] LT SELECT [ 0 ] [ 0 ] EQ SELECT [ 'zero' ] The inner SELECT becomes the outer one's false candidate.

When neither is chosen

If an absence (NIL) arrives in the Boolean position, it is UNKNOWN (three-valued logic). UNKNOWN chooses neither candidate and answers the absence it read, reason and all. So you can ask afterwards, with NIL-REASON, "why could this not branch?".

There is no state of "no clause matched". There are always two candidates, and the Boolean is TRUE, FALSE or UNKNOWN, so SELECT always has an answer. This is why forgetting an else clause can never crash at run time.

Sample codeExpected valueNotes
[ 'y' ] [ 'n' ] NIL SELECT [ NIL ] UNKNOWN chooses neither and answers the absence.

A bare number cannot go in the Boolean position (nonTruthValue), because 1 and 0 are not Booleans (the domains are disjoint). To decide on a number, write a comparison, such as [ 1 ] [ 0 ] EQ NOT.

Iteration (MAP, FILTER, FOLD, SCAN)

A code block [ ] is a first-class value. Higher-order Words apply a block, or the quoted name of a defined Word, to each element of a Vector.

These four are all the iteration there is. There is no way to repeat by having a Word call itself, because DEF rejects cyclic definitions. So the number of iterations is always written as the length of a finite Vector at hand, visible before you read the block.

What the block leaves becomes the mapped element as it is. A one-element Vector is a one-element Vector, like any other value. So [ 1 2 ] [ 1 COLLECT ] MAP is [ [ 1/1 ] [ 2/1 ] ], and [ [ 1 ] [ 2 3 ] ] [ REVERSE ] MAP is [ [ 1/1 ] [ 3/1 2/1 ] ]. Nothing unwraps a result just because it happens to be short. A cluster with a single point, or a row with a single output, keeps its shape all the way through.

Sample codeExpected valueNotes
[ 1 2 3 ] [ 2 MUL ] MAP [ 2/1 4/1 6/1 ] Applies the block to each element.
Sample codeExpected valueNotes
[ 2 MUL ] 'DBL' DEF [ 1 2 3 ] [ DBL ] MAP [ 2/1 4/1 6/1 ] A defined Word can be passed by its quoted name.
Sample codeExpected valueNotes
[ 1 2 3 4 ] [ 2 DIV 'M' BIND M FLOOR M EQ ] FILTER [ 2/1 4/1 ] Keeps the elements for which the predicate is true (here, the even ones).
Sample codeExpected valueNotes
[ 1 2 3 4 ] [ 0 ] [ ADD ] FOLD [ 10/1 ] Folds with an initial accumulator: 0 + 1 + 2 + 3 + 4. The shape of the initial value decides the shape of the result: starting from the Vector [ 0 ] here, the answer is the Vector [ 10/1 ] too. Start from a bare 0 and the answer is a bare 10/1.

A fold that keeps its progress (SCAN)

SCAN walks the same way as FOLD and answers every accumulator along the way, not only the last one. What the block sees and leaves is identical to FOLD; only what is returned differs.

The answer has the same length as the input. The initial accumulator is a "seed", not a lane, so it is not part of the answer. Because the lengths match, you can ZIP it with the original Vector, or compare it to make a mask for SELECT.

Sample codeExpected valueNotes
[ 1 2 3 4 ] 0 [ ADD ] SCAN [ 1/1 3/1 6/1 10/1 ] A running sum. The same expression written with FOLD returns only the final 10/1.

This is the only way in this language to carry one result forward to the next. A Word cannot call itself and there are no loops, so the accumulator is the only means of carrying state along the way. FOLD shows that state once, at the end; SCAN shows it along the way too. Running maxima, balance histories, a state machine stepping along a sequence, recurrences — every computation that takes effect step by step has this form.

Sample codeExpected valueNotes
[ 3 1 4 1 5 ] 0 [ MAX ] SCAN [ 3/1 3/1 4/1 4/1 5/1 ] A running maximum: the largest value seen up to each point.

An empty Vector answers an empty Vector (zero lanes in, zero lanes out; the seed is not returned). A Vector that is absent (NIL) answers that absence, reason and all. Here it differs from FOLD, which answers the seed in both cases — with nothing to fold, the answer is the seed. These are two sides of the same rule: the shape of the answer follows the shape of the question.

Sample codeExpected valueNotes
[ 1 3 5 8 ] [ 2 DIV 'M' BIND M FLOOR M EQ ] MAP
FALSE [ 'B' BIND NOT B NOT AND NOT ] FOLD
TRUE "Does at least one element satisfy the predicate?" MAP to a Vector of Booleans, then fold with logical OR starting from FALSE. To ask only whether a specific value is present, [ 1 2 3 ] 2 MEMBER? (TRUE) is simpler.
Sample codeExpected valueNotes
[ 2 4 6 8 ] [ 2 DIV 'M' BIND M FLOOR M EQ ] MAP
TRUE [ AND ] FOLD
TRUE "Does every element satisfy the predicate?" Fold with AND starting from TRUE.

EXEC runs a code block taken from the stack.

Writing a Word that takes two arguments

Several values travel inside a Vector, not on the stack. A Word that needs two arguments takes one two-element Vector. The caller writes it directly, as in [ 3 4 ], or builds it from computed values with COLLECT.

Sample codeExpected valueNotes
[ 0 [ ADD ] FOLD ] 'PSUM' DEF [ 3 4 ] PSUM 7/1 The argument is the Vector itself. Fold it rather than unpack it.
Sample codeExpected valueNotes
[ [ 1 -1 ] MUL 0 [ ADD ] FOLD ] 'PDIFF' DEF 2 5 MUL 3 2 COLLECT PDIFF 7/1 An asymmetric Word weights the positions by broadcasting, then folds. 2 COLLECT gathers two computed values.

When you need one position rather than a fold, read it with GET: 0 GET is the first element and -1 GET the last. So [ SORT -1 GET ] 'PMAX' DEF answers the larger of two values. REVERSE exchanges the two elements of a pair, and TAKE combined with CONCAT rebuilds one Vector from parts of another.

The tool for duplication is BIND. Name a value and it is pushed every time the name is written, so a value of any kind can be used as many times as needed.

Contracts: Iteration

MAP Semantic Kernel

A block applied to each element of a Vector, in order, answering a Vector of the results: [ 1 2 3 ] [ 2 MUL ] MAP is [ 2 4 6 ]. The block's result is whatever it leaves on top, a Vector included (LANG.COLLECTIONS.HIGHER): [ 1 2 ] [ 1 COLLECT ] MAP is [ [ 1 ] [ 2 ] ]. An absent Vector answers that absence. A non-Vector is nonVector, a non-block notExecutable, and a block that leaves nothing blockContractViolation.

Syntax
[ 1 2 3 ] [ 2 MUL ] MAP
Stack effect
[ vec ] [ body ] -> [ mapped ]
Stack
2 input(s) → 1 output(s)
Operands
data, control (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
partial
Purity / determinism
conditional / stateRelative
Effects
none
ERROR conditions
nonVector, notExecutable, blockContractViolation
Clauses
LANG.COLLECTIONS.HIGHER

FILTER Standard · operational

The elements of a Vector for which a predicate block answers TRUE, in order: [ 1 2 3 4 ] [ 2 GT ] FILTER is [ 3 4 ]. The block's answer is read as a truth value, and an UNKNOWN drops the element like FALSE does. A non-Vector is nonVector, a non-block notExecutable, a block that leaves nothing blockContractViolation, and an answer that is not a truth value nonTruthValue.

Syntax
[ 1 2 3 ] [ 2 EQ ] FILTER
Stack effect
[ vec ] [ pred ] -> [ kept ]
Stack
2 input(s) → 1 output(s)
Operands
data, control (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
partial
Purity / determinism
conditional / stateRelative
Effects
none
ERROR conditions
nonVector, notExecutable, blockContractViolation, nonTruthValue
Clauses
LANG.COLLECTIONS.HIGHER

FOLD Semantic Kernel

A Vector reduced to one value by a block that combines the accumulator with each element in turn, starting from a seed: [ 1 2 3 ] 0 [ ADD ] FOLD is 6, and [ 1 2 3 ] 10 [ SUB ] FOLD is 4. An empty Vector answers the seed: [ ] 0 [ ADD ] FOLD is 0. SCAN answers every accumulator instead of the last. A non-Vector is nonVector, a non-block notExecutable, and a block that leaves nothing blockContractViolation.

Syntax
[ 1 2 3 ] 0 [ ADD ] FOLD
Stack effect
[ vec ] [ init ] [ combine ] -> [ result ]
Stack
3 input(s) → 1 output(s)
Operands
data, element, control (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
partial
Purity / determinism
conditional / stateRelative
Effects
none
ERROR conditions
nonVector, notExecutable, blockContractViolation
Clauses
LANG.COLLECTIONS.HIGHER

SCAN Standard · operational

Reduce a vector step by step, answering the accumulator after each element rather than only the last one: [ 1 2 3 4 ] 0 [ ADD ] SCAN is [ 1/1 3/1 6/1 10/1 ]. The answer has one lane per input lane — the initial accumulator is the seed, not a lane, so it is not among them — which is what lets a scan pair with the Vector it came from. The block sees the accumulator and the current element, exactly as FOLD's does, and what it leaves is both the next accumulator and that lane's answer. An empty Vector answers an empty Vector, and an absent Vector answers that same absence.

Syntax
[ 1 2 3 4 ] 0 [ ADD ] SCAN
Stack effect
[ vec ] [ init ] [ combine ] -> [ steps ]
Stack
3 input(s) → 1 output(s)
Operands
data, element, control (LANG.FAILURE.PASSTHROUGH)
NIL policy
passthrough; projection: none
Partiality
partial
Purity / determinism
conditional / stateRelative
Effects
none
ERROR conditions
nonVector, notExecutable, blockContractViolation
Clauses
LANG.COLLECTIONS.HIGHER

Contracts: Control

EXEC Semantic Kernel

A block run where it stands, its results left on the stack: [ 1 2 ADD ] EXEC is 3. It reads and writes the stack like any other code, so 2 [ 3 MUL ] EXEC is 6. A non-block is notExecutable.

Syntax
[ 1 2 ADD ] EXEC
Stack effect
[ code ] -> [ result... ]
Stack
1 input(s) → variable output(s)
Operands
control (LANG.FAILURE.PASSTHROUGH)
NIL policy
rejectNil; projection: none
Partiality
partial
Purity / determinism
conditional / stateRelative
Effects
none
ERROR conditions
notExecutable
Clauses
LANG.MACHINE.TRANSFORMERS, LANG.SOURCE.CODE

FAIL Semantic Kernel

Raise an ERROR the program states: 'width must be positive' FAIL halts evaluation with category declaredFailure and that text as its message. This is the other half of what ABSENT gives a user Word — the trichotomy's third outcome, for a call that is wrong rather than data that did not work out. Like every ERROR it propagates and cannot be caught; a caller who wants a value to recover from asks for ABSENT instead. A non-text operand is nonText.

Syntax
'width must be positive' FAIL
Stack effect
[ message ] -> [ ]
Stack
1 input(s) → 0 output(s)
Operands
control (LANG.FAILURE.PASSTHROUGH)
NIL policy
rejectNil; projection: none
Partiality
partial
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonText, declaredFailure
Clauses
LANG.FAILURE.TRICHOTOMY, LANG.FAILURE.ERROR

2.4.2.6 Handling absence (NIL?, ABSENT)

Inspecting NIL: NIL? and NIL-REASON

These two Words inspect NIL. NIL? asks whether the top of the stack is NIL; NIL-REASON reads the reason it carries. Like every other Word, both consume the value they inspect and push their answer in its place. If you still need the value afterwards, name it with BIND first.

Sample codeExpected valueNotes
1 0 DIV NIL? TRUE NIL? consumes the top value and pushes its answer in its place.
Sample codeExpected valueNotes
1 0 DIV NIL-REASON 'divisionByZero' The reason is a protocol string, so a program can branch on why a value is missing.

Declaring an outcome yourself: ABSENT and FAIL

Absence and errors do not belong only to the built-in Words. A program can itself declare "there is no value here" or "this is misuse". ABSENT takes one string and pushes a NIL whose reason is that string. The reason identifier is userDeclared, but what NIL-REASON answers is the string you passed. The string is part of the value, so two ABSENTs made from different strings are different values.

FAIL also takes one string and raises an ERROR with that string as its message (classified declaredFailure). Being an ERROR, it stops evaluation there, and no Word can catch it. FAIL is not a "stronger ABSENT"; it is the Word for drawing the line between absence and misuse from the program's side. Use ABSENT when the caller should be able to recover, and FAIL when it should stop as the caller's mistake.

Sample codeExpected valueNotes
'rate not quoted' ABSENT NIL-REASON 'rate not quoted' The declared string becomes the reason as it is.
'rate not quoted' ABSENT 'S' BIND 0 S S NIL? SELECT 0/1 It is an ordinary NIL, so the recovery phrase below works on it unchanged.
1 'width must be positive' FAIL 2 error The message is width must be positive. Evaluation stops, and the following 2 does not run. The operand 1 and the string stay on the stack.

Supplying a fallback value: BIND, NIL?, SELECT

There is no special syntax for recovering from absence. Name the value being tested with BIND, then line up SELECT and NIL?.

Like every other Word, NIL? consumes the value it inspects and pushes a Boolean saying whether it was absent ([ x ] -> [ bool ]). So name the value once and read it twice. Writing value 'S' BIND fallback S S NIL? SELECT pushes things in exactly the order SELECT asks for (the candidate when true, the candidate when false, the Boolean), giving "the value if there is one, otherwise the fallback".

The fallback is an ordinary operand, so like any other operand it has been computed before SELECT runs. In other words the fallback is computed even when the value is present (see the SELECT section). Do not put a heavy computation or an effect such as PRINT in the fallback position.

NIL? asks about the whole value. Even if one lane of a Vector is NIL, the Vector itself is not absent, so the answer is FALSE. [ 1 NIL ] 'S' BIND [ 0 0 ] S S NIL? SELECT is [ 1/1 NIL ]: the lane's absence is not recovered. To recover a lane, recover inside the Vector rather than outside it.

Sample codeExpected valueNotes
1 0 DIV 'S' BIND 99 S S NIL? SELECT 99/1 The value was absent, so the fallback is chosen.
Sample codeExpected valueNotes
7 2 DIV 'S' BIND 99 S S NIL? SELECT 7/2 When the result is a real value, the fallback is not chosen.

Contracts: Absence (NIL)

NIL Semantic Kernel

The absence written into the program: NIL NIL? is TRUE, and its reason is literal: NIL NIL-REASON is 'literal' (LANG.VALUES.NIL). ABSENT writes one with a reason of the program's own.

Syntax
NIL
Stack effect
-> [ NIL ]
Stack
0 input(s) → 1 output(s)
NIL policy
preserveReason; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
Clauses
LANG.VALUES.NIL, LANG.FAILURE.RECOVERY

NIL? Semantic Kernel

Whether a value is absent: 1 0 DIV NIL? is TRUE and 5 NIL? is FALSE. It asks about the whole value, so a Vector holding a NIL is present: [ 1 NIL ] NIL? is FALSE. With SELECT it chooses a fallback (LANG.FAILURE.RECOVERY).

Syntax
1 0 DIV NIL?
Stack effect
[ x ] -> [ TRUE | FALSE ]
Stack
1 input(s) → 1 output(s)
Operands
element (LANG.FAILURE.PASSTHROUGH)
NIL policy
consumeNil; projection: none
Partiality
total
Purity / determinism
pure / deterministic
Effects
none
Clauses
LANG.VALUES.NIL, LANG.FAILURE.RECOVERY

NIL-REASON Semantic Kernel

The reason an absence carries, as text: 1 0 DIV NIL-REASON is 'divisionByZero', and a NIL that ABSENT made answers the text it was given. The reason is the whole observable content of a NIL (LANG.VALUES.NIL), so this is how a program reads it. A value that is not a NIL is a well-formed operand outside the question's domain and projects NIL(domainMiss), as a negative radicand does for SQRT.

Syntax
1 0 DIV NIL-REASON
Stack effect
[ x ] -> [ text ]
Stack
1 input(s) → 1 output(s)
Operands
element (LANG.FAILURE.PASSTHROUGH)
NIL policy
consumeNil; projection: valueIsNotNil → domainMiss
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
Clauses
LANG.VALUES.NIL, LANG.FAILURE.RECOVERY, LANG.FAILURE.PROJECT

ABSENT Semantic Kernel

A NIL whose reason the program states: 'rate not quoted' ABSENT NIL? is TRUE. 'rate not quoted' ABSENT NIL-REASON answers 'rate not quoted'. Its registered reason is userDeclared, and the text is the reason NIL-REASON answers, so a user Word can say why it has no answer exactly as a Core Word's contract does — and a caller recovers it the same way, subject 'S' BIND fallback S S NIL? SELECT. The text is part of the value (LANG.VALUES.NIL): two absences with different texts are two values. A non-text operand is the program being wrong.

Syntax
'rate not quoted' ABSENT
Stack effect
[ reason ] -> [ NIL ]
Stack
1 input(s) → 1 output(s)
Operands
control (LANG.FAILURE.PASSTHROUGH)
NIL policy
createsNil; projection: always → userDeclared
Partiality
projecting
Purity / determinism
pure / deterministic
Effects
none
ERROR conditions
nonText
Clauses
LANG.VALUES.NIL, LANG.FAILURE.TRICHOTOMY, LANG.FAILURE.RECOVERY, LANG.FAILURE.PROJECT

2.4.2.7 Output (PRINT)

PRINT writes the top value of the stack to the output area. Like every other Word, it consumes its operand. To keep the value on the stack as well, name it with BIND and write the name twice. The Playground always shows the final stack, so PRINT is for the output channel, not for seeing results.

Quotes are a display convenience

To tell them apart from numeric Vectors, strings are shown on the stack in single quotes, as in 'TEST'. The quotes belong to the Stack display, not to the value. PRINT writes the raw text, so 'TEST' is output as TEST. Quote characters that are part of the text are kept, though, so a string whose content is T'ES'T is output as T'ES'T. Numbers and Booleans are output exactly as they appear on the stack.

Sample codeExpected valueNotes
42 'N' BIND N N PRINT 42/1 The value goes to the output area, and the other copy pushed by name stays on the stack.
Sample codeExpected valueNotes
42 PRINT — The default mode consumes the operand: the stack becomes empty and the value appears only in the output area.
Sample codeExpected outputNotes
'TEST' PRINT TEST The stack shows the string as 'TEST'. Output drops the display quotes and writes the raw text.

Contracts: Output

PRINT Semantic Kernel

Write a value to the output, consuming it: 42 PRINT writes 42/1 and leaves nothing. A value other than a text is written as the stack shows it. A text is written as its raw characters, without the quotes the stack shows ('TEST' prints as TEST); a text nested in a Vector keeps its quotes. Output is the one effect that leaves the machine (LANG.EFFECTS.OUTPUT).

Syntax
42 PRINT
Stack effect
[ x ] -> [ ]
Stack
1 input(s) → 0 output(s)
Operands
element (LANG.FAILURE.PASSTHROUGH)
NIL policy
consumeNil; projection: none
Partiality
total
Purity / determinism
effectful / hostRelative
Effects
consoleWrite
Clauses
LANG.EFFECTS.OUTPUT, LANG.MACHINE.ORDER

Appendix A Common patterns

Ajisai's vocabulary is 78 Words, deliberately kept small. A Word that a user definition can express at the same cost does not belong in Core. So operations such as argmin — the position of the smallest value — or a relaxation loop are written as combinations of short phrases. The common ones are collected below. Each is verified, and if you use one more than once it makes a fine body for your own DEF.

Sample codeExpected valueNotes
[ 1 2 3 4 ] 1 [ MUL ] FOLD 24/1 Product. The sum is 0 [ ADD ] FOLD, and the same FOLD shape with MIN or MAX gives the extremes.
[ 1 2 3 ] [ 4 5 6 ] MUL 0 [ ADD ] FOLD 32/1 Dot product: multiply element-wise, then sum.
[ [ 1 2 ] [ 3 4 ] ] 'A' BIND [ 5 6 ] 'V' BIND
A [ V MUL 0 [ ADD ] FOLD ] MAP
[ 17/1 39/1 ] Matrix–vector product: one dot product per row.
[ 4 1 7 1 ] ORDER 0 GET 1/1 argmin: the first position in ascending order. Use -1 GET instead for argmax.
[ 1 2 3 4 5 ] 'XS' BIND
XS 0 [ ADD ] FOLD XS LENGTH DIV 'M' BIND
XS M SUB 'D' BIND D D MUL 0 [ ADD ] FOLD XS LENGTH DIV
2/1 Variance: center, square, average. The standard deviation is one more SQRT.

Ordering by key, ties included

Sorting the values and then looking up their original positions is the obvious way to get an ordering, but it gives the wrong answer exactly when it matters. With two equal distances, SORT followed by INDEX-OF finds the same position twice, so "the three nearest" can return two neighbours, or four. ORDER answers the question SORT throws away — which position comes in which order — and answers it stably, so equal keys keep the order they were written in.

Sample codeExpected valueNotes
[ 18 13 1 1 13 2 ] ORDER 3 TAKE [ 2/1 3/1 5/1 ] The three nearest of six distances, two of them tied at 1. Drop 3 TAKE for the whole ordering, [ 2/1 3/1 5/1 1/1 4/1 0/1 ]. To carry a payload along, bind the permutation and gather with it: XS ORDER 'P' BIND YS P GET.

Appendix B The identity model

In Ajisai you write ordinary comparison Words such as EQ, LT and GT, but behind the scenes the language keeps several separate "is it the same?" questions.

LayerWhat it means to youWhy it matters
Value equalityAre these the same mathematical value?This is what the comparison Words use.
Structural identityAre they stored the same way internally?Not observable to you. An implementation is free to optimize representations.
Display equalityDo two rendered strings look the same?A display helps a human, but it does not prove equality.
Conformance equalityDo implementations produce the same observations?How a port shows it is the same Ajisai language.

The comparison Words work only at the top layer. They are exact and total across every Ajisai value, and they answer the mathematical question. The layers below take no part in the value model, so representation and display never affect a comparison's result.