Extended Infix Operators
Extended Infix Operators
Grid supports spreadsheet-style formulas plus a growing set of readable infix
operators that desugar to built-in function calls. This page is the operator
reference for the batch added in the 0.21.x line: divisibility, expanded IS
predicates, collection relations, bitwise ops, array append, and conditional
pipeline steps.
For the full precedence ladder see reference.md.
For the feature checklist see features.md.
Divisibility and number theory
These bind at the comparison layer (same precedence as = and IN).
| Syntax | Desugars to | Meaning |
|---|---|---|
a DIVIDES b |
MOD(a, b) = 0 |
a divides b evenly |
a NOT DIVIDES b |
NOT (MOD(a, b) = 0) |
a does not divide b |
a /? b |
MOD(a, b) = 0 |
Alias for DIVIDES (/? is not division) |
a IS MULTIPLE OF n |
MOD(a, n) = 0 |
a is a multiple of n |
a IS NOT MULTIPLE OF n |
NOT (MOD(a, n) = 0) |
Negated multiple test |
a COPRIME b |
GCD(a, b) = 1 |
Greatest common divisor is 1 |
Examples:
R1C1 = 10 DIVIDES 5 # TRUE
R1C2 = 10 NOT DIVIDES 3 # TRUE (10 mod 3 ≠ 0)
R1C3 = 12 IS MULTIPLE OF 4 # TRUE
R1C4 = 8 COPRIME 15 # TRUERelated arithmetic (multiplicative layer):
| Syntax | Desugars to |
|---|---|
a %% b, a MOD b, a MODULO b |
MOD(a, b) |
a // b |
Floor integer division |
IS predicates
The IS suffix attaches to any expression and lowers to a type or domain
predicate function. Prefix NOT after IS negates the result.
Type and emptiness
| Syntax | Function |
|---|---|
x IS BLANK |
ISBLANK(x) |
x IS EMPTY |
ISEMPTY(x) — blank cell, empty string, or empty array |
x IS NUMBER |
ISNUMBER(x) |
x IS NOT NUMBER |
NOT ISNUMBER(x) |
x IS STRING / TEXT |
ISTEXT(x) |
x IS LOGICAL / BOOLEAN |
ISLOGICAL(x) |
x IS ERROR |
ISERROR(x) |
x IS NA |
ISNA(x) |
x IS DATE |
ISDATE(x) |
x IS DATETIME |
ISDATETIME(x) |
x IS DURATION |
ISDURATION(x) |
x IS COMPLEX |
ISCOMPLEX(x) |
x IS URL |
ISURL(x) |
Number theory (via IS)
| Syntax | Function |
|---|---|
n IS ODD |
ISODD(n) |
n IS EVEN |
ISEVEN(n) |
n IS PRIME |
ISPRIME(n) |
n IS MULTIPLE OF d |
MOD(n, d) = 0 |
Examples:
R1C1 = 7 IS PRIME # TRUE
R1C2 = 4 IS NOT PRIME # TRUE
R1C3 = "" IS EMPTY # TRUE
R1C4 = [1, 2, 3] IS NOT EMPTY # TRUECONTAINS is ordered containment. For text it is a case-sensitive substring
test; for arrays, tuples, vectors, and deques it requires one contiguous run of
deeply, type-sensitively equal direct elements. Use ILIKE when text matching
should ignore case. Ordered collection matching is linear in source plus pattern
length; a one-element pattern takes the direct scan fast path.
Collection operators
These compare collection values at the comparison layer.
| Syntax | Function | Meaning |
|---|---|---|
item IN collection |
IN(item, collection) |
A direct element deeply and type-sensitively equals item |
collection HAS item |
IN(item, collection) |
Converse spelling of IN |
left SUBSET OF right |
SUBSET(left, right) |
Every distinct direct element of left occurs in right; order and duplicates do not matter |
left SUPERSET OF right |
SUPERSET(left, right) |
Every distinct direct element of right occurs in left |
left OVERLAPS right |
OVERLAPS(left, right) |
At least one deeply equal direct element is shared |
ordered CONTAINS sequence |
— | sequence is a contiguous ordered run; text uses the same rule for characters |
Collection relations preserve structure: they do not recursively flatten
nested values. A 2-D array's direct elements are its rows. Consequently,
[1, 2] IN [[1, 2], [3, 4]] is true, while
1 IN [[1, 2], [3, 4]] is false. A column literal is 2-D as well, so
[1; 2; 3] exposes one-cell rows and [1, 2] SUBSET OF [1; 2; 3] is false for
every element type. SUBSET OF is set-like (unordered and
duplicate-insensitive); CONTAINS is sequence-like (ordered and contiguous).
For sets and maps, membership and set relations operate on keys. CONTAINS is
not defined for those unordered types.
All five relations use one structural equality contract. Types and semantic
tags are part of identity. Errors compare by error code rather than diagnostic
message; signed zero values compare equal; NaN never compares equal; and
infinities compare equal only with the same sign. Set-like relations build one
deduplicated hash index and verify collisions with full structural equality, so
ordinary evaluation is linear in the visited direct elements rather than a
repeated nested scan.
Set algebra (UNION, INTERSECT, EXCEPT) lives on the set layer above
comparison; see features.md.
Examples:
R1C1 = [1, 2, 3] HAS 2
R1C2 = [1, 2] SUBSET OF [1, 2, 3] # TRUE
R1C3 = [1, 2] OVERLAPS [3, 4] # FALSE
R1C4 = [1, 2] IN [[1, 2], [3, 4]] # TRUE
R1C5 = [1, 2, 3, 4] CONTAINS [2, 3] # TRUE
R1C6 = [1, 2, 3, 4] CONTAINS [2, 4] # FALSE (not contiguous)
R1C7 = "abcd" CONTAINS "bc" # TRUE (case-sensitive)Callable forms: IN(item, candidate, ...), CONTAINS(source, pattern),
SUBSET(a, b), SUPERSET(a, b), OVERLAPS(a, b), and ISEMPTY(x) — see
functions.md. Callable IN requires at least one candidate;
CONTAINS, SUBSET, SUPERSET, and OVERLAPS require exactly two arguments;
ISEMPTY requires exactly one.
Bitwise infix
Bitwise operators bind between combinatoric and multiplicative — tighter than
*, /, //, and %%.
| Syntax | Function |
|---|---|
a BITAND b |
BITAND(a, b) |
a BITOR b |
BITOR(a, b) |
a BITXOR b |
BITXOR(a, b) |
a BITLSHIFT b / a SHL b |
BITLSHIFT(a, b) |
a BITRSHIFT b / a SHR b |
BITRSHIFT(a, b) |
Operands are truncated to integers before the operation (Excel / Sheets semantics).
Example:
R1C1 = 5 BITAND 3 # 1
R1C2 = 5 BITXOR 3 # 6
R1C3 = 1 SHL 3 # 8Array append (++)
At the additive layer, ++ vertically stacks two arrays:
R1C1 = [1, 2] ++ [3, 4] # → VSTACK → [1, 2, 3, 4]This is distinct from string concatenation (&) and numeric addition (+).
Conditional pipeline
The pipe operator >> (alias |>) threads a value through function calls.
Append IF condition after a pipe step to apply that step only when the
condition holds; otherwise the value from before that step passes through.
Each >> step may have its own trailing IF; earlier steps in the chain always run.
# Desugars to IF(R1C2 > 0, ABS(R1C2), R1C2)
R1C1 = R1C2 >> ABS() IF R1C2 > 0
# Only ROUND is conditional; ABS always runs first.
# When R1C2 = -5: ABS → 5, IF false skips ROUND → 5 (not -5).
R1C1 = R1C2 >> ABS() >> ROUND(_, 0) IF R1C2 > 0Without IF, ordinary pipe desugaring applies: x >> f() → f(x),
x >> f(_, y) → f(x, y).
For elementwise filtering, use a named predicate lambda:
positive = values |> FILTER(value => value > 0)
safe = values |> FILTER(value => value > 0, "empty")This is exact sugar for FILTER(values, MAP(values, value => value > 0));
the optional second argument is FILTER's empty-result fallback. The older
FILTER(_, _ > 0) spelling remains accepted, but named lambdas keep pipe
insertion (_ as a direct argument) distinct from element binding.
The parser performs this desugaring before MIR. Stages therefore retain
left-to-right authored order and the same errors and empty fallback whether the
compiler later fuses the chain or uses generic evaluation. Outside a pipe,
FILTER(values, include_mask [, if_empty]) remains the ordinary spreadsheet
form. Collection-specific optimization and incremental behavior are documented
in collections.md.
Infix apply (OF)
OF is the function-first counterpart to >>: the callable appears on the
left, the value on the right, and the result matches a reversed pipe.
| Syntax | Equivalent pipe | Notes |
|---|---|---|
ABS() OF R1C2 |
R1C2 >> ABS() |
Left side must be a call |
ROUND(_, 2) OF ABS() OF R1C2 |
R1C2 >> ABS() >> ROUND(_, 2) |
Right-associative chain |
ADD(_, 5) OF R1C2 |
R1C2 >> ADD(_, 5) |
Placeholder in the callable |
Not supported in v1
| Syntax | Why |
|---|---|
R1C2 OF ABS() |
Value-before-function; use pipe |
ABS() OF ROUND(_, 2) |
Callable OF callable |
ABS OF R1C2 |
Bare name is not a call |
Distinct tokens
| Syntax | Meaning |
|---|---|
25 %OF 200 |
PERCENTOF(25, 200) — domain operator, not apply |
12 IS MULTIPLE OF 4 |
Divisibility at comparison precedence |
Precedence (among expression forms)
OFbinds tighter than+/-:ABS() OF R1C2 + 1→ABS(R1C2 + 1).>>binds tighter than a trailing step on a completedOFapply:ABS() OF R1C2 >> ROUND(_, 0)→ROUND(ABS(R1C2), 0).- Use parentheses when you need
(ABS() OF R1C2) + 1.
Mixed style in one expression is allowed when the precedence rules make the
intent clear; for long chains prefer either all-pipe or all-OF.
Record projection (PICK / OMIT / RENAME)
Project, drop, or rename keys on objects and headered 2D arrays. These bind at the
projection layer — tighter than set ops, looser than comparison — and
lower to the same PICK / OMIT / RENAME builtins as @ sugar and call forms.
| Syntax | Function | Meaning |
|---|---|---|
source PICK id, glob"*amount*" |
PICK(...) |
Keep listed keys (exact or glob/wild pattern) |
source OMIT glob"*secret*" |
OMIT(...) |
Drop matching keys |
source RENAME "old" AS "new" |
RENAME(...) |
Rename keys or headers |
Examples:
R1C1 = users PICK id, glob"*name*"
R1C2 = profile OMIT glob"*token*"
R1C3 = merged UNION other PICK "id" # PICK applies after the union
R1C4 = row OMIT "draft" PICK "id" # left-associative chain
R1C5 = srcA PICK "x" UNION srcB # project before combining
R1C6 = table RENAME "Revenue" AS "rev", "Cost" AS "cost"Glob keys: use glob"pattern" or wild"pattern" in the key list. Matching is
case-insensitive and uses * / ? wildcard semantics (same family as
WILDCARDMATCH).
Not the same as EXCLUDE: [1, 2, 3] EXCLUDE [2] is set algebra on flat
arrays. row OMIT "secret" removes object keys or table columns by header name.
Callable forms: PICK(source, ...), OMIT(source, ...), RENAME(source, from, to, ...)
— see functions.md.
In infix PICK/OMIT, a bare identifier is a literal key; use the callable
form when the key itself comes from an expression. Static exact PICK over a
source-proven Frame lowers to the same native projection stage as SQL SELECT.
Dynamic type cast (INTO)
Overlay a semantic type tag on an expression value. This is the expression-level
counterpart to declaration-level target IS <tag> = …: the same tag overlay,
but usable inside further processing.
| Syntax | Equivalent form | Meaning |
|---|---|---|
expr INTO <type-tag> |
TYPE_TAG(expr, "<tag>") |
Cast / tag overlay |
Examples:
R1C1 = amount INTO currency
R1C2 = { "total": 100 } PICK "total" INTO currency
R1C3 = ROUND(eur_amount * fx_rate, 2) INTO currencyIS remains reserved for predicates (x IS BLANK, x IS MULTIPLE OF 5) and
for assignment declarations (A1 IS currency = …). Use INTO when tagging a
value inside an expression.
Precedence summary (relevant slice)
Highest listed first among operators covered on this page:
- Bitwise —
BITAND…SHR - Multiplicative —
*,/,//,%%,MOD - Additive —
+,-,++ - Concatenation —
& - Comparison —
DIVIDES,NOT DIVIDES,COPRIME,IS …,HAS,SUBSET OF, … - Set —
UNION,INTERSECT,EXCEPT,EXCLUDE - Projection —
PICK,OMIT,RENAME - Cast —
INTO <type-tag>(TYPE_TAG) - Infix apply —
OF(right-associative; desugars to reversed>>) - Pipe —
>>with optional trailingIF cond