Global builtins reference

Around 95 functions are always in scope, with no import.

Collections

Transform: map, filter, reduce, flatten, zip, enumerate, chunk, window, unique, reverse, sort, sort_by, sort_with, group_by, partition, frequencies, select.

Access: first, last, take, drop, slice, get, has, len, contains, index_of, find, count, any, all, sum, min, max, empty_map, keys, values.

Build: push, pop, set, insert, remove, range, list.

Three of those are easy to mix up:

  • set(coll, key, value) replaces; insert splices a new element in and the list gets longer. On a map both set the key. Reaching for insert when you meant set changes the length without saying so.
  • drop(list, n) is the complement of take, so take(n) + drop(n) rebuilds the input. slice(list, start, end) is the half-open range, with out-of-range bounds clamped rather than raised.
  • has(map, key) asks whether the key exists, which is not the same question as get(map, key) == null - a key can be present and hold null. It works on structs too, and refuses anything else rather than answering false.
  • sort_by takes a key; sort_with takes a comparator returning a negative number, zero or a positive one. Reach for sort_with when the order cannot be reduced to a key - a comparison whose answer depends on the operands' types, for instance.

Parallel: pmap.

get is the nullable lookup - get(m, k) and get(xs, i) return null on a miss, where m.k and xs[i] raise. See Structured data access.

Strings

upper, lower, trim, split, join, replace, contains, starts_with, ends_with, chars, lines, reverse, index_of, len, escape_html.

is_blank is not a global - it lives in std.str.

The full toolkit is std.str.

Numbers and conversion

int, float, decimal, bool, string, bytes, abs, floor, ceil, round, pow, sqrt, min, max, sum, approx, type_of, is_null, is_map, is_list, is_string, is_number.

The predicates read better than type_of(x) == "map" in a condition that already has two other clauses, which is most conditions in code that walks JSON. is_number is true for Int, Float and Decimal.

approx(a, b, eps?) is tolerance comparison for floats - == on floats is exact IEEE. See Numbers.

JSON

json_encode, json_decode. File forms are in std.json.

AI

embed, embed_all, cosine, tokens, cost, retry, session, uuid.

See Embeddings, Budgeting, Retry, Sessions.

Built-in types and their variants

Five structured types are defined before your program starts, so match works on them out of the box and ai[T] has something ready to extract into:

typevariants
ResultOk(value), Err(value)
OptionSome(value), None
SentimentPositive, Negative, Neutral
ClassificationSpam, Important, Social, Promotions
EntityPerson(name, role), Organization(name, industry), Location(name, country)

The variants with fields are called; the rest are values you write directly:

mood = ai[Sentiment] "How does this review feel? {review}"
match mood {
    Positive => print("glad to hear it")
    Negative => print("let us make it right")
    Neutral => print("noted")
}

See Typed output and Pattern matching.

Concurrency

cell, cell_get, cell_set, cell_update, channel, send, recv, try_recv, close, select, cancel, sleep, pmap.

sleep takes seconds, and accepts a fraction - sleep(0.25) is a quarter of a second. Worth stating because the rest of the language is not uniform here: std.bg's schedulers take milliseconds (bg.after(ms, f)), as do io.timeout and watch.next, and every tuning environment variable is *_MS. Each signature below says which it means.

See cell and Channels.

Secrets

secret, reveal, is_secret. See Secrets.

Errors and assertions

error, assert. See Error handling.

Output

print, print_no_newline, read_file, write_file.

Python escape hatch

print(py("math.sqrt", 144))  # 12
print(py("os.path.join", "a", "b"))  # a/b

py(name, args...) calls a Python function in a persistent worker process, for the case where a library exists there and nowhere else. It is the deliberate escape hatch: reaching for it stays inside your Ecko program rather than becoming a separate service.

It takes a dotted function path, never an expression. py("math.sqrt", 16) works; py("[x*2 for x in range(5)]") is refused. Python resolves the name with importlib and getattr and never eval, so even a name built from untrusted data cannot execute arbitrary code. Arguments travel as JSON values and are never spliced into source.

The worker inherits nothing. It gets only what Python needs to run - PATH, HOME, the locale and PYTHON* variables - so a snippet cannot read ECKO_API_KEY or any other credential your program holds. Forward variables deliberately with ECKO_PY_ENV=NAME,OTHER.

One call cannot park the rest. A call that does not answer within ECKO_PY_TIMEOUT_MS (default 30000) raises kind timeout, and the worker is restarted so later calls still work.

It needs Python on the machine, which is the one place Ecko's "one binary, nothing to install" promise does not reach - so treat it as a bridge, not a foundation.

Shadowing

Assigning to a builtin name shadows it in the current scope rather than erroring, so sum = 0 is always safe. ecko check warns, because it is usually accidental. Reach the original through core.*:

export fn get(c, key) { ... }
fn internal(m, k) = core.get(m, k)

Arity

Every builtin's arity is checked at the call site by ecko check, before the program runs - so a wrong argument count is caught rather than raising mid-run.