Infix operators

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          # TRUE

Related 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 # TRUE

CONTAINS 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       # 8

Array 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 > 0

Without 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)

  1. OF binds tighter than + / -: ABS() OF R1C2 + 1ABS(R1C2 + 1).
  2. >> binds tighter than a trailing step on a completed OF apply: ABS() OF R1C2 >> ROUND(_, 0)ROUND(ABS(R1C2), 0).
  3. 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 currency

IS 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:

  1. Bitwise — BITANDSHR
  2. Multiplicative — *, /, //, %%, MOD
  3. Additive — +, -, ++
  4. Concatenation — &
  5. Comparison — DIVIDES, NOT DIVIDES, COPRIME, IS …, HAS, SUBSET OF, …
  6. Set — UNION, INTERSECT, EXCEPT, EXCLUDE
  7. Projection — PICK, OMIT, RENAME
  8. Cast — INTO <type-tag> (TYPE_TAG)
  9. Infix apply — OF (right-associative; desugars to reversed >>)
  10. Pipe — >> with optional trailing IF cond