Overview


Zap is a compile-to-JavaScript language with a clean, concise syntax and powerful high-level operators. Zap can use JavaScript objects and libraries, and vice-versa.

Literals are particularly simple:

# u 5 v 6    // Object {u: 5, v: 6}
## u 5 v 6   // Map {'u' => 5, 'v' => 6}
@ 5 6        // Array [5, 6]
@@ 5 6       // Set {5, 6}

Everything in Zap is an expression — no blocks, statements or keywords. Except for a few special cases, code reads left to right and the 'current' operator is applied when the next operator or the end of the expression is reached:

@ 3 4 5     // [3, 4, 5]
3 @ 4 5     // [3, 4, 5]
3 4 5 @     // [3, 4, 5]

3 + 4 @ 5   // [7, 5]
+ 3 4 @ 5   // [7, 5]
3 4 + @ 5   // [7, 5]

Basic operators such as + can take more the two operands:

3 4 5 +   // 12
+ 3 4 5   // 12

Many operators have names rather than symbols:

5 isNumber print   // prints (and returns) true

Indented blocks or parentheses can be used for precedence:

3 * (4 + 5)   // 27
3 * 
    4 + 5     // 27

x = @ 3 4 5
x each v (print v)   // prints 3 4 5
x each v             
    print v          // prints 3 4 5

Indented blocks can contain multiple expressions:

// prints 12 30 56
@ 3 4 5 each value index
    z = value + index
    z ^ 2 + z print

An expression terminates at the end of a line unless the following line opens an indentated block or continues the line with |.

// array-of-objects
dogs = 
    @
        #
        | name 'Bill'
        | breed 'boxer'
        | food (@ 'bananas' 'beans' 'biscuits')
    | 
        #
        | name 'Debra'
        | breed 'doberman'
        | food (@ 'dates' 'donuts')

// on fewer lines
dogs = @
| (# name 'Bill'  breed 'boxer'    food (@ 'bananas' 'beans' 'biscuits'))
| (# name 'Debra' breed 'doberman' food (@ 'dates' 'donuts'))

fun creates a function; \ calls a function. \ has special behavior: the first right operand is always the function:

double = fun x (2 * x)   // body in parentheses

add = fun x y
    x + y                // body in indented block

\double 5 \add 20        // 30
5 \double \add 20        // 30
\add (\double 5) 20      // 30

Bracket functions can be used for 'one-liners'. Bracket functions have parameters a, b and c, and are comprised of a single expression:

add = [a + b]   // function

The ~ operator calls a method:

'abcd' ~slice 1 3 ~repeat 2   // 'bcbc'

The range operators create generators:

1 to 3           // generator (1 2 3)
1 to 7 2         // generator (1 3 5 7)
1 7 linSpace 4   // generator (1 3 5 7)

There are lots of operators for working with iterables (arrays, sets, generators etc.). For example:

s = @@ 5 6 7              // Set {5, 6, 7}
s array                   // [5, 6, 7]
s each x (x + 10 print)   // prints 15 16 17
s map x (x + 10)          // [15, 16, 17]
s max                     // 7
s sum                     // 18
s filter [a > 5]          // [6, 7] 
s group [a > 5]           // Map {false => [5], true => [6, 7]}

Backticks tell an arithmetic, logical or comparison operator to iterate over an operand:

x = @ 5 6 7     // [5, 6, 7]
x  `+  10       // [15, 16, 17]       
10  +` x        // [15, 16, 17]
x  `+` x        // [10, 12, 14]

'ab'  +  'cd'   // 'abcd'
'ab' `+  'cd'   // ['acd', bcd']
'ab' `+` 'cd'   // ['ac', 'bd']

The many elementwise operators can be used with iterables or non-iterables:

@ 4 9 16 sqrt   // [2, 3, 4]
4 sqrt          // 2

Use class to write a constructor and extends to write a subclass constructor. :: gets a property from the prototype of an object so can be used to add methods:

// class with empty body: parameters become properties of 'this'
Cat = class name ()

// subclass with empty body: calls constructor of parent class
Lion = extends Cat ()
Lion :: 'speak' = fun mood
    + 'I am 'this,name' the 'mood' lion'

leo = new Lion 'Leo'   // Lion {name: "Leo"}
leo ~speak 'hungry'    // 'I am Leo the hungry lion'

Except for class and extends, all operators that take a body operand have an asynchronous version:

// prints the length of the data (and returns a promise that
// resolves to the length)
asyncScope
    data = 'data.json' \fetch await ~json await
    data,length print

DOM operators work with individual HTML/SVG elements, iterables of elements and CSS selector strings:

// set color of each span with class danger to red
'span.danger' style 'color' 'red'

Data can be encoded as (and is automatically attached to) elements:

// add 3 buttons to body, click a button to see its data
@ 3 4 5 encode 'button' 
| text 'show data'
| on 'click' [this text a]
| into 'body'

Use


Install
npm install --save '@gjmcn/zap'
Load

Zap can be loaded from a CDN. Examples:

The exported zap function compiles a string of Zap code to JavaScript. The optional second argument of zap is an options object that specifies which properties to include in the returned object:

Property Default Description
js true JavaScript code
sourceMap false source map
tokens false tokens produced by the lexer
procTokens false tokens after processing blocks
jsTree false JavaScript tree produced by the parser

When sourceMap is true, add a sourceFile option giving the path to the source file.

The options object can also have an iife property that specifies whether the generated JavaScript should be wrapped in an IIFE. This property can be 'sync' (synchronous IIFE, the default), 'async' (asynchronous IIFE) or 'none' (no IIFE). Wrapper IIFEs use strict mode.

Zap code compiles to modern JavaScript. Where appropriate, use a transpiler such as Babel to ensure compatibility with the target environment(s).

Command Line

The command line app and the REPL are currently very basic with limited functionality.

Zap can be installed as a command line app:

npm install -g @gjmcn/zap

Use zap with no arguments to start an interactive Zap REPL:

zap

Pass a file path to compile a file. For example, to compile the file index.za to index.js (in the same directory):

zap index.za

Use:

zap --help

to print the command-line options:

Short Long Behavior
-a --all Compile all .za files in directory to .js files
-c --compile Compile .za file to .js file of same name
-e --eval Evaluate a string from the command line
-h --help Print help
-i --interactive Run an interactive Zap REPL
-m --map Generate source map(s), save as .js.map file(s)
-n --nodes Print the JavaScript tree produced by the parser
-p --print Print the generated JavaScript
-t --tokens Print the tokens produced by the lexer
-r --proc Print the tokens after processing the blocks
-v --version Print version number

Additional information:

Code Editors

VS Code

Use the Zap Highlight extension for syntax highlighting. Given the flexibility of operator positioning in Zap, and that only operators 'do things' (functions are called with operators and there are no keywords), a theme that highlights operators differently to all other language elements can help readability. The included Solarized Dark theme and the One Dark Pro extension theme are good examples of this.

Basic Syntax


Operators are represented by symbols or reserved words. Each line is an expression that returns a value:

2 + 3    // 5
9 sqrt   // 3

Use | to continue an expression on a new line:

4 + 5
| sqrt   // 3

In general, Zap does not use operator associativity, position or precedence rules. Code reads left to right and the 'current' operator is applied when the next operator or end of the expression is reached. The result returned by an operator is used as the first operand of the next operator:

+ 2 3   // 5
2 + 3   // 5
2 3 +   // 5

+ 2 3 * 4   // 20
2 + 3 * 4   // 20
2 3 + * 4   // 20

If adjacent operators are represented by symbols, they must be separated by whitespace.


Parentheses and Indentation

Parentheses can be used for precedence:

2 + 3 * 4     // 20
2 + (3 * 4)   // 14

Parentheses cannot span multiple expressions:

2 + (3 *
| 4)      // 14 (this is a single expression)

(2 + 3
9 sqrt)   // syntax error   

Indentation can also be used for precedence. Indented blocks can contain multiple expressions:

2 + 
    3 * 4   // 14

2 + 
    x = 3
    x * 4   // 14    

Opening an indented block automatically continues an expression — do not use a |.

We can close an indented block (or more than one) and continue the expression with |. The following example uses the array literal operator @and the object literal operator # to create an array-of-objects:

// an array-of-objects
@
    #
    | name 'Alex'
    | best (# score 143 level 2)
| 
    #
    | name 'Cody'
    | best (# score 201 level 3)

Four spaces must be used for indentation. Use 'soft tabs' in code editors so that tabs are converted to spaces.


Identifiers

The term identifier is used frequently in these docs. An identifier is any combination of letters (currently only a-z, A-Z), digits (0-9), _ and $, but cannot start with a digit.


Special Syntax


Additional syntax rules apply to some operators:

The Right Operand Rule

The \, <\ operators (call function), and ~, <~ operators (call method) use the right operand rule: the first right operand is the function/method. We typically omit space to the right of these operators:

f = [a + b]   // function (adds its arguments)
\f 5 10       // 15
5 \f 10       // 15
5 10 \f       // 15
5 10 f \      // syntax error

Autoquoting

The operators ~ and <~ (call method), # (object literal) and ## (map literal) autoquote property names that are identifiers:

# 'u' 5 'v' 6   // {u: 5, v: 6}
# u 5 v 6       // {u: 5, v: 6}

Use the identifier as part of a larger expression (wrapping in parentheses suffices) to avoid autoquoting:

u = 'v'

# 
| u 5
| (u) 6
| (u + 'w') 7   // {u: 5, v: 6, vw: 7}

The property getter , autoquotes the property name:

o = # u 5 v 6   // {u: 5, v: 6}
o,u             // 5         

Operator names are not autoquoted:

o = # 'if' 5   // {if: 5} (quotes required)
o,if           // syntax error
o : 'if'       // 5 (the : property getter has no special behavior)

Property Getters

The , and ; property getters have high precedence and can only be used in subexpressions of the form:

variableName[, or ;]propertyName1[, or ;]propertyName2 ...

The variable name must be an identifier. The property name(s) must also follow the identifier rules except that property names can start with a digit.

, autoquotes the property name whereas ; does not. Space and line breaks are not permitted on either side of , or ;:

o = # u 5 v (@ 6 7 8)   // {u: 5, v: [6, 7, 8]}
p = 'u'

10 + o,u     // 15
10 + o;p     // 15
10 + o,v;2   // 18

, or ; can be used when the property name is an index (e.g. x,100 or x;100), but be careful with scientific notation:

Assignment

Assignment operators have low precedence and must be placed between their two operands:

x = 1 + 2     // 3
x += 3 + 4    // 10
5 + (x = 6)   // 11 (x is 6)
y = x = 8     // syntax error, multiple assignments are not allowed 

Destructuring assignment operators must be placed after the last variable being assigned to:

x = @ 5 6
u v @= x   // [5, 6] (u is 5, v is 6)

Body Operands

The following operators take a body operand:

fun proc scope as class extends each map do try catch

asyncFun asyncProc asyncScope asyncAs asyncEach asyncMap asyncDo asyncTry asyncCatch

The body is always the final operand and must be in parentheses or be an indented block. Also, the body cannot be before the operator:

fun x y
    x + y         // function

fun x y (x + y)   // function

x y fun (x + y)   // function

x y (x + y) fun   // syntax error, missing body

Body operands are important for the following reasons:

Operators


Symbols

Operator Arity Description
+ - 1+ unary plus/minus, add, subtract
* / % ^ 2+ multiply, divide, remainder, exponentiate
<> >< 2+ least, greatest
&& || ?? 2+ logical and, or, nullish coalescing
? 2-3 conditional
! 1 logical not
< <= > >= 2 compare
== != 2 strict equality, strict inequality
= 2 assign
\= 2 assign, do not trigger creation of local variable
<- 2 assign, use option of same name or default
?= 2 conditional assign
+= -= *= /= %= ^= 2 update-assign
#= 2+ destructure object
@= 2+ destructure array
, 2 get property (high precedence, autoquote)
; 2 get property (high precedence)
: 2 get property
:: 2 get property of prototype
?: 2 conditional get property
\ 1+ call function
<\ 2+ call function, return first argument
~ 2+ call method
<~ 2+ call method, return calling object
# 0+ object literal
## 0+ map literal
@ 0+ array literal
@@ 0+ set literal

The operators &&, ||, ??, ?, <-, ?= and ?: use short-circuit evaluation.


JavaScript Operators

await delete in instanceof new typeof yield yieldFrom void

These operators behave the same as their JavaScript counterparts except that:

o = # u 5 v 6
o delete 'u'    // true
o               // {v: 6}
new Date           // current local date and time
new Date '2050'    // 01 January 2050, 00:00:00
new Date 2050 11   // 01 December, 2050, 00:00:00

Body

as catch class do each extends fun map proc scope try

asyncAs asyncCatch asyncDo asyncEach asyncFun asyncMap asyncProc asyncScope asyncTry


Elementwise Math

abs acos acosh asin asinh atan atanh cbrt ceil clz32 cos cosh exp expm1 floor fround log log10 log1p log2 round sign sin sinh sqrt tan tanh trunc


Elementwise Other

boolean date neg not number string toLowerCase toUpperCase trim trimEnd trimStart


Filter, Group, Reduce, Sort

bin binCount count correlation covariance covariancePop deviation deviationPop every filter filterIndex find findIndex group groupCount max maxIndex mean median min minIndex order orderIndex quantile reduce some sum sumCumu rank variance variancePop


Get/Set Property

assign at attach chg getter setter


Import and Export

export import importAll importAs importDefault load


Is

isArray isBigInt isBoolean isFinite isFunction isInteger isNaN isNull isNullish isNumber isString isSymbol isUndefined


Joins

antiJoin crossJoin crush innerJoin leftJoin outerJoin rightJoin semiJoin


Nested Data

arrObj mapAt objArr pick transpose


Other

array apply call debugger empties if linSpace ones period print throw to zeros interpolate


Random and Shuffle

binomial categorical exponential geometric logNormal normal random randomInt shuffle


Set Theory

difference intersection union


DOM

addClass attr create createSVG encode encodeSVG fragment hasAttr hasClass html insert insertEach into lower off on prop raise remove removeAttr removeClass removeStyle select selectAll sketch style text toggleClass

HTML Elements

$a $abbr $address $area $article $aside $audio $b $base $bdi $bdo $blockquote $body $br $button $canvas $caption $cite $code $col $colgroup $data $datalist $dd $del $details $dfn $dialog $div $dl $dt $em $embed $fieldset $figcaption $figure $footer $form $h1 $h2 $h3 $h4 $h5 $h6 $head $header $hgroup $hr $i $iframe $img $input $ins $kbd $label $legend $li $link $main $map $mark $menu $meta $meter $nav $noscript $object $ol $optgroup $option $output $p $param $picture $pre $progress $q $rb $rp $rt $rtc $ruby $s $samp $script $section $select $slot $small $source $span $strong $style $sub $summary $sup $table $tbody $td $template $textarea $tfoot $th $thead $time $title $tr $track $u $ul $var $video $wbr

SVG Elements

$animate $animateMotion $animateTransform $circle $clipPath $defs $desc $discard $ellipse $feBlend $feColorMatrix $feComponentTransfer $feComposite $feConvolveMatrix $feDiffuseLighting $feDisplacementMap $feDistantLight $feDropShadow $feFlood $feFuncA $feFuncB $feFuncG $feFuncR $feGaussianBlur $feImage $feMerge $feMergeNode $feMorphology $feOffset $fePointLight $feSpecularLighting $feSpotLight $feTile $feTurbulence $filter $foreignObject $g $hatch $hatchpath $image $line $linearGradient $marker $mask $metadata $mpath $path $pattern $polygon $polyline $radialGradient $rect $set $solidcolor $stop $svg $switch $symbol $text $textPath $tspan $use $view

Literals


Basics

5           // 5 (number)
5.6         // 5.6 (number)
5e-2        // 0.05 (number)
'abc'       // 'abc' (string)
"abc"       // 'abc' (string)
"a
bc"         // 'a\nbc' ("..." strings can be multiline)
&/abc/      // /abc/ (regular expression)
false       // false (boolean)
null        // null
undefined   // undefined

Objects and Maps

Use the # operator for an object, and ## for a map:

#             // empty object
# u 5 v 6     // {u: 5, v: 6}

##            // empty map
## u 5 v 6    // Map {'u' => 5, 'v' => 6}

# and ## use autoquoting, which is why the property names in the above examples need not be quoted.


Arrays

Use the @ operator for an array:

@       // empty array
@ 5 6   // [5, 6]

The array operator is like @, except that if an operand is a non-string iterable, array uses the elements of the iterable rather than the iterable itself:

i = 2         // 2
s = '34'      // '34'
x = @ 5 6 7   // [5, 6, 7] 
y = @@ 8 9    // Set {8, 9} (see below)

array     // empty array
i array   // [2]   
s array   // ['34']  
x array   // [5, 6, 7]
y array   // [8, 9]

i s x y @       // [2, '34', [5, 6, 7], Set {8, 9}]
i s x y array   // [2, '34', 5, 6, 7, 8, 9]

// array does not 'flatten' iterables recursively
z = @ 2 (@ 3 4)   // [2, [3, 4]]
1 z array         // [1, 2, [3, 4]] 

empties, zeros and ones create an array of a given length. zeros and ones set each entry to 0 or 1. Include additional operands to create nested arrays:

3 empties    // [empty x 3]
2 3 zeros    // [[0, 0, 0], [0, 0, 0]]
2 3 2 ones   // [
             //   [[1, 1], [1, 1], [1, 1]],
             //   [[1, 1], [1, 1], [1, 1]]]
             // ]

Sets

Use the @@ operator for a set:

@@       // empty set
@@ 5 6   // Set {5, 6}

Note that @@ interprets each operand as an element, whereas the Set constructor extracts elements from an iterable:

@@ 'ab'        // Set {'ab'}
new Set 'ab'   // Set {'a', 'b'}

Calling Functions


The \ operator calls a function. \ uses the right operand rule:

f = [a + b]   // function (adds its arguments)
\f 5 10       // 15 
5 \f 10       // 15
5 10 \f       // 15

The behavior of \ allows us to 'pipe' function calls rather than nest them:

double = [2 * a]      // function
add = [a + b]         // function
\double 5 \add 20     // 30 (piped calls)
\add (\double 5) 20   // 30 (nested calls)

Methods

\ can be used to call methods, but there is also a dedicated method call operator ~ which is often more convenient. ~ uses the right operand rule and autoquoting:

x = @ 5 6 7 8
x ~slice 1 3   // [6, 7]
~slice x 1 3   // [6, 7]
x 1 3 ~slice   // [6, 7]
\x,slice 1 3   // [6, 7]

JSON ~stringify x   // '[5,6,7,8]'
\JSON,stringify x   // '[5,6,7,8]'
x \JSON,stringify   // '[5,6,7,8]'

Return Calling Object or First Argument

<~ is like ~, but <~ always returns the calling object. For example, we can use <~ to chain the array methods push and shift (neither of which return the calling array):

@ 5 6 <~push 7 <~shift   // [6, 7]

Similarly, <\ is like the function call operator \, but <\ returns the first argument passed to the function.


call and apply

Call a function.

The first operand of call must be a function; any other operands are passed to the function:

f = [a + b]   // function (adds its arguments)
call f 5 10   // 15 
f call 5 10   // 15

apply is like call, but any arguments for the function are passed inside a single iterable:

f = [a + b]        // function (adds its arguments)
f apply (@ 5 10)   // 15
apply f (@ 5 10)   // 15

call and apply are like the function methods call and apply respectively, but the Zap operators do not specify the value of this. Also, the apply operator can take any iterable, whereas the apply method is restricted to array-like objects.

Assignment


Assignment operators do not follow the normal position and precedence rules.


=

Standard assignment:

x = 5   // 5
x       // 5

\=

Assign, but do not trigger the creation of a local variable (see Local Variables):

x = 5         // 5
scope
    x         // 5
    x \= 10   // 10
    x         // 10
x             // 10

+=, -=, *=, /=, %=, ^=

Update-assignment:

x = 5     // 5
x += 10   // 15
x         // 15

?=

Use ?= for conditional assignment: if the variable (the first operand) is currently undefined or null, the second operand is evaluated and the result is assigned to the variable. If the variable is not currently undefined or null, the variable is assigned its current value and the second operand is not evaluated (short-circuit evaluation):

x = null
x ?= 5    // 5 (x is 5)
x ?= 10   // 5 (x is 5)

#=

Destructure an object:

o = # h 5 i 6 j 7
h j z #= o   // returns o, h is 5, j is 7, z is undefined

@=

Destructure an iterable:

x = @ 5 6 7
u v @= x       // returns x, u is 5, v is 6
u v w z @= x   // returns x, u is 5, v is 6, w is 7, z is undefined

Local Variables

Assignment triggers the creation of a local variable. The variable is created at the start of the current body (or start of the file if not inside a body), and has the value undefined until it is assigned to. The following example uses the scope operator:

x = 5        // 5 (outer x)

scope
    x        // 5 (outer x)

// assign to x in the body of scope so a local x is created
scope
    x        // undefined (local x)
    x = 10   // 10 (local x)
    x        // 10 (local x)

x            // 5 (outer x)

Using the default compiler options (see Use), variables created outside of a body are not global — i.e. they are not visible to other files. Set a property of the global object to create a global variable:

globalThis,x = 5   // works in any JavaScript environment
window,y = 6       // works in browsers
global,z = 7       // works in Node.js

Only =, #=, @= and <- (discussed in Writing Functions) trigger the creation of local variables. The other assignment operators change the value of an existing variable.

Get Property


,, ;

Get an object/array property or a character from a string. These operators have special behavior including high precedence. , autoquotes the property name whereas ; does not:

circle = #
| radius 50
| center (@ 100 200)
| color 'red'

r = 'radius'

circle,radius     // 50
circle;r          // 50
circle,center,1   // 200   
circle,color,2    // 'd'

:

Get an object/array property or a character from a string:

circle = #
| radius 50
| center (@ 100 200)
| color 'red'

r = 'radius'

circle : 'radius'          // 50
circle : r                 // 50
circle : ('rad' + 'ius')   // 50
circle : 'center' : 1      // 200   
circle : 'color' : 2       // 'd'

::

As :, but :: gets a property from the object's prototype:

Array : 'prototype' : 'slice'   // function
Array :: 'slice'                // same function

?:

As :, but ?: short-circuits and returns undefined if the object is null or undefined:

o = # u 5 v (# x 10 y 20)   // {u: 5, v: {x: 10, y: 20}}

o : 'q'          // undefined
o : 'q' : 'y'    // TypeError: Cannot read property 'y' of undefined
o : 'q' ?: 'y'   // undefined

at

at can get multiple properties or characters — the second operand of at must be an iterable.

at returns the same kind of object that it gets properties or characters from (string → string, array → array, object → object). Use a truthy third operand to force at to return an array:

// array-of-objects
data = @
| (# Name 'vw pickup'     Horsepower 52 Origin 'Europe')
| (# Name 'dodge rampage' Horsepower 84 Origin 'USA')
| (# Name 'ford ranger'   Horsepower 79 Origin 'USA')

data at (@ 2 0)
    // [
    //   {Name: 'ford ranger', Horsepower: 79, Origin: 'USA'}
    //   {Name: 'vw pickup', Horsepower: 52, Origin: 'Europe'}
    // ]

data,2 at (@ 'Name' 'Origin')
    // {Name: 'ford ranger', Origin: 'USA'}

data,2 at (@ 'Name' 'Origin') 'array'
    // ['ford ranger', 'USA']

data,0,Origin at (@ 1 3)   // 'uo'

The second operand of at can be any iterable. For example, a string or a range:

# u 5 v 6 w 7 at 'uw'        // {u: 5, w 7}
'abcdefghij' at (1 to 9 2)   // 'bdfhj'

getter

Use a custom getter function for a property:

o = # celsius 10
o 'fahrenheit' getter [this,celsius * 1.8 + 32]

o,fahrenheit     // 50

o,celsius = 20   // 20 (set celsius property to 20)
o,fahrenheit     // 68

In general:

o 'x' getter f

is equivalent to:

Object ~defineProperty o 'x'
    #
    | get f
    | configurable true
    | enumerable true

See Object,defineProperty for details.

getter returns the modified object.

Set Property


Assignment

The assignment operator = and the update-assignment operators +=, -=, *=, /=, %=, ^= can be used to set an object/array property:

circle = # radius 50 center (@ 100 200)

circle,color = 'red'   // 'red'
circle,radius = 60     // 60
circle,center,1 += 5   // 205

circle   // {radius: 60, center: [100, 205], color: 'red'}

chg

chg takes an object/array, a property name and a new value for the property. chg sets the property and returns the modified object:

o = # u 5 v 6
o 
| chg 'u' 10
| chg 'w' 20   // {u: 10, v: 6, w: 20} 

Setting a property with chg is equivalent to using assignment; it is not equivalent to using Object,defineProperty.


attach

attach sets properties using variables of the same name:

width = 100
height = 200
color = 'red'

rect = # attach width      // {width: 100}
rect attach height color   // {width: 100, height: 200, color: 'red'}

assign

The assign operator copies properties from its second, third, fourth, ... operands to its first operand:

o = # u 5 v 6
p = # v 7 w 8

# assign o     // {u: 5, v: 6} (shallow copy o)
# assign o p   // {u: 5, v: 7, w: 8}
o assign p     // {u: 5, v: 7, w: 8}

assign is shorthand for calling Object,assign.


setter

Use a custom setter function for a property:

o = # celsius 10

o 'fahrenheit' getter [this,celsius * 1.8 + 32]
o 'fahrenheit' setter [this,celsius = a - 32 / 1.8]

o,fahrenheit   // 50

o,celsius = 20
o,fahrenheit   // 68

o,fahrenheit = 86
o,celsius      // 30

In general:

o 'x' setter f

is equivalent to:

Object ~defineProperty o 'x'
    #
    | set f
    | configurable true
    | enumerable true

See Object,defineProperty for details.

setter returns the modified object.

Writing Functions


A function can be a regular function or a procedure: a function that does not have its own this, arguments or super (i.e. a JavaScript arrow function).

A function returns the value of the last expression evaluated.


fun, asyncFun

Create a regular function.

The operands of fun are the parameter names and the function body:

// no parameters, body in parentheses
f = fun (5)
\f   // 5

// 2 parameters, body in parentheses
f = fun x y (x + y)
\f 5 10   // 15

// 2 parameters, body in indented block
f = fun x y
    z = x + y
    z + 30 / z
\f 5 10   // 3

Use asyncFun to create an asynchronous function. The following example uses period to create a promise that resolves after delay milliseconds:

af = asyncFun delay
    delay period await
    print 'done'

1000 \af   // waits 1000 ms, prints 'done'

proc, asyncProc

As fun and asyncFun, but proc and asyncProc create a procedure.


Bracket Functions

Brackets can be used to create a (synchronous) function comprised of a single expression. Use square brackets for a regular function and curly brackets for a procedure. Bracket functions have parameters a, b and c:

f = []        // regular function (body is empty)
\f            // undefined

f = [5]       // regular function
\f            // 5 

f = [a + 5]   // regular function
\f 10         // 15

f = {a + b}   // procedure
\f 10 5       // 15

When bracket functions are nested, the inner function cannot see the parameters of the outer function because of the shared parameter names.


The rest Parameter

If the final parameter of a function is called rest, it collects 'the rest of the arguments' in an array:

f = fun x y rest
    rest
\f 5 6 7 8   // [7, 8]

f = fun rest
    rest
\f 5 6 7 8   // [5, 6, 7, 8]

Default Parameters

Use ?= to set a default parameter value — the default is used if undefined or null is passed, or if no value is passed:

f = fun x
    x ?= 5
    x + 10

\f 3   // 13
\f     // 15  

The ops Parameter

A parameter called ops ('options') automatically defaults to an empty object. The assignment operator <- is specifically for working with ops objects. <- assigns a property of ops to a variable of the same name; the given default value is used if the property does not exist, or is undefined or null:

area = fun ops
    width <- 5
    height <- 10
    width * height

# width 20 height 30 \area   // 600
# height 30 \area            // 150
\area                        // 50

ops can be used with other parameters:

f = fun x ops rest
    u <- 5
    v <- 6
    @ x u v rest

\f 10 (# u 20) 30 40   // [10, 20, 6, [30, 40]]   

Except for defaulting to an empty object, there is nothing special about ops — it can be used and modified like any other object.

<- simply looks for the variable ops and gets the required property. ops will typically be a parameter of the current function, but need not be.

<- uses short-circuit evaluation: if the relevant property of ops is neither null nor undefined, the right-hand side of <- is not evaluated.


scope, asyncScope

Using scope is equivalent to writing a procedure with no parameters and immediately calling it. Like all body operands, variables created inside the body of scope are local:

x = 5
y = scope
    x = 10
    x + 20
y   // 30
x   // 5

asyncScope is the asynchronous version of scope — so asyncScope returns a promise rather than returning a value directly. await can be used in the body of asyncScope:

// waits 1000 ms, prints 5
asyncScope
    period 1000 await
    print 5

as, asyncAs

Like scope and asyncScope, but the body is preceded by an expression and a parameter that represents the value of the expression. The following example uses the max operator:

// array-of-objects
dogs = @
| (# name 'Alex' age 3) 
| (# name 'Beth' age 8) 
| (# name 'Cody' age 2)

// returns 'The oldest dog Beth is 8'
dogs max 'age' as oldest
    + 'The oldest dog 'oldest,name' is 'oldest,age

The parameter of as can be ops, but not rest.


Generator Functions

If yield or yieldFrom are used inside the body of fun (or asyncFun), a generator function (or asynchronous generator function) is created:

f = fun
    yield 5
    yield 10   // generator function

g = \f         // generator
g ~next        // {value: 5, done: false}

A procedure cannot be a generator function:

proc
    yield 5   // syntax error, invalid use of yield

yield and yieldFrom can be used inside scope, asyncScope, as and asyncAs:

g = scope
    yield 5
    yield 10   // generator

g ~next        // {value: 5, done: false}

When yield or yieldFrom are used in scope, asyncScope, as or asyncAs, the operator will behave like a regular function rather than a procedure — in particular, the body will have its own this.

Classes


class

Create a class.

We can use a regular function as a constructor:

Animal = fun name age
    this,name = name
    this,age = age

alex = new Animal 'Alex' 8   // Animal {name: "Alex", age: 8}

The class operator is used like fun, but returns a class rather than a function. Furthermore, when class is used:

Animal = class name age ()

// is equivalent to:

Animal = class name age
    this,name = name
    this,age = age
Animal = fun (# u 5)     // function
new Animal               // {u: 5}

Animal = class (# u 5)   // class
new Animal               // Animal {}
Animal = class name ()

new Animal 'Alex'   // Animal {name: "Alex"}

\Animal 'Alex'      // TypeError: Class constructor Animal cannot
                    // be invoked without 'new'

Use :: to add a function to an object's prototype — i.e. to add a method to a class:

Animal = class name ()

Animal :: 'speak' = fun
    + this,name' makes a noise'

alex = new Animal 'Alex'
alex ~speak   // 'Alex makes a noise'

extends

Create a subclass.

extends is like class except that:

Animal = class name ()

Cat = extends Animal ()   // default constructor
Cat :: 'speak' = fun
    + this,name' meows'

Dog = extends Animal name breed
    \super name   // see below
    this,breed = breed
Dog :: 'speak' = fun
    + this,name' the 'this,breed' barks'

colin = new Cat 'Colin'
colin ~speak   // 'Colin meows'

debra = new Dog 'Debra' 'doberman'
debra ~speak   // 'Debra the doberman barks'  

In the Dog constructor above, we call super to call the constructor of the parent class. In fact, a subclass constructor must call super — and the call must be before this is used.

When using extends, the parent class need not have been created with class; it can be a regular function or a built-in object:

Animal = fun ()             // function
Dog = extends Animal ()     // class
new Dog                     // Dog {}

Vector = extends Array ()   // class
new Vector 3                // Vector [empty × 3]

Loops


Operators for working with iterables (including loop operators, but also see e.g. Reduce, Filter, Group, Order, Bin, Backticks and Nested Data) treat empty array elements as normal elements (with value undefined). In contrast, some array methods ignore empty elements.


each

Loop over an iterable.

The first operand of each is the iterable; the final operand is the loop body. Between these, we can provide optional loop parameters for the current value, current index and the iterable:

// body in parentheses, prints: 4 5 6
@ 4 5 6 each x (print x)

// body in indented block, prints: 'a0' 'b1' 'c2'
'abc' each s i
    s + i print

each returns the iterable.

Use stop to exit a loop at the end of the current step:

// prints 4 5 6
@ 4 5 6 7 8 each x
    print x
    if (x == 6)
        stop

// prints 4 5 6
@ 4 5 6 7 8 each x
    if (x == 6)
        stop
    print x

map

As each, but map collects the value of the body (the last expression evaluated) at each step in an array:

@ 4 5 6 map x (x + 10)   // [14, 15, 16]

// returns [14, 15, 16]
@ 4 5 6 7 8 map x i
    if (i == 2)
        stop
    x + 10

do

Basic loop.

The final operand of do is the loop body. The optional operands are the maximum number of steps and the current index:

// prints 0 1 2 3 4
5 do i
    print i

do returns undefined.

Use stop to exit a loop at the end of the current step.

Use Infinity as the maximum number of steps when there is no actual maximum, but the index parameter is required:

// prints 0 1 2 3 4
Infinity do i
    print i
    if (i == 4)
        stop

If only the loop body is given, the maximum number of steps defaults to Infinity:

x = 1
do
    x *= 2
    if (x > 20)
        stop
x   // 32

Even though stop only exits a loop at the end of the current step, we can use if with an else branch to effectively exit a loop at the point where stop is encountered:

x = 25
do
    if (x > 20)
        stop
    | else
        x *= 2
x   // 25

The maximum number of steps is fixed before the loop starts:

// prints 0 1 2 3 4
n = 5
n do i
    n += 10
    print i

asyncEach, asyncMap, asyncDo

Asynchronous versions of each, map and do.

These operators return a promise that resolves to:

await can be used in the body of an asynchronous loop:

// wait 1000 ms, print 5, wait 1000 ms, print 6 
@ 5 6 asyncEach x
    period 1000 await
    print x

In the following example, we await the loop itself:

// wait 1000 ms, print 5, wait 1000 ms, print 6, print 'done'
asyncScope
    @ 5 6 asyncEach x
        period 1000 await
        print x
    | await
    print 'done'

Loop Parameters and Variables

Loop parameters, and variables created inside the loop body are local to a single step of the loop. Assigning a new value to a parameter/variable will not affect its value at the next step, nor will it affect the behavior of the loop.


Using yield and yieldFrom

If yield or yieldFrom is used in the body of an each, do, asyncEach or asyncDo loop, the operator returns a generator (or asynchronous generator):

g = 5 do i (yield i)   // generator
g array                // [0, 1, 2, 3, 4]

@ 5 6 7 each x
    yield x
    yieldFrom 'ab'
| array                // [5, 'a', 'b', 6, 'a', 'b', 7, 'a', 'b']

yield and yieldFrom turn a loop into a generator function (which is automatically called to give the returned generator) so the loop will have its own this, arguments and super.

Exceptions


throw

Use the throw operator to throw an exception:

\Error 'something went wrong' throw

The exception need not be an Error object, it can be anything:

@ 5 6 7 throw

try, asyncTry

The single operand of try is the body. The code in the body is executed; if it throws, try immediately returns whatever was thrown:

// prints 1, returns the Error object (2 is not printed)
try
    print 1
    \Error 'something went wrong' throw
    print 2

If the body of try does not throw, try returns undefined.

asyncTry is the asynchronous version of try. asyncTry returns a promise that resolves to whatever was thrown, or undefined if nothing was thrown. await can be used in the body of asyncTry — see catch for an example.


catch, asyncCatch

catch takes three operands:

The body is only executed if the try expression is truthy:

// prints 'something went wrong'
try
    \Error 'something went wrong' throw
| catch err
    err,message print

catch returns undefined.

Here is an example of using catch with asyncTry:

// waits 1000 ms, prints 'something went wrong'
asyncScope
    asyncTry
        period 1000 await
        \Error 'something went wrong' throw
    | await catch err
        err,message print

asyncCatch is the asynchronous version of catch. asyncCatch returns a promise that resolves to undefined. await can be used in the body of asyncCatch.

Is


These operators return a boolean:

Operator Description
isArray is array?
isBigInt is bigint?
isBoolean is boolean?
isFinite is finite? (always false for non-number)
isFunction is function?
isInteger is integer? (always false for non-number)
isNaN is NaN? (always false for non-number)
isNull is null?
isNullish is null or undefined?
isNumber is number?
isString is string?
isSymbol is symbol?
isUndefined is undefined?
5 isNumber        // true
5 isInteger       // true

'5' isNumber      // false
'5' isInteger     // false

null isNullish    // true
false isNullish   // false

Elementwise


Elementwise operators take a single operand. If the operand is a non-string iterable, the operator acts on each element independently and returns the results as an array. Otherwise, the operator acts on the operand directly and returns the result:

4 sqrt        // 2
@ 9 16 sqrt   // [3, 4]

'ab' toUpperCase          // 'AB'
@ 'cd' 'ef' toUpperCase   // ['CD', 'EF']

Elementwise Math

Elementwise math operators use the corresponding methods of the Math object. The elementwise math operators are:

abs acos acosh asin asinh atan atanh cbrt ceil clz32 cos cosh exp expm1 floor fround log log10 log1p log2 round sign sin sinh sqrt tan tanh trunc


Elementwise Other

Operator Description
boolean convert to boolean
date convert to date
neg unary minus
not logical not
number convert to number
string convert to string
toLowerCase convert to lowercase string
toUpperCase convert to uppercase string
trim remove whitespace from both ends of string
trimEnd remove whitespace from end of string
trimStart remove whitespace from start of string

toLowerCase, toUpperCase, trim, trimEnd and trimStart convert values to strings.

Conditional


?

The ? operator evaluates a test; if the test is truthy, the second operand is evaluated and returned, otherwise the third operand is evaluated and returned. If the third operand is omitted and the test is falsy, no code is evaluated (except for the test) and ? returns undefined.

true ? 5 10    // 5
false ? 5 10   // 10
4 > 5 ? 6      // undefined
4 < 5 ? 6      // 6

// returns 8
3 == 4 ?
    5
    6
|
    7
    8

if

The if operator has operands test, action, test, action, ... The tests are evaluated in sequence; when a test returns truthy, the corresponding action is evaluated and if returns the result. If no test is truthy, no action is evaluated and if returns undefined.

Often, we want the final test of an if to be truthy to ensure that some action is taken. In this case, we use the special constant else as the final test. This is equivalent to using true, but the code is easier to read:

exam = 60

if (exam >= 50) 'Pass'          // 'Pass'
if (exam >= 80) 'Distinction'   // undefined

// prints 'You got a Pass'
'You got a ' +
    if
    | (exam >= 80) 'Distinction'
    | (exam >= 50) 'Pass'
    | else         'Fail'

// prints 'Good', returns 'Pass'
if (exam >= 80)
    'Great' print
    'Distinction'
| (exam >= 50)
    'Good' print
    'Pass'
| else
    'Try again' print
    'Fail'

else is equivalent to a literal true. For example, else == true is true, and else string is 'true'.

Ranges


to

to returns a generator that represents a sequence of equally spaced numbers. to takes three operands: start, end and step. step is 1 by default:

5 to 7           // generator (5 6 7)
1.5 to 10 3      // generator (1.5 4.5 7.5)
1 to (-2) (-1)   // generator (1 0 -1 -2)

r = 5 to 7   // generator (5 6 7)
r ~next      // {value: 5, done: false}
r ~next      // {value: 6, done: false}
r ~next      // {value: 7, done: false}
r ~next      // {value: undefined, done: true}

Generators are iterables, so can be used with many operators — see e.g. Loops, Reduce, Filter, Group, Order, Bin, Backticks and Destructuring. Some examples:

5 to 7 each a (print a)   // prints 5 6 7
5 to 7 array              // [5, 6, 7]
5 to 7 mean               // 6
5 to 7 `+ 10              // [15, 16, 17]
u v w @= 5 to 7           // u is 5, v is 6, w is 7
'abcdefg' at (0 to 6 2)   // 'aceg'

linSpace

As to, but the third operand of linSpace (which should be a non-negative integer) is the number of steps:

2 8 linSpace 4      // generator (2 4 6 8)
3 6 linSpace 3      // generator (3 4.5 6)
2 (-7) linSpace 4   // generator (2 -1 -4 -7) 

Due to floating-point arithmetic, the final element of a range created with linSpace may not be identical to the end operand.

Backticks


A backtick before an operator indicates that the first operand is an iterable. A backtick after an operator indicates that the second operand is an iterable:

x = @ 5 6 7
x `+ 10              // [15, 16, 17]       
10 +` x              // [15, 16, 17]
x map xi (xi + 10)   // [15, 16, 17] (without backtick)

y = @ 10 20 30
x `+` y                 // [15, 26, 37]  
x map xi i (xi + y;i)   // [15, 26, 37] (without backticks)

Backticks can be used with the following operators: +-*/%^&&||??<>><<<=>>===!=.

If an operator has any backticks, it must have exactly two operands — x + y z is valid code, but x `+` y z is not.

If any backticks are used with &&, || or ??, short-circuit evaluation is not used — both operands are always evaluated.

Backticks can be used with any iterables; the result is always an array:

r = 5 to 7     // generator (5 6 7)
s = @@ 4 6 8   // Set {4, 6, 8}
r `==` s       // [false, true, false]

When both operands are iterables, the returned array is the length of the shortest iterable:

@ 5 6 7 `+` (@ 10 20)   // [15, 26]

A backtick really means "treat this operand as an iterable". For example, a string is an iterable, but we may want to treat it as a single value:

'ab'  +  'X'    // 'abX'
'ab' `+  'X'    // ['aX', 'bX']
'ab'  +` 'X'    // ['abX']
'ab' `+` 'X'    // ['aX']

'ab'  +  'XY'   // 'abXY'
'ab' `+  'XY'   // ['aXY', 'bXY']
'ab'  +` 'XY'   // ['abX', 'abY']
'ab' `+` 'XY'   // ['aX', 'bY']

Reduce


Unless otherwise stated, reduction operators take an iterable and a callback function (default [a]). The callback is applied to each element of the iterable (the callback is passed the element, index and iterable) and the returned values are used to compute the result:

x = @ 4 5 6
x sum            // 15
x sum [a >< 5]   // 16 (>< is 'greatest')

When 'reducing' an iterable of objects/arrays, an inner property/index can be used instead of a callback function:

x = @ 
| (# u 5 v 10)
| (# u 6 v 20)
| (# u 5 v 30)   // array-of-objects

x sum [a,v]   // 60
x sum 'v'     // 60

count

Count the truthy values:

s = @@ 1 '1' true 0    // Set {1, "1", true, 0}
s count                // 3
s count [a isNumber]   // 2

every, some

every returns true if all values are truthy; some returns true if any value is truthy:

x = @ 5 null 'abc'
x every            // false
x some             // true
x some [a == 10]   // false

m = ##
| apple 'green'
| banana 'yellow'
| cherry 'red'   // Map {'apple' => 'green', ...}

// use values method of map
m ~values every [a == 'green']   // false 
m ~values some  [a == 'green']   // true

If the iterable is empty, every returns true, some returns false.


find, findIndex

find returns the element corresponding to the first truthy value. findIndex is like find, but returns the index of the element:

x = @ 3 7 2 9
x find [a > 5]        // 7
x findIndex [a > 5]   // 1

x = @
| (# u 5)
| (# u 6 v 10)
| (# u 7 v 30)   // array-of-objects

x find 'v'              // {u: 6, v: 10}
x find [a,u == 7]       // {u: 7, v: 30}
x findIndex [a,u > 5]   // 1

If there are no truthy values, find returns undefined, findIndex returns -1.


min, max, minIndex, maxIndex

min/max returns the element corresponding to the minimum/maximum value:

@ 7 3 2 5 min   // 2

x = @ (@ 2 10) (@ 7 20) (@ 5 30)   // array-of-arrays
x max 0                            // [7, 20]

If multiple elements correspond to the minimum/maximum value, the first such element is returned.

minIndex and maxIndex are like min and max respectively, but return the index corresponding to the minimum/maximum value:

@ 3 9 5 maxIndex   // 1

Values are treated as numbers. If any value converts to NaN or if the iterable is empty, min and max return undefined, minIndex and maxIndex return -1.


sum, mean, variance, variancePop, deviation, deviationPop

Sum, mean, variance and standard deviation:

x = @ 4 6 8
x sum         // 18
x mean        // 6
x variance    // 4
x deviation   // 2

x = @
| (# u 5 v 10)
| (# u 6 v 20)
| (# u 7 v 30)   // array-of-objects
x sum 'v'        // 60

variance and deviation compute the sample variance and sample standard deviation. Use variancePop and deviationPop for the population variance and population standard deviation.

Values are treated as numbers. If any value converts to NaN, these operators return NaN.

sum returns 0 if the iterable is empty, mean returns NaN. The variance and standard deviation operators return NaN if the iterable has less than 2 elements.


sumCumu

sumCumu is like sum, but returns an array of cumulative sums rather than just the result:

x = @ 3 8 2 10
x sum       // 23
x sumCumu   // [3, 11, 13, 23]

x = @
| (# u 5 v 10)
| (# u 6 v 20)
| (# u 7 v 30)     // array-of-objects
x sumCumu 'v'      // [10, 30, 60]

sumCumu is not a reduction operator, but is listed here due to its similarity to sum.


correlation, covariance, covariancePop

Correlation and covariance. These operators take two callbacks (both are passed the element, index and iterable). As with other reduction operators, an inner property/index can be used instead of a callback:

x = @
| (# u 5 v 8)
| (# u 6 v 9)
| (# u 7 v 12)   // array-of-objects

x correlation [a,u] [a,v]   // 0.96
x correlation 'u' 'v'       // 0.96

If the data is in two separate iterables, refer to the second iterable explicitly in the second callback and use the index to get the element:

u = @ 5 6 7
v = @ 8 9 12

u correlation [a] [v;b]   // 0.96

correlation computes the Pearson correlation coefficient. To compute Spearman's rank correlation coefficient, use rank then correlation:

u = @ 3 4 5
v = @ 3 9 5
ru = rank u   // [0, 1, 2]
rv = rank v   // [0, 2, 1]

u correlation [a] [v;b]     // 0.33
ru correlation [a] [rv;b]   // 0.5

covariance computes the sample covariance. Use covariancePop for the population covariance.

Values are treated as numbers. If any value converts to NaN or if the iterable has less than 2 elements, these operators return NaN.


median, quantile

median returns the median:

@ 4 2 7 median      // 4
@ 4 2 7 10 median   // 5.5

y = @ 
| (# u 5 v 25)
| (# u 6 v 10)
| (# u 7 v 30)    // array-of-objects
y median 'v'      // 25

If the values are already sorted, use a third truthy operand with median to skip the sorting step.

quantile is like median, but takes an extra operand — a probability bounded at 0 and 1 — after the iterable:

x = @ 4 2 7 10 
x quantile 0         // 2
x quantile 1         // 10
x quantile 0.5       // 5.5 (the median)
x quantile (2 / 3)   // 7

y = @ 
| (# u 5 v 25)
| (# u 6 v 10)
| (# u 7 v 30)        // array-of-objects
y quantile 0.25 'v'   // 17.5

median and quantile use linear interpolation. Values (and the probability operand of quantile) are treated as numbers. If any of the numbers are NaN or if the iterable is empty, median and quantile return NaN.


reduce

The reduce operator takes an iterable, a 'reducer function' and an initial result. The reducer function is called once for each element of the iterable; it is passed the 'current result', the element, the index and the iterable:

@ 5 6 7 reduce [a + b] 0   // 18

The initial result is required — in contrast to the reduce array method where the initial result is optional.

Filter


filter takes an iterable and a callback function (default [a]). The callback is applied to each element of the iterable (it is passed the element, index and iterable); if the result is truthy, the element is included in the array returned by filter:

@ true false null 5 filter   // [true, 5]
@ 3 7 4 9 filter [a > 5]     // [7, 9]

x = @
| (# u 5 v 10)
| (# u 6 v 20)
| (# u 5 v 30)   // array-of-objects

x filter [a,u == 5]   // [{u: 5, v: 10}, {u: 5, v: 30}]

// filterIndex
x filterIndex [a,u == 5]   // [0, 2]

As shown in the final example, filterIndex can be used to get the original indices of the filtered elements.

When filtering an iterable of objects/arrays, an inner property/index can be used instead of a callback function:

x = @ 
| (# u 5 v 10)
| (# u 6)
| (# u 5 v 30)   // array-of-objects

x filter [a,v]   // [{u: 5, v: 10}, {u: 5, v: 30}]
x filter 'v'     // [{u: 5, v: 10}, {u: 5, v: 30}]

Group


group takes an iterable and a callback function (default [a]). The callback is applied to each element of the iterable (it is passed the element, index and iterable); the results are used to group the elements. When grouping an iterable of objects/arrays, an inner property/index can be used instead of a callback function — for example, 'v' can be used instead of the callback [a,v].

group returns a map with arrays for values:

x = @ 
| (# u 5 v 10)
| (# u 6 v 20)
| (# u 5 v 30)   // array-of-objects
  
x group 'u'   // Map {
              //   5 => [{u: 5, v: 10}, {u: 5, v: 30}]
              //   6 => [{u: 6, v: 20}]
              // }

// second callback
x group 'u' [a mean 'v']   // Map {5 => 20, 6 => 20}

As shown in the example, group can take a second callback. This is applied to each group (it is passed a value and key of the map at each step, as well as the index) and the results are used as the values of the returned map.

groupCount gets the size of each group:

@ 5 2 5 5 7 2 groupCount   // Map {5 => 3, 2 => 2, 7 => 1}

x = @ 
| (# u 5 v 10)
| (# u 6 v 20)
| (# u 5 v 30)   // array-of-objects

x groupCount 'u'           // Map {5 => 2, 6 => 1}
x groupCount 'u' [a > 1]   // Map {5 => true, 6 => false}

Order


order takes an iterable and a 'comparison function' (see the sort array method), and returns a new array containing the sorted elements of the iterable. The default comparison function is [a - b] which sorts (finite) numbers or dates in ascending order.

orderIndex and rank behave like order except that:

x = @ 5 9 2
x order        // [2, 5, 9]
x orderIndex   // [2, 0, 1] 
x rank         // [1, 2, 0]

y = @ 
| (# u 5 v 20)
| (# u 6 v 10)
| (# u 7 v 30)   // array-of-objects

// sort on property v, descending
f = [b,v - a,v]
y order f        // [{u: 7, v: 30}, {u: 5, v: 20}, {u: 6, v: 10}]
y orderIndex f   // [2, 0, 1]
y rank f         // [1, 2, 0]

// tied values
@ 2 3 4 3 rank   // [0, 1, 3, 1]

For convenience, the comparison function can be replaced with 'asc' (ascending) or 'desc' (descending):

x = @ 5 9 2
x order 'desc'        // [9, 5, 2]
x orderIndex 'desc'   // [1, 0, 2] 
x rank 'desc'         // [1, 0, 2]

To order an iterable of objects/arrays on an inner property/index, an additional operand can be used after 'asc' or 'desc' that specifies the property/index:

y = @ 
| (# u 5 v 20)
| (# u 6 v 10)
| (# u 7 v 30)   // array-of-objects

// sort on property v, descending
y order 'desc' 'v'        // [{u: 7, v: 30}, {u: 5, v: 20}, {u: 6, v: 10}]
y orderIndex 'desc' 'v'   // [2, 0, 1]
y rank 'desc' 'v'         // [1, 2, 0]

'asc' or 'desc' should only be used to sort (finite) numbers or dates.

Bin


bin takes an iterable to bin, an iterable of bin limits and a 'comparison function' (see order and the sort array method). The default comparison function is [a - b] which is suitable for binning (finite) numbers or dates. bin returns a map with arrays for values:

@ 5 9 12 7 bin (@ 10 20)   // Map {10 => [5, 9, 7], 20 => [12]}

x = @ 
| (@ 23 0) (@ 7 1) (@ 32 2) 
| (@ 38 3) (@ 3 4) (@ 34 5)   // array-of-arrays

limits = @ 10 20 30 40

x bin limits [a,0 - b]   // Map {
                         //   10 => [[7, 1], [3, 4]],
                         //   20 => [],
                         //   30 => [[23, 0]],
                         //   40 => [[32, 2], [38, 3], [34, 5]]
                         // }

// second callback
x bin limits [a,0 - b] [a pick 1]   // Map {
                                    //   10 => [1, 4]
                                    //   20 => []
                                    //   30 => [0]
                                    //   40 => [2, 3, 5]
                                    // }

// binCount
@ 5 9 12 7 binCount (@ 10 20)   // Map {10 => 3, 20 => 1}
x binCount limits [a,0 - b]     // Map {10 => 2, 20 => 0, 30 => 1, 40 => 3}
x binCount limits [a,0 - b] [a > 1]   // Map {
                                      //   10 => true,
                                      //   20 => false,
                                      //   30 => false,
                                      //   40 => true
                                      // }

As shown in the example, bin can take a second callback (the example uses pick). This is applied to each bin (it is passed a value and key of the map at each step, as well as the index) and the results are used as the values of the returned map.

binCount gets the number of elements in each bin — see the final examples above. While we could use bin with a second callback for this, binCount is more efficient and is easier to read.

An element is assigned to a bin if the comparison function (which is passed the element and the bin limit) is less than or equal to 0. Using the default comparison function [a - b] is equivalent to specifying inclusive upper bin limits — use [b - a] to specify lower bin limits. Elements that are not assigned to any bin do not appear in the map returned by bin (or contribute to any value in binCount).

bin and binCount assume that the bin limits are already sorted (with respect to the comparison function). If this is not the case, bin and binCount will give unexpected results.

The elements inside each bin are not sorted — they appear in the same order as in the original iterable.

Joins


innerJoin, leftJoin, rightJoin, outerJoin

Database-style joins. The first two operands are iterables; the elements of each iterable should be objects or arrays. The third operand specifies the join criteria which can be a:

The optional fourth operand is the maximum number of results to yield.

Join operators return a generator that yields arrays; each array contains a pair of elements from the iterables. The operators differ on how they treat unmatched elements:

p = @ 
| (# u 1 v 4)
| (# u 2 v 5)
| (# u 3 v 6)        // array-of-objects

q = @
| (# u 0 w 7 x 10)
| (# u 3 w 8 x 20)
| (# u 1 w 9 x 30)   // array-of-objects

r = @
| (# x 10 y 11)
| (# x 30 y 31)      // array-of-objects

// these are equivalent
p outerJoin q [a,u == b,u]   // generator
p outerJoin q (@ 'u' 'u')    // generator
p outerJoin q 'u'            // generator

p outerJoin q 'u' array
  // [
  //   [{u: 1, v: 4}, {u: 1, w: 9, x: 30}],
  //   [{u: 3, v: 6}, {u: 3, w: 8, x: 20}],
  //   [{u: 2, v: 5}, null               ],
  //   [null,         {u: 0, w: 7, x: 10}]
  // ]

// chained join - this is easier with crush (see below)
p outerJoin q 'u' outerJoin r [a,1 ?: 'x' == b,x] array
  // [
  //   [{u: 1, v: 4}, {u: 1, w: 9, x: 30}, {x: 30, y: 31}], 
  //   [null,         {u: 0, w: 7, x: 10}, {x: 10, y: 11}], 
  //   [{u: 3, v: 6}, {u: 3, w: 8, x: 20}, null          ], 
  //   [{u: 2, v: 5}, null,                null          ]
  // ]

Unless speed or memory usage is critical, we typically crush the generator returned by a join operator — the resulting array-of-objects is easier to work with.

In the "chained join" example above, the depth of nesting does not increase — elements have the form [{...}, {...}, {...}], not [[{...}, {...}], {...}]. When chaining joins, the intermediate join must be the returned generator or crushed to get this behavior.


crush

Flatten a join into an array-of-objects. The first operand is the join; additional operands are objects that specify which properties to keep and property name prefixes:

p = @ 
| (# u 1 v 4)
| (# u 2 v 5)
| (# u 3 v 6)        // array-of-objects

q = @
| (# u 0 w 7 x 10)
| (# u 3 w 8 x 20)
| (# u 1 w 9 x 30)   // array-of-objects

r = @
| (# x 10 y 11)
| (# x 30 y 31)      // array-of-objects

p outerJoin q 'u' crush
  // [
  //   {u: 1, v: 4, w: 9, x: 30},
  //   {u: 3, v: 6, w: 8, x: 20},
  //   {u: 2, v: 5             },
  //   {u: 0,       w: 7, x: 10}
  // ]                 

p outerJoin q 'u'
| crush (# prefix 'p_') (# prefix 'q_' keep (@ 'u' 'x'))
  // [
  //   {p_u: 1, p_v: 4, q_u: 1, q_x: 30},
  //   {p_u: 3, p_v: 6, q_u: 3, q_x: 20},
  //   {p_u: 2, p_v: 5                 },
  //   {                q_u: 0, q_x: 10}
  // ]

// chained join
p outerJoin q 'u' crush outerJoin r 'x' crush
  // [
  //   {u: 1, v: 4, w: 9, x: 30, y: 31}, 
  //   {u: 0,       w: 7, x: 10, y: 11}, 
  //   {u: 3, v: 6, w: 8, x: 20       }, 
  //   {u: 2, v: 5                    }
  // ]

Notes:


crossJoin

All pairs of elements from two iterables. crossJoin is equivalent to innerJoin with a callback function that always returns true:

p = @ (@ 1 2) (@ 3 4)   // array-of-arrays
q = @ (@ 5 6) (@ 7 8)   // array-of-arrays

p crossJoin q array
  // [
  //   [[1, 2], [5, 6]], 
  //   [[1, 2], [7, 8]], 
  //   [[3, 4], [5, 6]], 
  //   [[3, 4], [7, 8]] 
  // ]

p crossJoin q crush (# prefix 'p_') (# prefix 'q_')
  // [
  //   {p_0: 1, p_1: 2, q_0: 5, q_1: 6}, 
  //   {p_0: 1, p_1: 2, q_0: 7, q_1: 8}, 
  //   {p_0: 3, p_1: 4, q_0: 5, q_1: 6}, 
  //   {p_0: 3, p_1: 4, q_0: 7, q_1: 8} 
  // ]

semiJoin, antiJoin

These operators take the same operands as innerJoin.

semiJoin yields elements of the first iterable that match an element of the second iterable. antiJoin yields elements of the first iterable that do not match any elements of the second iterable.

While semiJoin and antiJoin only yield elements from the first iterable, they behave like other join operators for consistency: they return a generator that yields arrays (each array containing a single object/array in this case):

p = @ 
| (# u 1 v 4)
| (# u 2 v 5)
| (# u 3 v 6)        // array-of-objects

q = @
| (# u 0 w 7 x 10)
| (# u 3 w 8 x 20)
| (# u 1 w 9 x 30)   // array-of-objects

p semiJoin q 'u' array   // [[{u: 1, v: 4}], [{u: 3, v: 6}]]
p semiJoin q 'u' crush   // [{u: 1, v: 4}, {u: 3, v: 6}]
p antiJoin q 'u' crush   // [{u: 2, v: 5}]

Random


These operators (except for shuffle) generate a number from a given distribution. An optional extra operand can be used to specify how many numbers to generate — in this case, the operator returns an array rather than a number.


random

Generate values from a uniform distribution with the given minimum (inclusive, default 0) and maximum (exclusive, default 1):

random           // value in [0, 1)
random 5 10      // value in [5, 10)
random 5 10 20   // array of 20 values in [5, 10)

randomInt

Generate values from a discrete uniform distribution with the given minimum (inclusive, default 0) and maximum (exclusive, default 2):

randomInt           // value in {0, 1}
randomInt 5 10      // value in {5, 6, 7, 8, 9}
randomInt 5 10 20   // array of 20 values in {5, 6, 7, 8, 9}

normal

Generate values from a normal distribution with the given mean (default 0) and standard deviation (default 1):

normal           // value from normal distbn: mean 0, sd 1
normal 5 10      // value from normal distbn: mean 5, sd 10
normal 5 10 20   // array of 20 values from normal distbn: mean 5, sd 10

logNormal

Generate values from a log-normal distribution. Equivalent to applying exp to result of normal:

logNormal   // is equivalent to:
normal exp

logNormal 5 3 20   // is equivalent to:
normal 5 3 20 exp

exponential

Generate values from an exponential distribution with the given rate:

exponential 5      // value from exponential distbn: rate 5
exponential 5 20   // array of 20 values from exponential distbn: rate 5

binomial

Generate values from a binomial distribution with the given number of trials and success probability (default 0.5):

binomial 5       // value from binomial distbn: trials 5, prob 0.5
binomial 5 0.3   // value from binomial distbn: trials 5, prob 0.3

// array of 20 values from binomial distbn: trials 5, prob 0.3
binomial 5 0.3 20

geometric

Generate values (positive integers) from a geometric distribution with the given success probability (default 0.5):

geometric          // value from geometric distbn: prob 0.5
geometric 0.3      // value from geometric distbn: prob 0.3
geometric 0.3 20   // array of 20 values from geometric distbn: prob 0.3

categorical

Generate values from a categorical distribution with the given ratio — the 'probabilities' should be non-negative, but need not sum to 1. The ratio must be an iterable:

// value in {0, 1}, probabilities: 3/4, 1/4
categorical (@ 3 1)      

// array of 20 values in {0, 1, 2}, probabilities: 1/6, 2/6, 3/6
1 to 3 categorical 20

shuffle

Shuffle the elements of an iterable and return them in a new array:

x = @ 5 7 9   // [5, 7, 9]
x shuffle     // possible result: [9, 5, 7]
x             // [5, 7, 9]

1 to 4 shuffle   // possible result: [2, 4, 1, 3]

Interpolate


interpolate linearly interpolates between its first two operands. The third operand is the interpolation parameter:

5 9 interpolate 0         // 5
5 9 interpolate 1         // 9
5 9 interpolate 0.5       // 7
5 9 interpolate (3 / 8)   // 6.5
5 9 interpolate 1.5       // 11
5 9 interpolate (-0.5)    // 3

20 10 interpolate 0.7     // 13

// interpolate multiple values
@ 0.2 0.6 0.9 map x (10 20 interpolate x)   // [12, 16, 19]

interpolate treats all operands as numbers.

Nested Data


See Reduce, Filter, Group, Order and Bin for other operators that can act directly on nested data.


pick

pick gets a property from each element of an iterable. The first operand of pick is the iterable; the second operand is the property name/index. pick returns an array:

// pick from an array-of-arrays
@ 
| (@ 4 5 6)
| (@ 7 8 9)
| pick 1   // [5, 8]

// pick from a set-of-objects
@@
| (# u 4)
| (# u 5 v 6)
| (# u 7)
| pick 'v'   // [undefined, 6, undefined]

// pick from an array-of-strings
@ 'abc' 'def' pick 0   // ['a', 'd']

mapAt

mapAt applies at to each element of an iterable. The first operand is the iterable; the second and third operands are used as the second and third operands of at:

// apply mapAt to an array-of-arrays
@
| (@ 4 5 6)
| (@ 7 8 9)
| mapAt (@ 2 0)
  // [
  //   [6, 4],
  //   [9, 7]
  // ]

// apply mapAt to an array-of-strings
@ 'abc' 'def' mapAt (@ 0 2)   // ['ac', 'df']

// set-of-objects
s = @@
| (# u 4 v 5)
| (# u 6 v 7 w 8)
| (# u 9 w 10)

s mapAt (@ 'u' 'w')
  // [
  //   {u: 4, w: undefined},
  //   {u: 6, w: 8},
  //   {u: 9, w: 10}
  // ]

s mapAt (@ 'u' 'w') 'array'
  // [
  //   [4, undefined],
  //   [6, 8],
  //   [9, 10]
  // ]

transpose

transpose takes an iterable-of-iterables (typically an array-of-arrays) and returns an array-of-arrays:

x = @ 
| (@ 5 6 7)
| (@ 10 20 30)   // [[5, 6, 7], [10, 20, 30]] 

x transpose      // [[5, 10], [6, 20], [7, 30]] 

The result of transpose can have empty elements and inner arrays of different lengths. Empty array elements in the original data have value undefined in the transpose:

@ 
| (@ 1 2)
| (new Array 2)  // [empty, empty]
| (@ 3 4 5)
| (@ 6)
| transpose
  // [
  //   [1, undefined, 3, 6],
  //   [2, undefined, 4],
  //   [empty, empty, 5]
  // ] 

arrObj

arrObj takes an object-of-iterables (typically an object-of-arrays) and returns an array-of-objects:

// object-of-arrays
data = #
| Name       (@ 'vw pickup' 'dodge rampage' 'ford ranger')
| Horsepower (@ 52 84 79)
| Origin     (@ 'Europe' 'USA' 'USA')

data arrObj
  // [
  //   {Name: 'vw pickup', Horsepower: 52, Origin: 'Europe'}
  //   {Name: 'dodge rampage', Horsepower: 84, Origin: 'USA'}
  //   {Name: 'ford ranger', Horsepower: 79, Origin: 'USA'}
  // ]
  
data arrObj (@ 'Horsepower' 'Origin')
  // [
  //   {Horsepower: 52, Origin: 'Europe'}
  //   {Horsepower: 84, Origin: 'USA'}
  //   {Horsepower: 79, Origin: 'USA'}
  // ]

As shown in the final example, arrObj can take a second operand (an iterable) that specifies the properties to include in the result. When this argument is omitted (or falsy), all properties are included.

The iterables in the original data can have different lengths:

#
| u (@ 5 6 7)
| v (@ 8 9)
| arrObj   // [
           //   {u: 5, v: 8},
           //   {u: 6, v: 9},
           //   {u: 7}
           // ]

Empty array elements in the original data will result in properties with value undefined in the result.


objArr

objArr takes an iterable-of-objects (typically an array-of-objects) and returns an object-of-arrays:

// array-of-objects
data = @
| (# Name 'vw pickup'     Horsepower 52 Origin 'Europe')
| (# Name 'dodge rampage' Horsepower 84 Origin 'USA')
| (# Name 'ford ranger'   Horsepower 79 Origin 'USA')

data objArr
  // {
  //   Name: ['vw pickup', 'dodge rampage', 'ford ranger'],
  //   Horsepower: [52, 84, 79],
  //   Origin: ['Europe', 'USA', 'USA']
  // }

data objArr (@ 'Horsepower' 'Origin')
  // {
  //   Horsepower: [52, 84, 79],
  //   Origin: ['Europe', 'USA', 'USA']
  // }

As shown in the final example, objArr can take a second operand (an iterable) that specifies the properties to include in the result. When this argument is omitted (or falsy), the returned object has an array for each unique property name in the original data.

When a second operand is used, the arrays in the result have no empty elements and have the same length as the orginal data. When a second operand is not used, the arrays of the result can have empty elements and different lengths:

data = @
| (# u 1 v 2)
| (# u 3 v 4 w 5)
| (# u 6)

data objArr (@ 'v' 'w')   // {
                          //   v: [2, 4, undefined],
                          //   w : [undefined, 5, undefined]
                          // }

data objArr   // {
              //   u: [1, 3, 6],
              //   v: [2, 4],
              //   w: [empty, 5]
              // }

Set Theory


Set theory operators take any number of iterables and return a new set. The order of elements is preserved, i.e. the order of elements in the result is determined by where the elements first appear in the operands.


difference

Elements of the first operand that do not appear in any of the other operands:

x = @ 3 7 2 9    // [3, 7, 2, 9]
s = @@ 9 7 5     // Set {9, 7, 5}
x difference s   // Set {3, 2}

intersection

Elements that appear in all operands:

x = @ 3 7 2 9      // [3, 7, 2, 9]
s = @@ 9 7 5       // Set {9, 7, 5}
x intersection s   // Set {7, 9}

union

Elements that appear in any operand:

x = @ 3 7 2 9   // [3, 7, 2, 9]
s = @@ 9 7 5    // Set {9, 7, 5}
x union s       // Set {3, 7, 2, 9, 5}

Import and Export


The following rules apply to import and export operators:

While not necessary, we typically use import and export operators at the start of a file:

import '/modules/a.js' f1 f2
import '/modules/b.js' g
export h1 h2

h1 = [a \f1 \g]
h2 = [a \f2 \g]

Do not assign to imports. Assignment operators that create local variables will create a new variable that masks the import. Assignment operators that update variables (e.g. +=) will throw an error since imports are constants.


export

Export variables:

export add subtract

add = [a + b]
subtract = [a - b]

export can only be used once per file.


import

Use import with one operand to import a module for side effects only:

import '/modules/do-something.js'

Use additional operands for named imports:

import '/modules/arithmetic.js' add subtract
// use add and subtract here

importAs

Rename imports. Operands are passed as importName newName pairs:

importAs '/modules/arithmetic.js'
| add plus
| subtract takeaway
// use plus and takeaway here

importAll

Import all of a module's exports as an object:

importAll '/modules/arithmetic.js' arith
// use arith,add and arith,subtract here

importDefault

Import default export.

Zap does not use default exports, but many JavaScript modules do:

importDefault '/modules/my-js-module.js' theDefault
// use theDefault here

load

Dynamically import a module.

load is not subject to the restrictions that apply to other import operators: load can be used anywhere and the module path/url can be any expression. import returns a promise that resolves to an object:

load '/modules/arithmetic.js' ~then 
    fun arith
        // use arith,add and arith,subtract here

Or using await:

asyncScope
    add subtract #= '/modules/arithmetic.js' load await
    // use add and subtract here

CommonJS Modules

CommonJS modules do not require dedicated operators or rules.

The following export examples are equivalent:

module,exports = #
| add [a + b]
| subtract [a - b]
add = [a + b]
subtract = [a - b]
exports attach add subtract
exports
| chg 'add' [a + b]
| chg 'subtract' [a - b]

Use the require function to load a module:

arith = 'arithmetic.js' \require
// use arith,add and arith,subtract here

Or with destructuring:

add subtract #= 'arithmetic.js' \require
// use add and subtract here

print

Print operand and return it:

print 5            // prints and returns 5
# u 5 v 6 print    // prints and returns {u: 5, v: 6}
x = 5 print + 10   // prints 5, returns 15
x                  // 15

print uses console,log.


debugger

Invoke debugger (if it exists):

debugger

period

Create a promise that resolves after the given number of milliseconds:

500 period   // promise that resolves (to undefined) after 500 ms

If used with two operands, period resolves to the value of the second operand:

// waits 1000 ms, prints 'abc'
asyncScope
    1000 period 'abc' await print

period uses setTimeout.

DOM


The following terms are used in this section to describe the elements associated with an operand:

first element:

all elements:

The DOM operators are loosely based on d3-selection. In particular, data can be encoded as (and is automatically attached to) elements. Zap uses the encode operator to map data to elements rather than D3's enter-update-exit approach. Currently, encode cannot map data to existing elements; this functionality will be added in a future version.


select

select takes a CSS selector string and returns the first matching element — or null if there are no matching elements. When used with one operand, select searches the document. If two operands are used, select searches the descendants of the first element of the first operand:

select 'p'               // first <p> in the document
select '.news p'         // first <p> in an element with class 'news' 
elmA select 'p'          // first <p> in element elmA
@ elmA elmB select 'p'   // first <p> in element elmA 
'.news' select 'p'       // first <p> in first element with class 'news'

Since many DOM operators can take a CSS selector string, select and selectAll are not used as frequently as might be expected.


selectAll

As select, but selectAll returns all of the matching elements as an array — an empty array if there are no matching elements.


create, createSVG

Create HTML/SVG elements. The first operand is a tag name:

create 'p'         // <p>
createSVG 'rect'   // <rect>

The second operand specifies the number of elements to create. When a second operand is used, an array is returned:

create 'p' 2         // [<p>, <p>]
createSVG 'rect' 2   // [<rect>, <rect>]

Typically, we use convenience operators instead of create/createSVG. For example:

$p        // <p>
$rect 2   // [<rect>, <rect>]

fragment

fragment takes no operands and returns a new document fragment.


encode, encodeSVG

encode takes an iterable (the 'data') and an HTML tag name. For each datum, an HTML element of the given tag name is created and the element's __data__ property is set to the datum. The new elements are returned as an array:

p = @ 5 6 7 encode 'p'   // [<p>, <p>, <p>]
p,1,__data__             // 6

Various DOM operators such as insert, into, insertEach, attr, prop, style, html, text, on and off use the __data__ property of elements — it is rare to use the __data__ property explicitly.

The optional third operand of encode specifies whether to set the __data__ property of the new elements. This operand defaults to true.

Use encodeSVG to encode data as SVG elements rather than HTML:

o = @ (# u 5 v 6) (# u 7 v 8)
r = encodeSVG o 'rect'   // [<rect>, <rect>]
r,1,__data__             // {u: 7, v: 8}

insert

Insert all elements of the second operand into the 'target': the first element of the first operand. Alternatively, the second operand can be a callback function that returns an element or an iterable of elements. The callback is passed the target's data (see encode) and the callback's this is set to the target.

insert returns the inserted element(s):

// insert 3 <p> into elm, returns [<p>, <p>, <p>]
elm insert (3 $p)

// insert 1 <p> into elm, returns [<p>]
elm insert (1 $p)

// insert 1 <p> into elm, returns <p>
elm insert ($p)

// insert all <p> in document into elm, returns all <p> as an array
elm insert 'p'

As shown in the final example, insert can be used to move elements that are already in the document.

The optional third operand of insert specifies where to insert elements into the target. The third operand must be a callback function; it is passed the target's data and the callback's this is set to the target. Elements are inserted before the first element returned by the callback. If the callback returns null or is omitted, elements are inserted at the end of the target.


into

into is the same as insert, but with the first two operands swapped. The only other difference is that into returns the target element rather than the inserted elements:

2 $p into ($div)   // <div><p></p><p></p></div>

insertEach

insertEach is similar to using insert on each element of the first operand (which can be a CSS selector string, an iterable of elements or an element). However, the second operand of insertEach must be a callback function. The callback is passed the 'current index' as its second argument and the results of the callback are collected in an array and returned (each entry of the array is an element or an iterable of elements):

divs = @ 5 6 7 encode 'div'   // [<div>, <div>, <div>]

// insert a <p> into each <div>
ps = divs insertEach
    fun data index
        $p text 
            + this,tagName' 'data' 'index
            
ps        // [<p>, <p>, <p>]
ps text   // ['DIV 5 0', 'DIV 6 1' 'DIV 7 2']

The following example uses insert and insertEach to represent an array-of-arrays as an HTML table:

x = @ (@ 4 5 6) (@ 7 8 9)   // [[4, 5, 6], [7, 8, 9]]

'body' insert ($table)
| insert [encode x 'tr']
| insertEach [a encode 'td' text [a]]

attr, prop, style

Get or set an attribute/property/style of all elements of the first operand.

The second operand is the attribute/property/style name.

When used with two operands, these operators are getters. If the first operand is an iterable, the values are returned as an array.

When used with three operands, these operators are setters. If the third operand is a function, it is called for each element. The function is passed the element's __data__ property (see encode) and the current index; the function's this is set to the element. The value returned by the function is used as the new attribute/property/style value. If the third operand is not a function, it is used as the new attribute/property/style value for each element. Setters return the modified element(s).

divs = @ 'red' 'blue' encode 'div'
| style 'background-color' [a]
| style 'height' '20px'   // [<div>, <div>]

// style gets computed style - elements must be in the document
divs into 'body'

divs style 'background-color'   // ["rgb(255, 0, 0)", "rgb(0, 0, 255)"]
divs style 'height'             // ['20px', '20px']
divs,0 style 'height'           // '20px'

'div' style 'width' '40px'   // set width of all divs in document
'div' style 'width'          // ['40px', '40px'] (if no preexisting divs)

A callback function can be used with any elements; they need not have their own data:

colors = @ 'red' 'green' 'blue'

// create 3 <p>, give each a different color
3 $p style 'color' [colors;b]

style can be used to get/set CSS variables:

// use 'html' or document,documentElement to set globals
// (i.e. variables that would be set under :root in CSS)
'html' style '--size' '24px'

// scope --color variable to element with id 'apple'
'#apple' style '--color' 'green'

html, text

html and text are shorthand getters/setters for the innerHTML and textContent properties respectively. Specifically, they are equivalent to using prop:


remove

Remove all elements of the operand from the document.

Returns the element(s).


lower, raise

Move all elements of the operand (in order) to be the first child (lower) or the last child (raise) of their parents.

Returns the element(s).


addClass, removeClass

Add/remove classes from all elements of the first operand. The second operand is a string of space-separated class names.

Returns the element(s).


toggleClass

Add or remove a class from all elements of the first operand: if an element has the class, it is removed; otherwise it is added. The second operand is the class name.

Returns the element(s).


removeAttr, removeStyle

Remove an attribute/style from all elements of the first operand. The second operand is the name of the attribute/style to remove.

Returns the element(s).


hasAttr, hasClass

For all elements of the first operand, indicate if each element has the attribute/class specified by the second operand.

If the first operand is an iterable, these operators return an array of booleans. Otherwise, they return a boolean.


on, off

Add/remove event listeners from all elements of the first operand. The second operand is a string of space-separated event types. The third operand is the event handler — a function. The handler is passed the element's __data__ property (see encode) and the event. The handler's this is set to the element:

@ 5 6 7 encode 'p'
| text 'show data'
| on 'click' [this text a]    // [<p>, <p>, <p>] 

The optional fourth operand is an options object or a boolean indicating whether to use capture — see EventTarget,addEventListener for details. The fourth operand defaults to false.

The behavior of on and off differs slightly from that of the native methods EventTarget,addEventListener and EventTarget,removeEventListener:

on and off return the element(s).


sketch

sketch creates a <canvas> element and a drawing context. sketch can be used with no operands or with an options object. The options are:

The scale option is ignored if context is not '2d'.

Any additional options passed to sketch are passed on to getContext when creating the context.

The canvas and context are returned as an array:

canvas ctx @= sketch

ctx,fillStyle = 'green'
ctx ~fillRect 50 20 200 100

Convenience Operators for Creating Elements

The following are convenience operators for creating elements:

HTML:

$a $abbr $address $area $article $aside $audio $b $base $bdi $bdo $blockquote $body $br $button $canvas $caption $cite $code $col $colgroup $data $datalist $dd $del $details $dfn $dialog $div $dl $dt $em $embed $fieldset $figcaption $figure $footer $form $h1 $h2 $h3 $h4 $h5 $h6 $head $header $hgroup $hr $i $iframe $img $input $ins $kbd $label $legend $li $link $main $map $mark $menu $meta $meter $nav $noscript $object $ol $optgroup $option $output $p $param $picture $pre $progress $q $rb $rp $rt $rtc $ruby $s $samp $script $section $select $slot $small $source $span $strong $style $sub $summary $sup $table $tbody $td $template $textarea $tfoot $th $thead $time $title $tr $track $u $ul $var $video $wbr

SVG:

$animate $animateMotion $animateTransform $circle $clipPath $defs $desc $discard $ellipse $feBlend $feColorMatrix $feComponentTransfer $feComposite $feConvolveMatrix $feDiffuseLighting $feDisplacementMap $feDistantLight $feDropShadow $feFlood $feFuncA $feFuncB $feFuncG $feFuncR $feGaussianBlur $feImage $feMerge $feMergeNode $feMorphology $feOffset $fePointLight $feSpecularLighting $feSpotLight $feTile $feTurbulence $filter $foreignObject $g $hatch $hatchpath $image $line $linearGradient $marker $mask $metadata $mpath $path $pattern $polygon $polyline $radialGradient $rect $set $solidcolor $stop $svg $switch $symbol $text $textPath $tspan $use $view

Using these operators is equivalent to using create/createSVG. For example:

Where HTML and SVG elements have the same name (e.g. a, script, style and title), there are convenience operators for the HTML elements, but not for the SVG elements.