use core::scalar
use core::error
use core::strings
@description("Get the length of a list")
@example("len([3, 2, 1])")
fn len(xs: List) -> Scalar
@description("Get the first element of a list. Yields a runtime error if the list is empty.")
@example("head([3, 2, 1])")
fn head(xs: List) -> A
@description("Get everything but the first element of a list. Yields a runtime error if the list is empty.")
@example("tail([3, 2, 1])")
fn tail(xs: List) -> List
@description("Prepend an element to a list")
@example("cons(77, [3, 2, 1])")
fn cons(x: A, xs: List) -> List
@description("Append an element to the end of a list")
@example("cons_end(77, [3, 2, 1])")
fn cons_end(x: A, xs: List) -> List
@description("Check if a list is empty")
@example("is_empty([3, 2, 1])")
@example("is_empty([])")
fn is_empty(xs: List) -> Bool = xs == []
@description("Concatenate two lists")
@example("concat([3, 2, 1], [10, 11])")
fn concat(xs1: List, xs2: List) -> List =
if is_empty(xs1)
then xs2
else cons(head(xs1), concat(tail(xs1), xs2))
@description("Get the first `n` elements of a list")
@example("take(2, [3, 2, 1, 0])")
fn take(n: Scalar, xs: List) -> List =
if n == 0 || is_empty(xs)
then []
else cons(head(xs), take(n - 1, tail(xs)))
@description("Get everything but the first `n` elements of a list")
@example("drop(2, [3, 2, 1, 0])")
fn drop(n: Scalar, xs: List) -> List =
if n == 0 || is_empty(xs)
then xs
else drop(n - 1, tail(xs))
@description("Get the element at index `i` in a list")
@example("element_at(2, [3, 2, 1, 0])")
fn element_at(i: Scalar, xs: List) -> A =
if i == 0
then head(xs)
else element_at(i - 1, tail(xs))
@description("Generate a range of integer numbers from `start` to `end` (inclusive)")
@example("range(2, 12)")
fn range(start: Scalar, end: Scalar) -> List =
if start > end
then []
else cons(start, range(start + 1, end))
@description("Reverse the order of a list")
@example("reverse([3, 2, 1])")
fn reverse(xs: List) -> List =
if is_empty(xs)
then []
else cons_end(head(xs), reverse(tail(xs)))
@description("Generate a new list by applying a function to each element of the input list")
@example("map(sqr, [3, 2, 1])", "Square all elements of a list.")
fn map(f: Fn[(A) -> B], xs: List) -> List =
if is_empty(xs)
then []
else cons(f(head(xs)), map(f, tail(xs)))
@description("Filter a list by a predicate")
@example("filter(is_finite, [0, 1e10, NaN, -inf])")
fn filter(p: Fn[(A) -> Bool], xs: List) -> List =
if is_empty(xs)
then []
else if p(head(xs))
then cons(head(xs), filter(p, tail(xs)))
else filter(p, tail(xs))
@description("Fold a function over a list")
@example("foldl(str_append, \"\", [\"Num\", \"bat\", \"!\"])", "Join a list of strings by folding.")
fn foldl(f: Fn[(A, B) -> A], acc: A, xs: List) -> A =
if is_empty(xs)
then acc
else foldl(f, f(acc, head(xs)), tail(xs))
fn _merge(xs, ys, cmp) =
if is_empty(xs)
then ys
else if is_empty(ys)
then xs
else if cmp(head(xs)) < cmp(head(ys))
then cons(head(xs), _merge(tail(xs), ys, cmp))
else cons(head(ys), _merge(xs, tail(ys), cmp))
@description("Sort a list of elements, using the given key function that maps the element to a quantity")
@example("fn last_digit(x) = mod(x, 10)\nsort_by_key(last_digit, [701, 313, 9999, 4])","Sort by last digit.")
fn sort_by_key(key: Fn[(A) -> D], xs: List) -> List =
if is_empty(xs)
then []
else if len(xs) == 1
then xs
else _merge(sort_by_key(key, take(floor(len(xs) / 2), xs)),
sort_by_key(key, drop(floor(len(xs) / 2), xs)),
key)
@description("Sort a list of quantities")
@example("sort([3, 2, 7, 8, -4, 0, -5])")
fn sort(xs: List) -> List = sort_by_key(id, xs)
@description("Add an element between each pair of elements in a list")
@example("intersperse(0, [1, 1, 1, 1])")
fn intersperse(sep: A, xs: List) -> List =
if is_empty(xs)
then []
else if is_empty(tail(xs))
then xs
else cons(head(xs), cons(sep, intersperse(sep, tail(xs))))
fn _add(x, y) = x + y # TODO: replace this with a local function once we support them
@description("Sum all elements of a list")
@example("sum([3 m, 200 cm, 1000 mm])")
fn sum(xs: List) -> D = foldl(_add, 0, xs)
# TODO: implement linspace using `map` or similar once we have closures. This is ugly.
fn _linspace_helper(start, end, n_steps, i) =
if i == n_steps
then []
else cons(start + (end - start) * i / (n_steps - 1), _linspace_helper(start, end, n_steps, i + 1))
@description("Generate a list of `n_steps` evenly spaced numbers from `start` to `end` (inclusive)")
@example("linspace(-5 m, 5 m, 11)")
fn linspace(start: D, end: D, n_steps: Scalar) -> List =
if n_steps <= 1
then error("Number of steps must be larger than 1")
else _linspace_helper(start, end, n_steps, 0)
@description("Convert a list of strings into a single string by concatenating them with a separator")
@example("join([\"snake\", \"case\"], \"_\")")
fn join(xs: List, sep: String) =
if is_empty(xs)
then ""
else if len(xs) == 1
then head(xs)
else "{head(xs)}{sep}{join(tail(xs), sep)}"
@description("Split a string into a list of strings using a separator")
@example("split(\"Numbat is a statically typed programming language.\", \" \")")
fn split(input: String, separator: String) -> List =
if input == ""
then []
else if !str_contains(input, separator)
then [input]
else cons(str_slice(input, 0, idx_separator),
split(str_slice(input, idx_separator + str_length(separator), str_length(input)), separator))
where
idx_separator = str_find(input, separator)