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 integer3is rendered as3/1. - Booleans: shown as
TRUEorFALSE. - 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
1is treated as an integer:3/1is rendered as3.
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.
- Whitespace (space, tab, newline and the other Unicode whitespace characters): skip one character. Nothing is emitted.
#: a comment. Discard everything up to the end of the line.': a string literal. Read up to a closing quote'followed by whitespace or the end of input.- 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.
- Exactly
[or]: a delimiter (the start or end of a Vector). - Otherwise containing
[or]: a source error (bracketMustStandAlone). - The shape of a fraction with a zero denominator (
1/0): a source error (zeroDenominator). - The whole word matches the number grammar: a number literal.
- 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, soADD# noteis notADDwith a comment but an undefined Word namedADD#.
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
| Source | Tokens | Why |
|---|---|---|
3 4 ADD | 3 tokens | Whitespace separates each token. |
3 4ADD | 2 tokens | 4ADD is taken as one name: an undefined-Word error, not an addition. |
[ 1 2 ] LENGTH | 5 tokens | Whitespace 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.
| Kind | Examples | Carries |
|---|---|---|
| Number literal | 3 -1/2 0.25 1e5 | The lexeme itself, read as an exact number |
| String literal | 'hello' | The text between the quotes |
| Name | ADD NIL? DOUBLE | The 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.
| Condition | Example | Meaning |
|---|---|---|
unclosedLiteral | 'foo | A string literal reached the end of input without a closing quote followed by whitespace. |
zeroDenominator | 1/0 | A fraction literal's denominator is zero. |
bracketMustStandAlone | [1 | A 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, exceptTRUE,FALSEandNIL. - 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.
- Numbers: exact rationals and algebraic irrationals (2.1 Numbers)
- Strings (2.2 Strings)
- Vectors: sequences of values, also read as code (2.3 Vectors)
- Symbols: names held as data (2.4.1 Bindings)
- Booleans:
TRUEandFALSE(2.4.2.1 Logic, comparison, arithmetic) - Records: keys paired with values (2.4.2.3 Keyed data)
NIL: absence with a reason (1.2.1 Outcomes)
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 code | Expected value | Notes |
|---|---|---|
3 4 ADD |
7/1 |
Both operands are consumed and their sum is pushed. |
| Sample code | Expected value | Notes |
|---|---|---|
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 code | Expected value | Notes |
|---|---|---|
1 0 DIV |
NIL |
Division by zero is not a trap; it becomes a reasoned NIL. |
| Sample code | Expected value | Notes |
|---|---|---|
NIL 1 ADD |
NIL |
NIL passes straight through arithmetic, and its reason stays traceable. |
| Sample code | Expected value | Notes |
|---|---|---|
[ 10 20 30 ] 5 GET |
NIL |
An out-of-range index into a valid Vector is NIL (reason: indexOutOfBounds). |
| Sample code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
'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 code | Expected value | Notes |
|---|---|---|
[ 2 SUB 1/10 MUL ] 'DELTA' DEF |
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 code | Expected value | Notes |
|---|---|---|
[ 2 SUB 1/10 MUL ] 'DELTA' DEF |
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 code | Expected value | Notes |
|---|---|---|
[ 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 name | What was exceeded | Typical fix |
|---|---|---|
executionLimitExceeded | The total number of work steps the whole program may spend | Rewrite with bulk operations (SORT, GET); review the termination condition |
resourceLimitExceeded | The 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 |
recursionLimitExceeded | The 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 code | Expected value | Notes |
|---|---|---|
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.
| Element | Examples | Evaluated, it |
|---|---|---|
| 2.1 Number | 3 -1/2 0.25 1e5 | pushes the number |
| 2.2 String | 'hello' | pushes the string |
| 2.3 Vector | [ 1 2 3 ] | pushes the Vector, without evaluating its contents |
| 2.4 Name | ADD N DOUBLE | pushes 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.
| Form | Written as | Examples |
|---|---|---|
| Integer | a run of digits, optionally preceded by - or + | 5 -5 +5 007 |
| Fraction | an integer, /, a run of digits | 1/2 -1/2 |
| Decimal | an integer, ., a run of digits | 0.5 5.0 |
| With exponent | an integer or decimal, then e or E, an optional sign, a run of digits | 1e5 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 number | A 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+3 | 1e 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 code | Expected value | Notes |
|---|---|---|
0.5 0.25 ADD |
3/4 |
Decimal literals are computed as exact rationals. |
| Sample code | Expected value | Notes |
|---|---|---|
1 3 DIV |
1/3 |
Division returns an exact rational. |
| Sample code | Expected value | Notes |
|---|---|---|
2 3 DIV 1 3 DIV ADD |
1/1 |
\(\frac{2}{3} + \frac{1}{3}\) is exactly \(1\). |
| Sample code | Expected value | Notes |
|---|---|---|
1000000000000 1000000000000 MUL |
1000000000000000000000000/1 |
Arbitrary-precision integers stay exact, however many digits they grow. |
| Sample code | Expected value | Notes |
|---|---|---|
9 SQRT |
3/1 |
The square root of a perfect square simplifies to an exact rational. |
| Sample code | Expected value | Notes |
|---|---|---|
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 code | Expected value | Notes |
|---|---|---|
'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 code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ [ 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 code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 1 2 ADD ] [ 1 2 ADD ] EQ |
TRUE |
How it was built is not part of the value: EQ cannot tell them apart. |
| Sample code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 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 Word | What the block sees | What the block leaves |
|---|---|---|
A Word's body (DEF) | The whole stack | Everything it pushed, any number of values |
EXEC | The whole stack | Everything it pushed, any number of values |
MAP FILTER | The current element only | Exactly one: the mapped element, or a Boolean for FILTER. Leaving nothing is an error; anything below the top is discarded |
FOLD SCAN | Two: the accumulator and the element | Exactly 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 code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
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 code | Expected value | Notes |
|---|---|---|
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 code | Expected value | Notes |
|---|---|---|
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.
| Role | Words |
|---|---|
| Arithmetic | ADD SUB MUL DIV |
| Comparison | EQ LT GT |
| Logic | AND NOT |
| Truth values and absence | TRUE FALSE NIL |
| Choice | SELECT |
| Iteration | MAP FILTER FOLD SCAN |
| Definition and binding | DEF 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 code | Expected value | Notes |
|---|---|---|
[ [ 10 ] ADD ] 'ADD10' DEF
5 ADD10 |
[ 15/1 ] |
Defines ADD10 and calls it. |
| Sample code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 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.
| Family | Words | Explained in |
|---|---|---|
| Logic | TRUE FALSE AND NOT SELECT | 2.4.2.1 Logic, comparison, arithmetic |
| Comparison | EQ LT GT | 2.4.2.1 Logic, comparison, arithmetic |
| Exact arithmetic | ADD SUB MUL DIV FLOOR ROUND MIN MAX SQRT POW GCD RATIO | 2.4.2.1 Logic, comparison, arithmetic |
| Vectors | GET LENGTH TAKE DROP CONCAT REVERSE COLLECT RANGE FILL SHAPE RESHAPE FLATTEN DEPTH SORT ORDER UNIQUE ZIP PUT INDEX-OF MEMBER? BSEARCH | 2.4.2.2 Vector operations |
| Records | TALLY GROUP RECORD KEYS VALUES WITHOUT HAS? MERGE | 2.4.2.3 Keyed data |
| Iteration | MAP FILTER FOLD SCAN | 2.4.2.5 Choice and iteration |
| Text | CHARS JOIN TRIM UPPER LOWER TOKENIZE SEARCH REPLACE NUM STR FORMAT JSON-DECODE JSON-ENCODE | 2.4.2.4 String operations |
| Control | EXEC FAIL | 2.4.2.5 Choice and iteration |
| Dictionary and names | CONTRACT BIND DEF DEL DIGEST | 2.4.2 Words |
| Absence (NIL) | NIL NIL? NIL-REASON ABSENT | 2.4.2.6 Handling absence |
| Output | PRINT | 2.4.2.7 Output |
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
| Word | Meaning |
|---|---|
ADD | Addition |
SUB | Subtraction |
MUL | Multiplication |
DIV | Exact 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 ] MULis[ 10/1 20/1 30/1 ], and[ [ 1 2 ] [ 3 4 ] ] [ 10 20 ] MULis[ [ 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 ] MULcannot 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 code | Expected value | Notes |
|---|---|---|
[ 1 2 3 ] [ 4 5 6 ] ADD |
[ 5/1 7/1 9/1 ] |
Element-wise addition. |
| Sample code | Expected value | Notes |
|---|---|---|
[ 1 2 3 ] [ 10 ] MUL |
[ 10/1 20/1 30/1 ] |
A one-element Vector broadcasts across the whole of the other operand. |
| Sample code | Expected value | Notes |
|---|---|---|
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 valuex 'X' BIND X X MUL SQRT. - Extremes:
MIN(minimum),MAX(maximum) - Square root:
SQRT - Rounding:
FLOOR(round down),ROUND(round half up). Ceiling is writtenx -1 MUL FLOOR -1 MUL. - Rounding to a grid:
x d MUL 1/2 ADD FLOOR d DIVrounds a value to a multiple of1/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 code | Expected value | Notes |
|---|---|---|
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
| Word | Meaning |
|---|---|
EQ | Equal |
LT | Less than |
GT | Greater than |
| Sample code | Expected value | Notes |
|---|---|---|
5 3 GT |
TRUE |
A comparison that is decided returns a Boolean. |
| Sample code | Expected value | Notes |
|---|---|---|
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 code | Expected value | Notes |
|---|---|---|
TRUE 1 EQ |
FALSE |
A Boolean is not the number 1. |
| Sample code | Expected value | Notes |
|---|---|---|
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 code | Expected value | Notes |
|---|---|---|
TRUE FALSE AND |
FALSE |
Logical AND of two decided values. |
| Sample code | Expected value | Notes |
|---|---|---|
TRUE NOT FALSE NOT AND NOT |
TRUE |
Logical OR of two decided values, written as a combination of AND and NOT. |
| Sample code | Expected value | Notes |
|---|---|---|
TRUE NOT |
FALSE |
Logical NOT of a decided value. |
| Sample code | Expected value | Notes |
|---|---|---|
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 code | Expected value | Notes |
|---|---|---|
[ 10 20 30 ] 0 GET |
10/1 |
Index 0 is the first element. Both the Vector and the index are consumed. |
| Sample code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 1 2 3 ] LENGTH |
3/1 |
The element count. The Vector is consumed. |
| Sample code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 1 2 ] [ 3 4 ] CONCAT |
[ 1/1 2/1 3/1 4/1 ] |
Concatenates two Vectors. |
| Sample code | Expected value | Notes |
|---|---|---|
[ 1 2 3 ] REVERSE |
[ 3/1 2/1 1/1 ] |
Reverses the order of the elements. |
| Sample code | Expected value | Notes |
|---|---|---|
1 5 RANGE |
[ 1/1 2/1 3/1 4/1 5/1 ] |
The integers from start to end, inclusive. |
| Sample code | Expected value | Notes |
|---|---|---|
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 code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 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.
| Word | Example | Result |
|---|---|---|
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.
| Word | Example | Result |
|---|---|---|
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 ] ] ] DEPTH | 3/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 code | Expected value | Notes |
|---|---|---|
[ 18 13 1 1 13 2 ] ORDER 3 TAKE 'P' BIND |
[ '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 code | Expected value | Notes |
|---|---|---|
[ '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. |
| Word | Example | Result |
|---|---|---|
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' GET | 2/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 code | Expected value | Notes |
|---|---|---|
[ '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 code | Expected value | Notes |
|---|---|---|
[ '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 code | Expected value | Notes |
|---|---|---|
[ '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 code | Expected value | Notes |
|---|---|---|
'hello' CHARS |
[ 'h' 'e' 'l' 'l' 'o' ] |
CHARS crosses from the string domain to the Vector domain. |
| Sample code | Expected value | Notes |
|---|---|---|
[ 'hel' 'lo' ] JOIN |
'hello' |
Concatenates a Vector of strings. |
| Sample code | Expected value | Notes |
|---|---|---|
' hi ' TRIM |
'hi' |
Removes whitespace from both ends. |
| Sample code | Expected value | Notes |
|---|---|---|
'a,b,c' ',' TOKENIZE |
[ 'a' 'b' 'c' ] |
Splits on a delimiter. JOIN puts the pieces back together. |
| Sample code | Expected value | Notes |
|---|---|---|
'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 code | Expected value | Notes |
|---|---|---|
42 STR |
'42' |
Number to string. |
| Sample code | Expected value | Notes |
|---|---|---|
'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 code | Expected value | Notes |
|---|---|---|
'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 code | Expected value | Notes |
|---|---|---|
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 code | Expected value | Notes |
|---|---|---|
'{"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 code | Expected value | Notes |
|---|---|---|
[ '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 code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ '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 code | Expected value | Notes |
|---|---|---|
[ '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 code | Expected value | Notes |
|---|---|---|
[ 1 2 3 ] [ 2 MUL ] MAP |
[ 2/1 4/1 6/1 ] |
Applies the block to each element. |
| Sample code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 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 code | Expected value | Notes |
|---|---|---|
[ 1 3 5 8 ] [ 2 DIV 'M' BIND M FLOOR M EQ ] MAP |
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 code | Expected value | Notes |
|---|---|---|
[ 2 4 6 8 ] [ 2 DIV 'M' BIND M FLOOR M EQ ] MAP |
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 code | Expected value | Notes |
|---|---|---|
[ 0 [ ADD ] FOLD ] 'PSUM' DEF
[ 3 4 ] PSUM |
7/1 |
The argument is the Vector itself. Fold it rather than unpack it. |
| Sample code | Expected value | Notes |
|---|---|---|
[ [ 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 code | Expected value | Notes |
|---|---|---|
1 0 DIV NIL? |
TRUE |
NIL? consumes the top value and pushes its answer in its place. |
| Sample code | Expected value | Notes |
|---|---|---|
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 code | Expected value | Notes |
|---|---|---|
'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 code | Expected value | Notes |
|---|---|---|
1 0 DIV 'S' BIND 99 S S NIL? SELECT |
99/1 |
The value was absent, so the fallback is chosen. |
| Sample code | Expected value | Notes |
|---|---|---|
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 code | Expected value | Notes |
|---|---|---|
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 code | Expected value | Notes |
|---|---|---|
42 PRINT |
— | The default mode consumes the operand: the stack becomes empty and the value appears only in the output area. |
| Sample code | Expected output | Notes |
|---|---|---|
'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 code | Expected value | Notes |
|---|---|---|
[ 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 |
[ 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 |
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 code | Expected value | Notes |
|---|---|---|
[ 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.
| Layer | What it means to you | Why it matters |
|---|---|---|
| Value equality | Are these the same mathematical value? | This is what the comparison Words use. |
| Structural identity | Are they stored the same way internally? | Not observable to you. An implementation is free to optimize representations. |
| Display equality | Do two rendered strings look the same? | A display helps a human, but it does not prove equality. |
| Conformance equality | Do 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.