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'
npm install --save '@gjmcn/zap'
CommonJS:
const zap = require('@gjmcn/zap');
ES module:
import zap from '@gjmcn/zap';
UMD: dist/zap.umd.js. This can be loaded with a <script> tag and will create a global zap function.
Zap can be loaded from a CDN. Examples:
Skypack, using import:
import zap from 'https://cdn.skypack.dev/@gjmcn/zap';
jsDelivr, using a <script>:
<script src="https://cdn.jsdelivr.net/npm/@gjmcn/zap"></script>
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).
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:
--all and --compile overwrite existing .js files.
--map overwrites existing .js.map files. If --all or --compile are used without --map, existing .js.map files (that correspond to the .za files being compiled) are deleted.
--all is recursive — .za files in subdirectories are compiled.
--eval, --help, --interactive and --version cannot be combined with other options.
--map is the only option that can be combined with --all.
--compile, --map, --nodes, --print, --tokens and --proc can be combined.
All file actions are synchronous.
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.
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 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.
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.
Additional syntax rules apply to some operators:
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
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)
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:
Since , autoquotes the property name, x,1e2 will get property '1e2' whereas x;1e2 will get property 100.
Do not include + or - in the index. These will not be recognized as part of the property name, so x,1e+2 is effectively x,1e + 2, and x;1e+2 will throw an error since 1e is neither a number nor an identifier.
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)
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:
They dictate the visibility of variables — see Local Variables.
await can only be used in the body of one of the asynchronous operators listed above.
yield and yieldFrom can only be used in the body of fun, scope, as, each and do (and their asynchronous counterparts).
stop (exit a loop) can only be used in the body of each, map and do loops (and their asynchronous counterparts).
| 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.
await delete in instanceof new typeof yield yieldFrom void
These operators behave the same as their JavaScript counterparts except that:
delete takes two operands: an object and a property name:o = # u 5 v 6
o delete 'u' // true
o // {v: 6}
new are a constructor and arguments to pass to the constructor (the constructor is not called explicitly):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
yield* is called yieldFrom in Zap.as catch class do each extends fun map proc scope try
asyncAs asyncCatch asyncDo asyncEach asyncFun asyncMap asyncProc asyncScope asyncTry
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
boolean date neg not number string toLowerCase toUpperCase trim trimEnd trimStart
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
assign at attach chg getter setter
export import importAll importAs importDefault load
isArray isBigInt isBoolean isFinite isFunction isInteger isNaN isNull isNullish isNumber isString isSymbol isUndefined
antiJoin crossJoin crush innerJoin leftJoin outerJoin rightJoin semiJoin
arrObj mapAt objArr pick transpose
array apply call debugger empties if linSpace ones period print throw to zeros interpolate
binomial categorical exponential geometric logNormal normal random randomInt shuffle
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
$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
$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
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
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.
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]]]
// ]
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'}
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)
\ 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]'
<~ 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 applyCall 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
callandapplyare like the function methodscallandapplyrespectively, but the Zap operators do not specify the value ofthis. Also, theapplyoperator can take any iterable, whereas theapplymethod is restricted to array-like objects.
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
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 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
atat 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'
getterUse 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.
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'}
chgchg 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
chgis equivalent to using assignment; it is not equivalent to usingObject,defineProperty.
attachattach 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'}
assignThe 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.
setterUse 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.
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, asyncFunCreate 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, asyncProcAs fun and asyncFun, but proc and asyncProc create a procedure.
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.
rest ParameterIf 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]
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
ops ParameterA 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 variableopsand gets the required property.opswill typically be a parameter of the current function, but need not be.
<-uses short-circuit evaluation: if the relevant property ofopsis neithernullnorundefined, the right-hand side of<-is not evaluated.
scope, asyncScopeUsing 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, asyncAsLike 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
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
yieldoryieldFromare used inscope,asyncScope,asorasyncAs, the operator will behave like a regular function rather than a procedure — in particular, the body will have its ownthis.
classCreate 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:
this: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'
extendsCreate a subclass.
extends is like class except that:
The first operand of extends is the parent class.
If the constructor body is empty, the subclass is given a default constructor that passes its arguments to the parent constructor.
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]
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.
eachLoop 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
mapAs 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
doBasic 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, asyncDoAsynchronous versions of each, map and do.
These operators return a promise that resolves to:
asyncEach: the iterableasyncMap: a new arrayasyncDo: undefinedawait 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 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.
yield and yieldFromIf 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']
yieldandyieldFromturn a loop into a generator function (which is automatically called to give the returned generator) so the loop will have its ownthis,argumentsandsuper.
throwUse 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, asyncTryThe 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, asyncCatchcatch takes three operands:
tryThe 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.
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 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 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
| 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,trimEndandtrimStartconvert values to strings.
?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
ifThe 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'
elseis equivalent to a literaltrue. For example,else == trueistrue, andelse stringis'true'.
toto 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'
linSpaceAs 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
linSpacemay not be identical to the end operand.
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 zis valid code, butx `+` y zis 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']
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
countCount the truthy values:
s = @@ 1 '1' true 0 // Set {1, "1", true, 0}
s count // 3
s count [a isNumber] // 2
every, someevery 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, findIndexfind 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, maxIndexmin/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, deviationPopSum, 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.
sumCumusumCumu 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]
sumCumuis not a reduction operator, but is listed here due to its similarity tosum.
correlation, covariance, covariancePopCorrelation 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, quantilemedian 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.
reduceThe 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
reducearray method where the initial result is optional.
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 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 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:
orderIndex returns the original indices of the sorted elements.
rank returns the rank of each element.
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 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).
binandbinCountassume that the bin limits are already sorted (with respect to the comparison function). If this is not the case,binandbinCountwill give unexpected results.
The elements inside each bin are not sorted — they appear in the same order as in the original iterable.
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:
function: called on each pair of elements of the iterables; if the function returns a truthy value, the pair of elements is included in the join.
non-string iterable: the inner property names/indices to join on — the first element corresponds to the first iterable, the second element to the second iterable.
string/number: the inner property name/index to join on — use this when the name/index is the same for both iterables.
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:
innerJoin: unmatched elements are not included.
leftJoin: unmatched elements of the first iterable are included.
rightJoin: unmatched elements of the second iterable are included.
outerJoin: unmatched elements of both iterables are included.
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.
crushFlatten 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:
Any number of additional operands can be used with crush — since joins can be chained, each 'row' of results can have more than two objects.
If prefixes are not used and objects in the same row share a property name, the result will use the value from the last object in the row that has the property.
Use a falsy value to skip an optional operand, e.g. someJoin crush null (# prefix '_').
Do not use the new prefixes when listing property names with keep.
If keep is not used, all enumerable own properties of the corresponding objects are included in the result.
crush can be applied to any iterable-of-iterables. Elements of the inner iterables that are (non-null) objects are used to construct the result; other elements are ignored.
crossJoinAll pairs of elements from two iterables. crossJoin is equivalent to innerJoin with a callback function that always returns true:
x crossJoin y is equivalent to x innerJoin y [true]
x crossJoin y 5 is equivalent to x innerJoin y [true] 5
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, antiJoinThese 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}]
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.
randomGenerate 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)
randomIntGenerate 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}
normalGenerate 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
logNormalGenerate 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
exponentialGenerate 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
binomialGenerate 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
geometricGenerate 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
categoricalGenerate 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
shuffleShuffle 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 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.
See Reduce, Filter, Group, Order and Bin for other operators that can act directly on nested data.
pickpick 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']
mapAtmapAt 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]
// ]
transposetranspose 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]
// ]
arrObjarrObj 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.
objArrobjArr 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 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.
differenceElements 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}
intersectionElements 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}
unionElements 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}
The following rules apply to import and export operators:
Can only be used at the 'top-level' — not inside parentheses, indented blocks or bracket functions.
The first operand of import operators is the module path/url which must be a string literal. All other operands of import and export operators must be identifiers.
Return undefined.
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.
exportExport variables:
export add subtract
add = [a + b]
subtract = [a - b]
export can only be used once per file.
importUse 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
importAsRename imports. Operands are passed as importName newName pairs:
importAs '/modules/arithmetic.js'
| add plus
| subtract takeaway
// use plus and takeaway here
importAllImport all of a module's exports as an object:
importAll '/modules/arithmetic.js' arith
// use arith,add and arith,subtract here
importDefaultImport default export.
Zap does not use default exports, but many JavaScript modules do:
importDefault '/modules/my-js-module.js' theDefault
// use theDefault here
loadDynamically 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 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
printPrint 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
console,log.
debuggerInvoke debugger (if it exists):
debugger
periodCreate 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
periodusessetTimeout.
The following terms are used in this section to describe the elements associated with an operand:
first element:
CSS selector string → first matching element.
non-string iterable → first element.
element → element.
all elements:
CSS selector string → all matching elements; where relevant, the operator returns the elements as an array.
non-string iterable → all elements of the iterable; where relevant, the operator returns the iterable.
element → element; where relevant, the operator returns the element.
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
encodeoperator to map data to elements rather than D3's enter-update-exit approach. Currently,encodecannot map data to existing elements; this functionality will be added in a future version.
selectselect 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.
selectAllAs select, but selectAll returns all of the matching elements as an array — an empty array if there are no matching elements.
create, createSVGCreate 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>]
fragmentfragment takes no operands and returns a new document fragment.
encode, encodeSVGencode 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}
insertInsert 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.
intointo 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>
insertEachinsertEach 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, styleGet 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, texthtml and text are shorthand getters/setters for the innerHTML and textContent properties respectively. Specifically, they are equivalent to using prop:
e html is equivalent to e prop 'innerHTML'
e html h is equivalent to e prop 'innerHTML' h
e text is equivalent to e prop 'textContent'
e text t is equivalent to e prop 'textContent' t
removeRemove all elements of the operand from the document.
Returns the element(s).
lower, raiseMove 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, removeClassAdd/remove classes from all elements of the first operand. The second operand is a string of space-separated class names.
Returns the element(s).
toggleClassAdd 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, removeStyleRemove 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, hasClassFor 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, offAdd/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:
The event handler must be a function when using on and off.
The event is the second argument passed to the event handler when using on and off — the event is the only argument when using the native methods.
on skips elements where the event handler is already registered for the given event type — even if the fourth operand of on is different to when the handler was originally registered.
on and off return the element(s).
sketchsketch creates a <canvas> element and a drawing context. sketch can be used with no operands or with an options object. The options are:
width: canvas width in pixels (default: 300).
height: canvas height in pixels (default: 300).
context: context type (default: '2d').
scale: scale canvas and context to account for the device pixel ratio (default: true).
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
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:
$div is equivalent to create 'div'
$div 5 is equivalent to create 'div' 5
$circle is equivalent to createSVG 'circle'
$circle 5 is equivalent to createSVG 'circle' 5
Where HTML and SVG elements have the same name (e.g.
a,script,styleandtitle), there are convenience operators for the HTML elements, but not for the SVG elements.