Skip to content

Operators and symbols

Everything that computes in Dendrite is an operator (an op, for short): a named function with named inputs, supplied by the standard library or by the application you are embedded in. Symbols are a second way to spell the common ones.

output sum  = 2 + 3
output same = Add(2, 3)

Those two lines are the same program. + is not built into the language: the standard library registers it as sugar over its own Add op, and the parser rewrites it on the way in. Nothing downstream can tell which spelling you used.

Because it means there is no privileged arithmetic, and nothing to reverse-engineer when you go looking for an op that should exist.

>= is the clearest case. It has no op of its own:

output a = $n >= 60
output b = Not(LessThan($n, 60))

Again, one program. If you go hunting for GreaterThanOrEqual in the standard library you will not find it, and now you know why. A symbol can desugar into a whole little tree, not just a single call.

The practical upshot: reach for the symbol when it reads better, the op when it reads clearer. Add(price, tax, shipping) says “sum these three” better than a chain of pluses, because Add is variadic and takes as many as you give it.

SymbolsMeaningThe op beneath
||or, variadicOr
&&and, variadicAnd
== !=equalityEquals, NotEquals
< >comparisonLessThan, GreaterThan
<= >=comparisonNot(GreaterThan(…)), Not(LessThan(…))
+ -additionAdd (variadic), Subtract
++join as textJoin over the two sides
* /multiplicationMultiply (variadic), Divide
!not, prefixNot
-negate, prefixNegate

Multiplication binds tighter than addition, addition tighter than comparison, comparison tighter than &&, and && tighter than ||: the ordering you already expect. Brackets override it:

output plain    = 2 + 3 * 4
output bracketed = (2 + 3) * 4

plain is 14 and bracketed is 20.

$quantity >= 3 appears twice here, once inside If and once as an output, and it is one comparison written two ways round. Edit the program: swap a symbol for its op, add a bracket, see the outputs agree.

An op’s inputs have names, and you may pass them positionally or by name:

output positional = If($ready, "go", "wait")
output named      = If(condition: $ready, then: "go", else: "wait")

Named arguments cost more to type and pay for themselves on an op with three or four inputs, or whenever the order is not obvious. The names are in the standard library reference, which is generated from the ops themselves and so cannot be out of date.

Two mistakes worth knowing by name. Pass an input an op does not have and you get unknown_op_input_key, a warning: the value is ignored. Leave out one it needs and you get missing_op_input, also a warning, with a default filled in for you, which is why a half-written program keeps running while you write the rest of it.

A template is text with expressions in it:

output label  = `{$name} has {$count} left`
output nested = `outer {`inner {$count > 3}`}`

The backticks hold the text, and each {…} holds any expression, a template included. A hole is {…} and not ${…}, because $ already means an input in this language: ${count} is the text $ followed by the hole. Underneath, a template is a Join over its parts, Join([$name, " has ", $count, " left"]), and Join converts each part to text itself, so a number, a boolean, a null or a list in a hole needs no ToString. A template can span lines, and \` and \{ write a backtick or a brace as text.

For two values, ++ says the same thing shorter:

output label = $name ++ " has " ++ $count

It is the same Join underneath, so each side becomes text the way a hole does. It binds exactly as + does, which matters when both appear: "total: " ++ $a + $b reads as ("total: " ++ $a) + $b, text handed to Add, and is reported as a type error. Bracket the sum, or write a template.