Skip to content

Built-in functions

These functions and methods are part of the language. They need no import, and they are available in a .cdlb artifact running under candela-vm with no library directory installed. With the methods the standard library adds to the same types, they make up the prelude.

A function your program declares or imports takes precedence over a built-in function of the same name; see functions. The built-in methods work the other way round and keep precedence over an impl block, as methods describes.

Everything here raises errors the same way the language does. A raised error carries a short code, and that code is what a catch binds and what catch "code" filters on; the longer message is what an uncaught error prints. The codes appear against each function below. See error handling and the error catalogue.

Output and input

print

print(value, ...)

Writes each argument to standard output, one per line. Takes any number of arguments of any type and returns nothing. A list prints as [1,2,3], a map as {a:1,b:2}, a struct as Name {field:value}, and an enum value as its variant name with any payload in brackets. A float keeps its decimal point wherever it appears, so print(3.0) writes 3.0 and print([3.0]) writes [3.0], the same text string gives. null, the value of a call that returns nothing, prints as null. An infinity prints as inf and a not-a-number as NaN. A function prints as <fn>, wherever it is held.

A stream that refuses the write costs the line, not the run. program | head -1 leaves standard output with no reader, and the prints that follow go nowhere while the program runs to the end as it otherwise would.

input

input()
input(prompt)

Writes prompt to standard output without a trailing newline, reads one line from standard input, and returns it as a string with the trailing newline (and a carriage return, if present) removed. prompt is a string; with no argument the prompt is empty.

Conversion

A conversion is the name of the type it converts to, called as a function. Text that may not hold a value of the type is read with s.parse<T>() instead, which answers None rather than raising.

int

int(value)

Converts a string or a float to an int. A float truncates towards zero, so int(3.9) is 3 and int(-3.9) is -3. A string that does not parse as an integer raises invalid_int.

float

float(value)

Converts a string or an int to a float. A string that does not parse as a floating-point number raises invalid_float.

string

string(value)

Renders any value as a string. Ints and floats use their shortest exact decimal form, bools become true or false, a string is returned unchanged, and collections, structs, and enum values use their literal form. A struct reads as Name {field:value}, the spelling print writes, wherever it appears: on its own, inside a list or a map, and as an enum payload. A function reads <fn>: there is no signature at run time to tell one function from another.

bool

bool(value)

Converts the string "true" or "false" to a bool. Any other string raises invalid_bool. The argument has to be a string; there is no truthiness conversion from other types.

type

type(value)

Returns the static type of the expression as a string, resolved at compile time. Every answer is spelled the way the same type is written in a program: int, float, bool, string, null, any for a dynamic value, int[] for a list of ints, {string: int} for a map, a|b for a union, the declared name for a struct or an enum, and fn(int) -> int for a function. A struct or an enum that takes type arguments carries them, as in Cell<int>. A list or a map names what it holds, so a map of your own enum reads {string: Value}.

A function reads as its declaration does. A parameter with no annotation is any, as is a return type the declaration leaves to the body, and a function that hands nothing back is written without the arrow.

struct Point { x: int, y: int }

fn id(x: int) -> int { return x; }
fn greet(name: string) { print(name); }
fn add(a, b) { return a + b; }

fn main() {
    print(type(Point { x: 1, y: 2 }));
    print(type(id), type(greet), type(add));
}
Point
fn(int) -> int
fn(string)
fn(any, any) -> any

The answer is the type the compiler inferred, not the tag the value carries when the program runs, so a value the compiler could not pin reads any whatever it turns out to hold. value is T answers at run time; see dynamic values.

Sequences

range

range(end)
range(start, end)

Returns a list of ints from start (0 when omitted) up to but not including end. Both arguments are ints. The result is empty when start is not less than end.

Dynamic values

A value typed any (a json::parse result, an Option payload, a host function return) carries its type at run time. value is T tests it, and value as T hands it on typed as T or raises bad_downcast; both are operators of the language rather than functions, described in types.

as {any: any} gives a map with any keys and values, and as any[] a list of any, because the entries of a dynamic collection are dynamic too. Such a collection takes a push, an xs[i] = v or an m[k] = v of any type, in any order, and an entry read back out is an any that needs its own downcast. as {string: any} checks every key on the way, and gives the map a function declaring string keys accepts.

import "std/json";

fn main() {
    let m = json::parse("{\"n\": 7}") as {string: any};
    print(m["n"] as int + 1);
    m["name"] = "ada";
    if m["name"] is string {
        print("a name");
    }
}

Parsing and serialising json is the json module.

Errors and process control

throw "kind"; raises an error. It is a statement of the language rather than a function; see error handling.

exit

exit()
exit(code)

Stops the program. With no argument it ends the run normally; with an int code the process exits with that status.

At the REPL a line calling exit leaves the prompt with the status the call names, and the line is not kept in the session: the prompt compiles the session in a process of its own, and a kept line ending that process would stop every later run before it printed anything. The argument is worked out by the session the line is typed into, so exit(code) leaves with what code holds, and the status is the one the same call leaves with in a file.

argv

argv()

Returns the command-line arguments that follow the script path, as a list of strings. Takes no arguments.

the_answer

the_answer()

Prints the answer to the Ultimate Question of Life, the Universe, and Everything, and returns 42.

File system

Files are read and written through the fs module, imported with import "std/fs";.

String methods

Called on a string receiver.

Method Returns Behaviour
s.len() int The number of characters
s.uppercase() string s with every character upper-cased
s.lowercase() string s with every character lower-cased
s.starts_with(prefix) bool True when s begins with the string prefix
s.ends_with(suffix) bool True when s ends with the string suffix
s.contains(needle) bool True when the string needle occurs in s
s.index_of(needle) Option<int> Some with the position of the first occurrence of needle, or None
s.replace(from, to) string s with every occurrence of from replaced by to
s.split(separator) string[] s cut at each occurrence of the string separator; an empty separator answers the characters
s.trim() string s without leading or trailing whitespace
s.trim_start() string s without leading whitespace
s.trim_end() string s without trailing whitespace
s.trim_chars(chars) string s with any of the characters in chars stripped from both ends
s.trim_start_chars(chars) string The same, from the start only
s.trim_end_chars(chars) string The same, from the end only
s.parse<T>() Option<T> Some with the int, float or bool the text holds, or None; "4" reads as a float as well as an int
s.repeat(n) string s joined to itself n times
s.reverse() string A new string with the characters in reverse order

len, index_of, indexing, slicing, and split with an empty separator all count characters; the empty separator answers the characters one at a time. A character is a Unicode scalar value, so an accented letter or an emoji is one position and arrives whole. The chars method in the string module is that empty-separator split under a name that says so.

List methods

Called on an array receiver. push, remove, reverse, and sort change the list in place and return nothing; the rest return a new value.

Method Returns Behaviour
arr.len() int The number of elements
arr.push(x) nothing Appends x
arr.remove(i) nothing Removes the element at index i; raises index_out_of_bounds when i is outside the list
arr.contains(x) bool True when some element equals x
arr.index_of(x) Option<int> Some with the index of the first element equal to x, or None
arr.repeat(n) list The elements of arr repeated n times
arr.reverse() nothing Reverses arr in place
arr.sort() nothing Sorts arr in place, ascending
arr.join() string The elements concatenated; the receiver has to be a list of strings
arr.join(separator) string The same, with separator between elements
arr.split(x) list[] arr cut into sub-lists at each element equal to x

sort picks its ordering from the first element: ints, floats, and strings sort ascending, and a list of any other element type is left unchanged. Strings sort by their bytes, the order < gives them; see operators.

The higher-order methods (map, filter, reduce, each, find, any, all, sort_by), the reductions (sum, product) and the lookups (first, last, min, max, which answer an Option) come from the list module, which is part of the prelude. find takes a predicate; the position of a value is index_of.

Map methods

Brackets read and write an entry. m[k] is the value stored under k, and raises unknown_map_key when the key is absent. m[k] = v stores v under k, adding the entry or replacing its value, and m[k] += 1 and the other compound assignments read and write the entry in one statement.

The methods below are called on a map receiver. remove changes the map in place and returns nothing. m.get(k), the lookup that answers an Option, is in the map module.

Method Returns Behaviour
m.len() int The number of entries
m.remove(k) nothing Drops the entry under k; a key the map does not hold changes nothing
m.contains(k) bool True when k is a key in m
m.keys() list The keys
m.values() list The values

A map keeps its entries in the order they went in, so keys, values, for k in m, printing, and json all walk it first-inserted first. Three rules settle every case:

  • Writing a key the map does not hold puts it at the end.
  • Writing a key the map already holds replaces the value and leaves the entry where it is.
  • Removing a key and writing it again puts it at the end; the entries after the removed one keep their order.

Two maps holding the same entries are equal whatever order they were built in. keys and values walk the map the same way, so their results line up entry for entry.

An empty map literal {} takes its key and value types from the first m[k] = v, the way an empty list takes its element type from the first push. Only a literal written empty works that way. A map whose entries are typed any, which is what as {any: any} hands back, keeps taking entries of any type.

Number methods

Method Receiver Returns Behaviour
x.abs() int or float same as receiver The magnitude of x
x.sqrt() float float The square root of x
x.round() float float x rounded to the nearest whole number, halves away from zero
x.floor() float float The largest whole number not greater than x

The math module covers the rest of the numeric surface, including ceil, trunc, the trigonometric functions, and the logarithms.