Lambdas and lists
Dendrite has no loop. It does not need one, because a function is a value you can hand to an op.
let scores = [4, 8, 15, 16, 23]
output high = Filter(scores, item => item > 10)item => item > 10 is a lambda: a function of one argument. Filter is an
ordinary op that happens to take one, the same way Add takes numbers. There is no
special syntax for iteration and no special node kind in the compiled program.
The list ops
Section titled “The list ops”Six of them, and between them they cover what a loop would have done:
let items = [4, 8, 15, 16, 23]
output kept = Filter(items, n => n > 10)
output doubled = Map(items, n => n * 2)
output total = Reduce(items, 0, (sum, n) => sum + n)
output first = Find(items, n => n > 10)
output anyBig = Some(items, n => n > 10)
output allBig = Every(items, n => n > 10)Reduce is the one to look at twice. It takes the list, a starting value, and a function
of two arguments called with the accumulator first and the item second, so
(sum, n) => sum + n, in that order.
Beside these live the ops that do not need a function at all: Length, Concat,
Includes, Average, Min, Max, Flatten. Reach for those
first; a lambda you do not need is a lambda someone has to read.
Text from a list
Section titled “Text from a list”Text is built the same way everything else is here, from a list. Join takes the parts and,
if you give it one, what goes between them:
let sources = ["cam 1", "cam 2", "mic"]
output label = Join(Map(sources, name => Upper(name)), ", ")
output packed = Join(sources)
output status = Join(["Live: ", Length(sources)])label is "CAM 1, CAM 2, MIC", and packed runs the parts together because it has
no separator. status is where a number goes into a sentence, and it works without a
ToString: Join is the one op that converts its parts to text itself, which its
signature shows as parts~. Nothing else converts on its own. The rest of the text ops
(Trim, Contains and their neighbours) are on the string
page.
Closures are real
Section titled “Closures are real”A lambda can use anything in scope where you wrote it, inputs included:
height => height >= $threshold captures $threshold. Change the threshold and
the lambda behaves differently, because the closure holds the input rather than a copy of its
value. This is lexical scope, working the way you expect it to.
A lambda’s parameter shadows an outer name of the same name, and only inside the lambda:
The n inside is the item, 1 then 2 then 3. The n outside is still 100, and is now
unused: the unused_binding warning on it is the analyser saying so. The shadowing
itself is allowed, and raises nothing.
Naming a function
Section titled “Naming a function”A lambda is a value, so it can be bound like any other and applied later:
let double = (n: number) => n * 2
output four = double(2)
output list = Map([1, 2, 3], double)Give a parameter a type when you want to be explicit, and pass an argument by its parameter’s name when that reads better:
let adjust = (x: number) => x + 10
output named = adjust(x: 5)A parameter can also convert what it is given. Mark it ~, the mark Join carries on
its parts~, and write the type to convert to:
let shout = (t~: string) => Upper(t)
output one = shout(5)
output many = Map([1, true], shout)shout accepts anything, and t is text by the time the body reads it: one is
"5" and many is ["1", "TRUE"]. Without the mark Upper would refuse a number,
and every call would need its own ToString. The type has to be written, and it has to be
string, number, boolean or a list of one of them, the types the
conversion ops cover. Anything else, or the mark with no type, is
invalid_convert_param.
Why recursion is impossible
Section titled “Why recursion is impossible”A function cannot call itself, and the language has no way to express it. A name referring to
itself is a binding_cycle, and self-application cannot be given a type, because a
function is never accepted where any is expected.
That is deliberate, and it is the reason every Dendrite program terminates. There is no program a user can write that loops forever inside your application. You pay for it by expressing iteration through the ops above rather than by writing your own recursion, which for the work this language is for is a trade worth making.