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 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
andapply
are like the function methodscall
andapply
respectively, but the Zap operators do not specify the value ofthis
. Also, theapply
operator can take any iterable, whereas theapply
method 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
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.
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 usingObject,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.
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.
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 variableops
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 ofops
is neithernull
norundefined
, 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
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
oryieldFrom
are used inscope
,asyncScope
,as
orasyncAs
, the operator will behave like a regular function rather than a procedure — in particular, the body will have its ownthis
.
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:
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'
extends
Create 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.
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:
asyncEach
: the iterableasyncMap
: a new arrayasyncDo
: undefined
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 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 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
andyieldFrom
turn a loop into a generator function (which is automatically called to give the returned generator) so the loop will have its ownthis
,arguments
andsuper
.
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:
try
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
.
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
,trimEnd
andtrimStart
convert 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
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 literaltrue
. For example,else == true
istrue
, andelse string
is'true'
.
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.
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, butx `+` 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']
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 tosum
.
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
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
).
bin
andbinCount
assume that the bin limits are already sorted (with respect to the comparison function). If this is not the case,bin
andbinCount
will 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.
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:
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.
crossJoin
All 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
, 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}]
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
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.
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 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}
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.
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 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
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
usessetTimeout
.
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
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
:
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
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:
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).
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:
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
,style
andtitle
), there are convenience operators for the HTML elements, but not for the SVG elements.