The Koto Programming Language

Language Overview

Koto is an expression-based scripting language with the goals of keeping things simple, and visually clean. Effort has been taken to only introduce visual elements to the syntax when absolutely necessary.

Assignments

Assignment to an identifier is performed using the = operator.

x = 1
a, b, c = 1, 2, 3

Identifiers

Identifier names use the Unicode xid_start and xid_continue definitions as outlined here.

Examples of valid identifiers include:

my_id = 123
foo42 = 99
héllø = -1
やあ = 100

Comments

Single-line comments are using a # symbol.

Multi-line comments are started with #- and finished with -#.

Mulii-line comments can be nested, and can be used as inline comments.

# This is a single-line comment

#- This is a
multi-line comment

#- Multi-line comments can be nested -#

-#

a = 1 + #- This is an inline comment -# 2

Indentation

Whitespace is used to define indented blocks of code, and for continuing long expressions.

Spaces and tabs are both counted as whitespace characters.

Any amount of indentation can be used for a block, but the level must remain consistent throughout a block for the parser to understand that the expressions belong together.

x = if foo                # An if expression, starting on column 1
  result = do_something() # An indented 'then' block for the if expression
  result                  # A continuation of the 'then' block
else                      # The else condition matching the 'if' indentation
  do_something_else()     # The 'else' body.

# No 'end if' is necessary here to start a new expression,
# the return of indentation to column 1
print x

Text format

Koto scripts are expected to contain valid UTF-8 data.

Value Types

Control Flow

Functions

Iterators

Error Handling

Importing Code

Testing

Core Library

The core library for Koto contains utilities that are a standard part of the Koto runtime.

The library is grouped into modules, e.g. test contains functions related to testing Koto code. Many of the modules are related to value types

io

A collection of utilities for working with the local filesystem.

Reference

create

|String| -> File

Returns an empty File at the provided path. If the file already exists it will be truncated.

Errors

A runtime error will be thrown if the file can't be created.

Example

f = io.create "foo.temp"
f.write_line "Hello"
f.read_to_string()
# Hello

current_dir

|| -> String

Returns the current working directory as a String, or Empty if the current directory can't be retrieved.

exists

|String| -> Bool

Returns true if a file exists at the provided path.

Example

path = "foo.temp"
io.exists path
# false

io.create path
io.exists path
# true

open

|String| -> File

Opens the file at the given path, and returns a corresponding File.

Errors

An error is thrown if a file can't be opened at the given path.

Example

f = io.open "path/to/existing.file"
f.exists()
# true

print

|Value| -> () |String, Value...| -> ()

Prints a formatted string to the active logger, by default this is the standard output.

Note

See string.format for the formatting syntax.

read_to_string

|String| -> String

Returns a string containing the contents of the file at the given path.

Errors

Errors are thrown:

  • if the file doesn't contain valid UTF-8 data.
  • if a file can't be opened at the given path.

Example

f = io.create "foo.temp"
f.write_line "Hello!"
io.read_to_string "foo.temp"
# Hello!

remove_file

|String| -> ()

Removes the file at the given path.

Errors

  • An error is thrown if a file can't be removed at the given path.

Example

path "foo.temp"
io.create path
io.exists path
# true

io.remove_file path
io.exists path
# false

stderr

|| -> File

Returns the standard error output of the current process as a file.

Example

io.stderr().write_line "An error occurred!"

See Also

stdin

|| -> File

Returns the standard input of the current process as a file.

Example

io.stdin().read_to_string()
# "..."

See Also

stdout

|| -> File

Returns the standard output of the current process as a file.

Example

io.stdout().write_line "Hello, World!"

See Also

temp_dir

|| -> String

Returns the path to a temporary directory.

Note

This defers to Rust's std::env::temp_dir, for details see its documentation.

File

A map that wraps a file handle, returned from functions in io.

File.flush

|File| -> ()

Ensures that any buffered changes to the file have been written.

See Also

File.path

|File| -> String

Returns the file's path.

File.read_line

|File| -> String or Empty

Reads a line of output from the file as a string, not including the newline.

When the end of the file is reached, Empty will be returned.

Errors

An error is thrown if the line doesn't contain valid UTF-8 data.

File.read_to_string

|File| -> String

Reads the file's contents to a string.

Errors

An error is thrown if the file doesn't contain valid UTF-8 data.

File.seek

|File, Number| -> ()

Seeks within the file to the specified position in bytes.

File.write

|File, Value| -> ()

Writes the formatted value as a string to the file.

File.write_line

|File, Value| -> ()

Writes the formatted value as a string, with a newline, to the file.

iterator

Iterators in Koto provide access to sequences of data, yielding values via .next(), until the end of the sequence is reached and the empty value () is returned.

Iterable Values

Values that can produce iterable sequences are referred to as Iterable.

Iterables include:

  • Iterators (naturally!)
  • Lists
  • Maps
    • The map's key/value pairs are provided as a Tuple.
  • Ranges
  • Strings
  • Tuples

The contents of the iterator module are made available to all Iterables.

Example

# Starting with a List
[1, 2, 3]
  # Calling iterator.each with the List as the implicit first argument
  .each |x| x * 2
  # Calling iterator.to_list with the Iterator resulting from iterator.each
  .to_list()
# [2, 4, 6]

Loops

for, while, and until loops take any Iterable and then provide its output values for each iteration of the loop.

for x in (2, 3, 4).each |n| n * 2
  io.print "-> {}", x
# -> 4
# -> 6
# -> 8

Reference

all

|Iterable, Function(|Value| -> Bool)| -> Bool

Checks the Iterable's values against a test Function. The Function should return true or false, and then all returns true if all values pass the test.

all stops running as soon as it finds a failing test, and then false is returned.

Example

(1..9).all |x| x > 0
# true

("", "", "foo").all string.is_empty
# false

[10, 20, 30]
  .each |x| x / 10
  .all |x| x < 10
# true

any

|Iterable, |Value| -> Bool| -> Bool

Checks the Iterable's values against a test Function. The Function should return true or false, and then any returns true if any of the values pass the test.

any stops running as soon as it finds a passing test.

Example

(1..9).any |x| x == 5
# true

("", "", "foo").any string.is_empty
# true

[10, 20, 30]
  .each |x| x / 10
  .any |x| x == 2
# true

chain

|Iterable, Iterable| -> Iterator

chain returns an iterator that iterates over the output of the first iterator, followed by the output of the second iterator.

Example

[1, 2].chain([3, 4, 5]).to_tuple()
# (1, 2, 3, 4, 5)

consume

|Iterable| -> ()

Consumes the output of the iterator. This is useful when the side-effects of the iterator chain are important, but not so much the output value.

count

|Iterable| -> Number

Counts the number of items yielded from the iterator.

Example

(5..=15).count()
# 10

(0..100)
  .keep |x| x % 2 == 0
  .count()
# 50

each

|Iterable, |Value| -> Value| -> Iterator

Takes an Iterable and a Function, and returns a new iterator that provides the result of calling the function with each value in the iterable.

Example

(2, 3, 4)
  .each |x| x * 2
  .to_list()
# [4, 6, 8]

enumerate

|Iterable| -> Iterator

Returns an iterator that provides each value along with an associated index.

Example

("a", "b", "c").enumerate().to_list()
# [(0, "a"), (1, "b"), (2, "c")]

fold

|Iterable, Value, |Value, Value| -> Value| -> Value

Returns the result of 'folding' the iterator's values into an accumulator function.

The function takes the accumulated value and the next iterator value, and then returns the result of folding the value into the accumulator.

The first argument is an initial accumulated value that gets passed to the function along with the first value from the iterator.

The result is then the final accumulated value.

This operation is also known in other languages as reduce, accumulate, inject, fold left, along with other names.

Example

("a", "b", "c").fold "", |result, x| result += x + "-"
# a-b-c-

See Also

intersperse

|Iterable, Value| -> Iterator

Returns an iterator that yields a copy of the provided value between each adjacent pair of output values.

|Iterable, || -> Value| -> Iterator

Returns an iterator that yields the result of calling the provided function between each adjacent pair of output values.

Example

("a", "b", "c").intersperse("-").to_string()
# "a-b-c"

separators = (1, 2, 3).iter()
("a", "b", "c")
  .intersperse || separators.next()
  .to_tuple(),
# ("a", 1, "b", 2, "c")

keep

|Iterable, |Value| -> Bool| -> Iterator

Returns an iterator that keeps only the values that pass a test function.

The function is called for each value in the iterator, and returns either true if the value should be kept in the iterator output, or false if it should be discarded.

Example

(0..10).keep(|x| x % 2 == 0).to_tuple()
# (0, 2, 4, 6, 8)

last

|Iterable| -> Value

Consumes the iterator, returning the last yielded value.

Example

(1..100).take(5).last()
# 5

(0..0).last()
# ()

max

|Iterable| -> Value

Returns the maximum value found in the iterable.

A < 'less than' comparison is performed between each value and the maximum found so far, until all values in the iterator have been compared.

Example

(8, -3, 99, -1).max()
# 99

See Also

min

|Iterable| -> Value

Returns the minimum value found in the iterable.

A < 'less than' comparison is performed between each value and the minimum found so far, until all values in the iterator have been compared.

Example

(8, -3, 99, -1).min()
# -3

See Also

min_max

|Iterable| -> (Value, Value)

Returns the minimum and maximum values found in the iterable.

A < 'less than' comparison is performed between each value and both the minimum and maximum found so far, until all values in the iterator have been compared.

Example

(8, -3, 99, -1).min_max()
# (-3, 99)

See Also

next

|Iterator| -> Value

Returns the next value from the iterator.

Example

x = (1, 2).iter()
x.next()
# 1
x.next()
# 2
x.next()
# ()

position

|Iterable, |Value| -> Bool| -> Value

Returns the position of the first value in the iterable that passes the test function.

The function is called for each value in the iterator, and returns either true if the value is a match, or false if it's not.

The first matching value will cause iteration to stop, and the number of steps taken to reach the matched value is returned as the result.

If no match is found then () is returned.

Example

(10..20).position |x| x == 15
# 5

(10..20).position |x| x == 99
# ()

product

|Iterable| -> Value

Returns the result of multiplying each value in the iterable together.

Example

(2, 3, 4).product()
# 24

See also

skip

|Iterable, Number| -> Iterator

Skips over a number of steps in the iterator.

Example

(100..200).skip(50).next()
# 150

See also

sum

|Iterable| -> Value

Returns the result of adding each value in the iterable together.

Example

(2, 3, 4).sum()
# 9

See also

take

|Iterable, Number| -> Iterator

Provides an iterator that consumes a number of values from the input before finishing.

Example

(100..200).take(3).to_tuple()
# (100, 101, 102)

See also

to_list

|Iterable| -> List

Consumes all values coming from the iterator and places them in a list.

Example

("a", 42, (-1, -2)).to_list()
# ["a", 42, (-1, -2)]

See also

to_map

|Iterable| -> Map

Consumes all values coming from the iterator and places them in a map.

If a value is a tuple, then the first element in the tuple will be inserted as the key for the map entry, and the second element will be inserted as the value.

If the value is anything other than a tuple, then it will be inserted as the map key, with () as the entry's value.

Example

("a", "b", "c").to_map()
# {"a": (), "b": (), "c": ()}

("a", "bbb", "cc")
  .each |x| x, x.size()
  .to_map()
# {"a": 1, "bbb": 3, "cc": 2}

See also

to_string

|Iterable| -> String

Consumes all values coming from the iterator and produces a string containing the formatted values.

Example

("x", "y", "z").to_string()
# "xyz"

(1, 2, 3).intersperse("-").to_string()
# "1-2-3"

See also

to_tuple

|Iterable| -> Tuple

Consumes all values coming from the iterator and places them in a tuple.

Example

("a", 42, (-1, -2)).to_list()
# ["a", 42, (-1, -2)]

See also

zip

|Iterable, Iterable| -> Iterator

Combines the values in two iterables into an iterator that provides corresponding pairs of values, one at a time from each input iterable.

Example

(1, 2, 3).zip(("a", "b", "c")).to_list()
# [(1, "a"), (2, "b"), (3, "c")]

koto

A collection of utilities for working with the Koto runtime.

Reference

args

Tuple

Provides access to the arguments that were passed into the script when running the koto CLI application.

If no arguments were provided then the list is empty.

Example

# Assuming that the script was run with `koto script.koto -- 1 2 "hello"`
koto.args.size()
# 3
koto.args.first()
# 1
koto.args.last()
# hello

exports

|| -> Map

Returns the current module's export map.

Although typically module items are exported with export expressions, it can be useful to export items programatically.

script_dir

String or Empty

If a script is being executed then script_dir provides the directory that the current script is contained in as a String, otherwise script_dir is Empty.

script_path

String or Empty

If a script is being executed then script_path provides the path of the current script as a String, otherwise script_path is Empty.

type

|Value| -> String

Returns the type of the input Value as a String.

Note that a map value can override the value returned from type by defining the @type meta value, for more information see the reference for map.

Example

koto.type true
# Bool

x = 42
koto.type x
# Int

foo =
  @type: "Foo"
koto.type foo
# Foo

list

Lists in Koto are dynamically sized contiguous arrays of values.

Like other containers in Koto, a list's data is shared between all instances of the list.

Example

x = [1, 2, "hello"]
x[1] = 99
x
# [1, 99, "hello"]

y = x
y[0] = "abc" # x and y share the same internal list data
x
# ["abc", 99, "hello"]

z = x.copy()
z[1] = -1 # z is a copy of x, so has unique internal data
x # x remains unchanged after the modificaton of z
# ["abc", 99, "hello"]

Reference

clear

|List| -> ()

Clears the list by removing all of its elements.

Example

x = [1, 2, 3]
x.clear()
x
# []

contains

|List, Value| -> Bool

Returns true if the list contains a value that matches the input value.

Matching is performed with the == equality operator.

Example

[1, "hello", (99. -1)].contains "hello"
# true

copy

|List| -> List

Makes a unique copy of the list data.

Note that this only copies the first level of data, so nested containers will share their data with their counterparts in the copy. To make a copy where any nested containers are also unique, use list.deep_copy.

Example

x = [1, 2, "hello"]
y = x
y[0] = "abc" # x and y share the same internal list data
x
# ["abc", 99, "hello"]

z = x.copy()
z[1] = -1 # z is a copy of x, so has unique internal data
x # x remains unchanged after the modificaton of z
# ["abc", 99, "hello"]

See also

deep_copy

|List| -> List

Makes a unique deep copy of the list data.

This makes a unique copy of the list data, and then recursively makes deep copies of any nested containers in the list.

If only the first level of data needs to be made unique, then use list.copy.

Example

x = [[1, 2], [3, [4, 5]]]
y = x.deep_copy()
y[1][1] = 99
x # a deep copy has been made, so x is unaffected by the assignment to y
# [[1, 2], [3, [4, 5]]]

See also

fill

|List, Value| -> ()

Fills the list with copies of the provided value.

Example

x = [1, 2, 3]
x.fill 99
x
# [99, 99, 99]

first

|List| -> Value

Returns the first value in the list, or () if the list is empty.

Example

[99, -1, 42].first()
# 99

[].first()
# ()

See also

get

|List, Number| -> Value

Gets the Nth value in the list, or () if the list doesn't contain a value at that position.

Example

[99, -1, 42].get 1
# -1

[99, -1, 42].get 5
# ()

See also

insert

|List, Number, Value| -> ()

Inserts the value into the Nth position in the list.

An error is thrown if the position is negative or greater than the size of the list.

Example

[99, -1, 42].insert 2, "hello"
# [99, -1, "hello", 42]

See also

is_empty

|List| -> Bool

Returns true if the list has a size of zero, and false otherwise.

Example

[].is_empty()
# true

[1, 2, 3].is_empty()
# false

iter

|List| -> Iterator

Returns an iterator that iterates over the list's values.

Lists are iterable, so it's not necessary to call .iter() to get access to iterator operations, but it can be useful sometimes to make a standalone iterator for manual iteration.

Example

x = [2, 3, 4].iter()
x.skip(1)
x.next()
# 3

last

|List| -> Value

Returns the last value in the list, or () if the list is empty.

Example

[99, -1, 42].first()
# 42

[].first()
# ()

See also

pop

|List| -> Value

Removes the last value from the list and returns it.

If the list is empty then () is returned.

Example

x = [99, -1, 42]
x.pop()
# 42

x
# [99, -1]

[].pop()
# ()

See also

push

|List, Value| -> ()

Adds the value to the end of the list.

Example

[99, -1].push "hello"
# [99, -1, "hello"]

See also

remove

|List, Number| -> Value

Removes the value at the given position from the list and returns it.

Throws an error if the position isn't a valid index in the list.

Example

[99, -1, 42].remove 1
# [99, 42]

See also

resize

|List, Number, Value| -> ()

Grows or shrinks the list to the specified size. If the new size is larger, then copies of the provided value are used to fill the new space.

Example

x = [1, 2]
x.resize 4, "x"
x
# [1, 2, "x", "x"]

x.resize 3, ()
x
# [1, 2, "x"]

retain

|List, Value| -> ()

Retains matching values in the list, discarding values that don't match.

If the test value is a function, then the function will be called with each of the list's values, and if the function returns true then the value will be retained, otherwise if the function returns false then the value will be discarded.

If the test value is not a function, then the list's values will be compared using the == equality operator, and then retained if they match.

Example

x = [1..10]
x.retain |n| n < 5
x
# [1, 2, 3, 4]

x = [1, 3, 8, 3, 9, -1]
x.retain 3
x
# [3, 3]

reverse

|List| -> ()

Reverses the order of the list's contents.

Example

x = ["hello", -1, 99, "world"]
x.reverse()
x
# ["world", 99, -1, "hello"]

size

|List| -> Number

Returns the number of values contained in the list.

Example

x = [1..=100]
x.size()
# 100

[].size()
# 0

sort

|List| -> ()

Sorts the list in place.

|List, |Value| -> Value| -> ()

Sorts the list in place, based on the output of calling a 'key' function for each value. The function result is cached, so it's only called once per value.

Example

x = [1, -1, 99, 42]
x.sort()
x
# [-1, 1, 42, 99]

x = ["bb", "ccc", "a"]
x.sort string.size
x
# ["a", "bb", "ccc"]

x = [2, 1, 3]
x.sort |n| -n
x
# [3, 2, 1]

sort_copy

|List| -> List

Returns a sorted copy of the list. The input is left untouched.

Example

x = [1, -1, 99, 42]
y = x.sort_copy()
y
# [-1, 1, 42, 99]

x # x remains untouched
# [1, -1, 99, 42]

swap

|List, List| -> ()

Swaps the contents of the two input lists.

Example

x = [1, 2, 3]
y = [7, 8, 9]
x.swap y

x
# [7, 8, 9]

y
# [1, 2, 3]

to_tuple

|List| -> Tuple

Returns a copy of the list data as a tuple.

Example

[1, 2, 3].to_tuple()
# (1, 2, 3)

transform

|List, |Value| -> Value| -> ()

Transforms the list data by calling a function with each value and then replacing the value with the function result.

Example

x = ["aaa", "bb", "c"]
x.transform string.size
x
# [3, 2, 1]

x.transform |n| "{}".format n
x
# ["3", "2", "1"]

with_size

|Number, Value| -> List

Returns a list containing N copies of a value.

Example

import list
list.with_size 5, "$"
# ["$", "$", "$", "$", "$"]

map

Maps in Koto are associative containers of keys mapped to values.

The order in which items are added to the map will be preserved.

Creating a map

There are two ways to directly create a map in Koto: map blocks, and inline maps.

Block map syntax

Maps can be created with indented blocks, where each line contains an entry of the form Key: Value.

x =
  hello: -1
  goodbye: 99

x.hello
# -1
x.goodbye
# 99

Nested Maps can be defined with additional indentation:

x =
  hello:
    world: 99
    everybody: 123
    to:
      you: -1
x.hello.world
# 99
x.hello.to.you
# 123

Inline map syntax

Maps can also be created with curly braces, with comma-separated entries.

x = {hello: -1, goodbye: "abc"}
x.hello
# -1
x.goodbye
# abc

If only the key is provided for an entry, then a value matching the name of the key is looked for and is then copied into the entry.

hello = "abc"
goodbye = 99
x = {hello, goodbye, tschüss: 123}
x.goodbye
# 99

Keys

When creating a Map directly, the keys are defined as strings. To use non-string values as keys, map.insert can be used.

x = {}
x.insert 0, "Hello"
x.insert true, "World"
"{}, {}!".format x.get(0), x.get(true)
# Hello, World!

Instance functions

When a Function is used as a value in a Map, and if it uses the keyword self as its first argument, then the runtime will pass the instance of the map that contains the function as the self argument.

x =
  # Initialize an empty list
  data: []
  # Takes a value and adds it to the list
  add_to_data: |self, n| self.data.push n
  # Returns the sum of the list
  sum: |self| self.data.sum()

x.add_to_data 2
x.add_to_data 20
x.sum()
# 22

Operators

The + operator can be used to merge two maps together.

x = {hello: 123}
y = {goodbye: 99}
x + y
# {hello, goodbye}

Meta Maps and overloaded operations

Maps can be used to create value types with custom behaviour.

Keys with @ prefixes go into the map's 'meta map', which is checked when the map is encountered in operations.

make_x = |n|
  data: n
  # Overloading the addition operator
  @+: |self, other|
    # a new instance is made with the result of adding the two values together
    make_x self.data + other.data
  # Overloading the subtraction operator
  @-: |self, other|
    make_x self.data - other.data

x1 = make_x 10
x2 = make_x 20

(x1 + x2).data
# 30
(x1 - x2).data
# -10

All binary operators can be overloaded following this pattern.

Additionally, the following meta functions can customize object behaviour:

  • @negate
    • Overloads the unary negation operator:
      • @negate: |self| make_x -self.data
  • @index
    • Overloads [] indexing:
      • @index: |self, index| self.data + index
  • @display
    • Customizes how the map will be displayed when formatted as a string:
      • @display: |self| "X: {}".format self.data
  • @type
    • Provides a String that's used when checking the map's type:
      • @type: "X"

Meta entries

@meta can be used as a prefix on a map entry to add it to the meta map. The entry will be accessible on value lookups but won't show up in the regular map data:

make_x = |n|
  data: n
  # Overloading the addition operator
  @meta get_data: |self| self.data

x = make_x 42
x.keys().to_list()
# ["data"]
x.get_data()
# 42

Tests

Tests are also stored in the meta map, see test.md for info.

Reference

clear

|Map| -> ()

Clears the map by removing all of its elements.

Example

x = {x: -1, y: 42}
x.clear()
x
# {}

contains_key

|Map, Key| -> Bool

Returns true if the map contains a value with the given key, and false otherwise.

copy

|Map| -> Map

Makes a unique copy of the map data.

Note that this only copies the first level of data, so nested containers will share their data with their counterparts in the copy. To make a copy where any nested containers are also unique, use map.deep_copy.

Example

x = {foo: -1, bar: 99}
y = x
y.foo = 42
x.foo
# 42

z = x.copy()
z.bar = -1
x.bar # x.bar remains unmodified due to the
# 99

See also

deep_copy

|Map| -> Map

Makes a unique deep copy of the map data.

This makes a unique copy of the map data, and then recursively makes deep copies of any nested containers in the map.

If only the first level of data needs to be made unique, then use map.copy.

Example

x = {foo: 42, bar: {baz: 99}}
y = m.deep_copy()
y.bar.baz = 123
x.bar.baz # a deep copy has been made, so x is unaffected by the change to y
# 99

See also

get

|Map, Key| -> Value

Returns the value corresponding to the given key, or () if the map doesn't contain the key.

Example

x = {hello: -1}
x.get "hello"
# -1

x.get "goodbye"
# ()

x.insert 99, "xyz"
x.get 99
# xyz

See also

get_index

|Map, Number| -> Tuple

Returns the entry at the given index as a key/value tuple, or () if the map doesn't contain an entry at that index.

An error will be thrown if a negative index is provided.

Example

x = {foo: -1, bar: -2}
x.get_index 1
# (bar, -2)

x.get_index 99
# ()

See also

insert

|Map, Key| -> Value

Inserts () into the map with the given key.

|Map, Key, Value| -> Value

Inserts a value into the map with the given key.

If the key already existed in the map, then the old value is returned. If the key didn't already exist, then () is returned.

Example

x = {hello: -1}
x.insert "hello", 99 # -1 already exists at `hello`, so it's returned here
# -1

x.hello # hello is now 99
# 99

x.insert "goodbye", 123 # No existing value at `goodbye`, so () is returned
# ()

x.goodbye
# 123

See also

is_empty

|Map| -> Bool

Returns true if the map contains no entries, otherwise false.

Example

{}.is_empty()
# true

{hello: -1}.is_empty()
# false

See also

iter

|Map| -> Iterator

Returns an iterator that iterates over the map's entries.

Each key/value pair is provided in order as a tuple.

Maps are iterable, so it's not necessary to call .iter() to get access to iterator operations, but it can be useful sometimes to make a standalone iterator for manual iteration.

Example

m =
  hello: -1
  goodbye: 99

x = m.iter();

x.next()
# ("hello", -1)

x.next()
# ("goodbye", 99)

x.next()
# ()

See also

keys

|Map| -> Iterator

Returns an iterator that iterates in order over the map's keys.

Example

m =
  hello: -1
  goodbye: 99

x = m.keys()

x.next()
# "hello"

x.next()
# "goodbye"

x.next()
# ()

See also

remove

|Map, Key| -> Value

Removes the entry that matches the given key.

If the entry existed then its value is returned, otherwise () is returned.

Example

x =
  hello: -1
  goodbye: 99

x.remove "hello"
# -1

x.remove "xyz"
# ()

x.remove "goodbye"
# 99

x.is_empty()
# true

See also

size

|Map| -> Number

Returns the number of entries contained in the map.

Example

{}.size()
# 0

{"a": 0, "b": 1}.size()
# 2

See also

sort

|Map| -> ()

Sorts the map's entries by key.

|Map, |Value, Value| -> Value| -> ()

Sorts the map's entries, based on the output of calling a 'key' function for each entry. The entry's key and value are passed into the function as separate arguments.

The function result is cached, so it's only called once per entry.

Example

x =
  hello: 123
  bye: -1
  tschüss: 99
x.sort() # Sorts the map by key
x
# {bye, hello, tschüss}

x.sort |_, value| value # Sort the map by value
x
# {bye, tschüss, hello}

x.sort |key, _| -key.size() # Sort the map by reversed key length
x
# {tschüss, hello, bye}

update

|Map, Key, |Value| -> Value| -> Value

Updates the value associated with a given key by calling a function with either the existing value, or () if it doesn't there isn't a matching entry.

The result of the function will either replace an existing value, or if no value existed then an entry with the given key will be inserted into the map with the function's result.

The function result is then returned from update.

|Map, Key, Value, |Value| -> Value| -> Value

This variant of update takes a default value that is provided to the function if a matching entry doesn't exist.

Example

x =
  hello: -1
  goodbye: 99

x.update "hello", |n| n * 2
# -2
x.hello
# -2

x.update "tschüss", 10, |n| n * 10
# 100
x.tschüss
# 100

See also

values

|Map| -> Iterator

Returns an iterator that iterates in order over the map's values.

Example

m =
  hello: -1
  goodbye: 99

x = m.values()

x.next()
# -1

x.next()
# 99

x.next()
# ()

See also

num2

A Num2 in Koto is a packed pair of 64bit floating-point numbers, which can be useful when dealing with operations that require pairs of numbers, like 2D coordinates.

Element-wise arithmetic operations between Num2s are available, while operations with Numbers apply the number to each element.

Example

x = num2 1, 2
y = num2 3, 4
x + y
# num2(4, 6)

x[0] + y[0]
# 4

x + 10
# num2(11, 12)

x[0] = -1
x
# num2(-1, 2)

Reference

iter

|Num2| -> Iterator

Returns an iterator that iterates over the list's values.

Num2 values are iterable, so it's not necessary to call .iter() to get access to iterator operations, but it can be useful sometimes to make a standalone iterator for manual iteration.

Example

x = (num2 3, 4).iter()
x.skip(1)
x.next()
# 4

length

|Num2| -> Float

Returns the length of the vector represented by the Num2's elements.

Example

x = num2(3, 4)
x.length()
# 5

max

|Num2| -> Float

Returns the value of the largest element in the Num2.

Example

x = num2(10, 20)
x.max()
# 20

min

|Num2| -> Float

Returns the value of the smallest element in the Num2.

Example

x = num2(10, 20)
x.min()
# 10

normalize

|Num2| -> Num2

Returns a Num2 with the same direction as the input, with its length normalized to 1.

Example

x = num2(3, 4)
x.normalize()
# num2(0.6, 0.8)

product

|Num2| -> Float

Returns the result of multiplying the Num2's elements together.

Example

x = num2(10, 20)
x.product()
# 300

sum

|Num2| -> Float

Returns the result of adding the Num2's elements together.

Example

x = num2(10, 20)
x.sum()
# 30

num4

A Num4 in Koto is a packed group of 32bit floating-point numbers, which can be useful when working with operations that require 3D coordinates, or RGBA colour values.

Element-wise arithmetic operations between Num4s are available, while operations with Numbers apply the number to each element.

Example

x = num4 1, 2, 3, 4
y = num4 5, 6, 7, 8
x + y
# num4(6, 8, 10, 12)

x[2]
# 10

x * 0.5
# num4(0.5, 1, 1.5, 2)

x[0..2] = -1
x
# num4(-1, -1, 10, 12)

Reference

iter

|Num4| -> Iterator

Returns an iterator that iterates over the list's values.

Num4 values are iterable, so it's not necessary to call .iter() to get access to iterator operations, but it can be useful sometimes to make a standalone iterator for manual iteration.

Example

x = (num4 3, 4, 5, 6).iter()
x.skip(2)
x.next()
# 5

length

|Num4| -> Float

Returns the length of the vector represented by the Num4's elements.

Example

x = num4(2, -2, 2, -2)
x.length()
# 4

max

|Num4| -> Float

Returns the value of the largest element in the Num4.

Example

x = num4(10, 20, -50, -10)
x.max()
# 20

min

|Num4| -> Float

Returns the value of the smallest element in the Num4.

Example

x = num4(10, 20, -50, -10)
x.min()
# -50

normalize

|Num4| -> Num4

Returns a Num4 with the same direction as the input, with its length normalized to 1.

Example

x = num4(2, -2, 2, -2)
x.normalize()
# num4(0.5, -0.5, 0.5, 0.5)

product

|Num4| -> Float

Returns the result of multiplying the Num4's elements together.

Example

x = num4(10, 20, -50, -10)
x.product()
# 100000

sum

|Num4| -> Float

Returns the result of adding the Num4's elements together.

Example

x = num4(10, 20, 30, 40)
x.sum()
# 100

number

Numbers in Koto are represented internally as either a signed 64 bit integer or float, switching between the two representations automatically depending on usage.

Example

x = 1        # x is assigned as an integer
y = x + 0.5  # y is assigned as a float
x += 0.99    # x is now a float

Reference

abs

|Number| -> Number

Returns the absolute value of the number.

Example

-1.abs()
# 1

1.abs()
# 1

acos

|Number| -> Float

Returns the arc cosine of the number. acos is the inverse function of cos.

Example

0.acos()
# π / 2

1.acos()
# 1

and

|Integer, Integer| -> Integer

Returns the bitwise combination of two integers, where a 1 in both input positions produces a 1 in corresponding output positions.

Example

0b1010.and 0b1100
# 0b1000

asin

|Number| -> Float

Returns the arc sine of the number. asin is the inverse function of sin.

Example

0.asin()
# 0

1.asin()
# π / 2

atan

|Number| -> Float

Returns the arc tangent of the number. atan is the inverse function of tan.

Example

0.atan()
# 0

1.atan()
# π / 4

ceil

|Number| -> Integer

Returns the smallest integer that's greater than or equal to the input.

Example

0.5.ceil()
# 1

2.ceil()
# 2

-0.5.ceil()
# 0

clamp

|Number, Number, Number| -> Number

Returns the first number restricted to the range defined by the second and third numbers.

Example

0.clamp 1, 2
# 1

1.5.clamp 1, 2
# 1.5

3.0.clamp 1, 2
# 2

cos

|Number| -> Float

Returns the cosine of the number.

Example

0.cos()
# 1.0

import number.pi

pi.cos()
# -1.0

cosh

|Number| -> Float

Returns the hyperbolic cosine of the number.

Example

3.cosh()
# (e.pow(3) + e.pow(-3)) / 2

degrees

|Number| -> Float

Converts radians into degrees.

Example

from number import pi, tau

pi.degrees()
# 180.0

tau.degrees()
# 360.0

e

Float

Provides the e constant.

exp

|Number| -> Float

Returns the result of applying the exponential function, equivalent to calling e.pow x.

Example

0.exp()
# 1.0

1.exp()
# number.e

exp2

|Number| -> Float

Returns the result of applying the base-2 exponential function, equivalent to calling 2.pow x.

Example

1.exp2()
# 2.0

3.exp2()
# 8.0

flip_bits

|Integer| -> Integer

Returns the input with its bits 'flipped', i.e. 1 => 0, and 0 => 1.

Example

1.flip_bits()
# -2

floor

|Number| -> Integer

Returns the smallest integer that's less than or equal to the input.

Example

0.5.floor()
# 0

2.floor()
# 2

-0.5.floor()
# -1

infinity

Float

Provides the constant.

is_nan

|Number| -> Bool

Returns true if the number is NaN.

Example

1.is_nan()
# false

(0 / 0).is_nan()
# true

ln

|Number| -> Float

Returns the natural logarithm of the number.

Example

1.ln()
# 0.0

from number import e

e.ln()
# 1.0

log2

|Number| -> Float

Returns the base-2 logarithm of the number.

Example

2.log2()
# 1.0

4.log2()
# 2.0

log10

|Number| -> Float

Returns the base-10 logarithm of the number.

Example

10.log10()
# 1.0

100.log10()
# 2.0

max

|Number, Number| -> Number

Returns the larger of the two numbers.

Example

1.max 2
# 2

4.5.max 3
# 4.5

min

|Number, Number| -> Number

Returns the smaller of the two numbers.

Example

1.min 2
# 1

4.5.min 3
# 3

nan

Float

Provides the NaN (Not a Number) constant.

negative_infinity

Float

Provides the -∞ constant.

or

|Integer, Integer| -> Integer

Returns the bitwise combination of two integers, where a 1 in either input positions produces a 1 in corresponding output positions.

Example

0b1010.or 0b1100
# 0b1110

pi

Float

Provides the π constant.

pow

|Number, Number| -> Number

Returns the result of raising the first number to the power of the second.

Example

2.pow 3
# 8

radians

|Number| -> Float

Converts degrees into radians.

Example

90.radians()
# π / 2

360.radians()
# π * 2

recip

|Number| -> Float

Returns the reciprocal of the number, i.e. 1 / x.

Example

2.recip()
# 0.5

shift_left

|Integer, Integer| -> Integer

Returns the result of shifting the bits of the first number to the left by the amount specified by the second number.

Note

The shift amount must be greater than or equal to 0.

Example

0b1010.shift_left 2
# 0b101000

shift_right

|Integer, Integer| -> Integer

Returns the result of shifting the bits of the first number to the right by the amount specified by the second number.

Note

The shift amount must be greater than or equal to 0.

Example

0b1010.shift_left 2
# 0b101000

sin

|Number| -> Float

Returns the sine of the number.

Example

import number.pi

(pi * 0.5).sin()
# 1.0

(pi * 1.5).sin()
# -1.0

sinh

|Number| -> Float

Returns the hyperbolic sine of the number.

Example

3.sinh()
# (e.pow(3) - e.pow(-3)) / 2

sqrt

|Number| -> Float

Returns the square root of the number.

Example

64.sqrt()
# 8.0

tan

|Number| -> Float

Returns the tangent of the number.

Example

1.tan()
# 1.sin() / 1.cos()

tanh

|Number| -> Float

Returns the hyperbolic tangent of the number.

Example

1.tanh()
# 1.sinh() / 1.cosh()

tau

Float

Provides the τ constant, equivalent to .

to_float

|Number| -> Float

Returns the number as a Float.

Example

1.to_float()
# 1.0

to_int

|Number| -> Integer

Returns the number as an Integer. This is equivalent to calling ceil.

Example

1.5.to_int()
# 2

-0.5.to_int()
# 0

xor

|Integer, Integer| -> Integer

Returns the bitwise combination of two integers, where a 1 in one (and only one) of the input positions produces a 1 in corresponding output positions.

Example

0b1010.xor 0b1100
# 0b0110

os

A collection of utilities for working with the operating system.

Reference

cpu_count

|| -> Int

Provides the number of logical CPU cores that are available in the system.

Note that this may differ from the number of physical CPU cores in the system, which is provided by physical_cpu_count.

physical_cpu_count

|| -> Int

Provides the number of physical CPU cores that are available in the system.

Note that this may differ from the number of logical CPU cores in the system, which is provided by cpu_count.

range

Ranges are represented in Koto by start and end integers.

Creating a range

Ranges are created using the syntax start..end for a non-inclusive range.

start..=end is used for an inclusive range, although this is currently just syntax sugar for creating a non-inclusive range that includes the end value. This simplifies the internal implementation but could be confusing for users, so this may change in the future.

Descending ranges are allowed, so the start value can be smaller than end.

Example

# Non-inclusive range
x = 10..20
# 10..20
x.start()
# 10
x.end()
# 20

# Inclusive range
x2 = 100..=200
# 100..201
x2.contains 200
# true

# Descending non-inclusive range
x3 = 3..0
# 3..0
x3.start()
# 3
x3.to_tuple()
# (3, 2, 1)

# Descending inclusive range
x4 = 3..=0
# 3..-1
x4.to_list()
# [3, 2, 1, 0]

Reference

contains

|Range, Number| -> Bool

Returns true if the provided number is within the range, and false otherwise.

Example

(10..20).contains 15
# true

(200..=100).contains 100
# true

x = 1..10
x.contains -1
# false

end

|Range| -> Int

Returns the end value of the range.

Example

(50..100).end()
# 100

(10..0).end()
# 0

See also

expanded

|Range, Number| -> Range

Returns a copy of the input range which has been 'expanded' in both directions by the provided amount. For an ascending range this will mean that start will decrease by the provided amount, while end will increase.

Negative amounts will cause the range to shrink rather than grow.

Example

(10..20).expanded 5
# 5..25

(10..20).expanded -2
# 12..18

(5..-5).expanded 5
# 10..-10

(5..-5).expanded -5
# 0..0

(5..-5).expanded -10
# -5..5

iter

|Range| -> Iterator

Returns an iterator that iterates over the values contained in the range.

Ranges are iterable, so it's not necessary to call .iter() to get access to iterator operations, but it can be useful sometimes to make a standalone iterator for manual iteration.

Example

x = 10..20
i = x.iter()

i.next()
# 10

i.next()
# 11

size

|Range| -> Int

Returns the size of the range. This is equivalent to range.end() - range.start().

Note that for descending ranges, a negative value will be returned.

Example

(10..20).size()
# 10

(100..=200).size()
# 101

(20..0).size()
# -20

start

|Range| -> Int

Returns the start value of the range.

Example

(50..100).start()
# 50

(10..0).start()
# 10

See also

union

|Range, Number| -> Range

Returns the union of the range and a provided number.

If the number falls outside of the range then the resulting range will be expanded to include the number.

|Range, Range| -> Range

Returns the union of two ranges.

The resulting range will encompass all values that are contained in the two ranges, and any values that lie between them.

Example

(0..10).union 5
# 0..10

(0..10).union 99
# 0..100

a = 10..20
b = 40..50
a.union b
# 10..50

string

Koto's strings are immutable sequences of characters with UTF-8 encoding.

String literals can be created with either double or single quotation marks. Both styles are offered as a convenience to reduce the need for escaping, e.g. 'a "b" c' is equivalent to "a \"b\" c", and "a 'b' c" is equivalent to 'a \'b\' c'

Functions that produce sub-strings (e.g. string.trim) share the string data between the original string and the sub-string.

Strings support indexing operations, with string indices referring to grapheme clusters.

Escape codes

Strings can contain the following escape codes to define special characters, all of which start with a \. To avoid having an escape sequence acting as an escape code, then it can be escaped with an additional \.

  • \n: Newline
  • \r: Carriage Return
  • \t: Tab
  • \u{NNNNNN}: Unicode character
    • Up to 6 hexadecimal digits can be included within the {} braces. The maximum value is \u{10ffff}.
  • \xNN: ASCII character
    • Exactly 2 hexadecimal digits follow the \x.
  • \': Single quote
  • \": Double quote
  • \\: Backslash

Example

a = "Hello"
b = 'World!'
x = "{}, {}!".format a, b
# Hello, World!
'👋🥳😆'[1]
# 🥳

Reference

bytes

|String| -> Iterator

Returns an iterator that yields a series of Numbers representing the bytes contained in the string data.

Example

"Hëy".bytes().to_tuple()
# (72, 195, 171, 121)

chars

|String| -> Iterator

Returns an iterator that yields the string's characters as strings.

Note

A 'character' in Koto is defined as a grapheme, so .chars() iterates over the string's grapheme clusters.

Example

"Héllø! 👋".chars().to_tuple()
# ("H", "é", "l", "l", "ø", "!", " ", "👋")

contains

|String, String| -> Bool

Returns true if the second provided string is a sub-string of the first.

Example

"xyz".contains "abc"
# false

"xyz".contains "yz"
# true

"xyz".contains "xyz"
# true

"xyz".contains ""
# true

ends_with

|String, String| -> Bool

Returns true if the first string ends with the second string.

Example

"abcdef".ends_with "def"
# true

"xyz".ends_with "abc"
# false

escape

|String| -> String

Returns the string with characters replaced with escape codes.

For example, newlines get replaced with \n, tabs get replaced with \t.

Example

"
".escape()
# "\n"

format

|String, Value...| -> String

Returns a formatted string, with the arguments being assigned to {} placeholders in the format string.

Formatting Syntax

The syntax for format strings in Koto is similar to Rust's formatting syntax.

Placeholders

  • {}
    • Takes the next value from the list of arguments, starting with the first.
    • Subsequent {} placeholders will take following values.
  • {0}, {1}, {2}, ...
    • Takes the value at the specified index.
  • {x}, {name}, {id}
    • Takes values by name from a Map.
      • The Map is expected to be the first argument after the format string.

{ characters can be included in the output string by escaping them with another {, e.g. "{{}}".format() will output "{}".

Formatting modifiers

Modifiers can be provided after a : separator in the format string.

Minimum width, fill, and alignment

A minimum width can be specified, ensuring that the formatted value takes up at least that many characters, e.g. "x{:4}x".format "ab" will output xab x.

The minimum width can be prefixed with an alignment modifier:

  • < - left-aligned
  • ^ - centered
  • > - right-aligned

e.g. "x{:>4}x".format "ab" will output x abx.

Values are left-aligned by default, except for numbers which are right-aligned by default, e.g. "x{:4}x".format 1.2 will output x 1.2x.

The alignment modifier can be prefixed with a character which will be used to fill any empty space in the formatted string (the default character being ). e.g. "{:x^8}".format 1234 will output xx1234xx.

Maximum width / Precision

A maximum width can be specified following a . character, e.g. "{:.2}".format abcd" will output ab.

For numbers this will define the number of decimal places that should be displayed.

Combining a maximum width with a minimum width is allowed, with the minimum coming before the maximum in the format string, e.g. "x{:4.2}x".format "abcd" will output xab x.

Example

"{}, {}!".format "Hello", "World"
# "Hello, World!"

"{0}-{1}-{0}".format 99, "xxx"
# "99-xxx-99

"{foo} {bar}".format {foo: 42, bar: true}
# "42 true"

"{:.2}".format 1/3
# 0.33

"{:-^8.2}".format 2/3
# --0.67--

"foo = {foo:8.3}".format {foo: 42}
# foo =   42.000

is_empty

|String| -> Bool

Returns true if the string contains no characters.

Example

"abcdef".is_empty()
# false

"".is_empty()
# true

lines

|String| -> Iterator

Returns an iterator that yields the lines contained in the input string.

Note

Lines end with either \r\n or \n.

Example

"foo\nbar\nbaz".lines().to_tuple()
# ("foo", "bar", "baz")

"\n\n\n".lines().to_tuple()
# ("", "", "")

size

|String| -> Number

Returns the number of graphemes in the string.

Note

Equivalent to calling .chars().count().

Example

"".size()
# 0

"abcdef".size()
# 6

"🥳👋😁".size()
# 3

slice

|String, Number| -> String

Returns a string with the contents of the input string starting from the provided character index.

|String, Number, Number| -> String

Returns the sub-string of the input string, starting at the first index and ending at the second number.

Note

Invalid start indices return Empty.

Example

"abcdef".slice 3
# "def"

"abcdef".slice 2, 4
# "cd"

"abcdef".slice 100, 110
# ()

split

|String, String| -> Iterator

Returns an iterator that yields strings resulting from splitting the first string wherever the second string is encountered.

|String, |String| -> Bool| -> Iterator

Returns an iterator that yields strings resulting from splitting the input string based on the result of calling a function. The function will be called for each grapheme in the input string, and splits will occur when the function returns true.

Example

"a,b,c".split(",").to_tuple()
# ("a", "b", "c")

"O_O".split("O").to_tuple()
# ("", "_", "")

"x!y?z".split(|c| c == "!" or c == "?").to_tuple()
# ("x", "y", "z")

starts_with

|String, String| -> Bool

Returns true if the first string starts with the second string.

Example

"abcdef".starts_with "abc"
# true

"xyz".starts_with "abc"
# false

to_lowercase

|String| -> String

Returns a lowercase version of the input string.

Example

"HÉLLÖ".to_lowercase()
# "héllö"

"O_o".to_lowercase()
# o_o

to_number

|String| -> Number

Returns the string parsed as a number.

Example

"123".to_number()
# 123

"-8.9".to_number()
# -8.9

to_uppercase

|String| -> String

Returns an uppercase version of the input string.

Example

"héllö".to_uppercase()
# "HÉLLÖ"

"O_o".to_uppercase()
# O_O

trim

|String| -> String

Returns the string with whitespace at the start and end of the string trimmed.

Example

"   x    ".trim()
# "x"

">    ".trim()
# >

test

A collection of utilities for writing tests.

Writing tests

To add tests to a Koto script, export a Map named @tests, and then any functions in the Map tagged with @test will be run as tests.

If a function named @pre_test is in the @tests Map, then it will be run before each test. Similarly, if a function named @post_test is present then it will be run after each test.

These functions are useful if some setup work is needed before each test, and then maybe there's some cleanup work to do after the test has finished.

To access the result of the setup work, if the test function takes self as its first argument, then the @tests Map itself will be passed in as self.

Example

# Tests are exported from a module as a map named `@tests`
export @tests =
  # '@pre_test' will be run before each test
  @pre_test: |self|
    self.test_data = 1, 2, 3

  # '@post_test' will be run after each test
  @post_test: |self|
    self.test_data = ()

  # Functions that are tagged with @test are automatically run as tests
  @test basic_assertions: ||
    # assert checks that its argument is true
    assert 1 > 0
    # assert_near checks that its arguments are equal, within a specied margin
    allowed_error = 0.1
    assert_near 1.3, 1.301, allowed_error

  # Instance test functions receive the tests map as `self`
  @test data_size: |self|
    # assert_eq checks that its two arguments are equal
    assert_eq self.test_data.size(), 3
    # assert_ne checks that its two arguments are not equal
    assert_ne self.test_data.size(), 1

Running tests

Enabling tests in the runtime

When the Koto runtime has the run_tests setting enabled, then after a module is compiled and initialized then tests will be run before calling the main function.

Enabling tests in the CLI

The run_tests setting can be enabled when using the koto CLI with the --tests flag.

Running tests from a Koto script

Tests can be run from a Koto script by calling test.run_tests.

Reference

assert

|Bool| -> ()

Throws a runtime error if the argument if false.

Example

# This assertion will pass, and no error will be thrown
assert 1 < 2

# This assertion will fail and throw an error
assert 1 > 2
# error: Assertion failed

assert_eq

|Value, Value| -> ()

Checks the two input values for equality and throws an error if they're not equal.

Example

# This assertion will pass, and no error will be thrown
assert_eq 1 + 1, 2

# This assertion will fail and throw an error
assert_eq 2 + 2, 5
# error: Assertion failed, '4' is not equal to '5'

assert_ne

|Value, Value| -> ()

Checks the two input values for inequality and throws an error if they're equal.

Example

# This assertion will pass, and no error will be thrown
assert_ne 1 + 1, 3

# This assertion will fail and throw an error
assert_ne 2 + 2, 4
# error: Assertion failed, '4' should not be equal to '4'

assert_near

|Number, Number, Number| -> ()

|Num2, Num2, Number| -> ()

|Num4, Num4, Number| -> ()

Checks that the two input numbers are equal, within an allowed margin of error.

This is useful when testing floating-point operations, where the result can be close to a target with some acceptable imprecision.

Example

allowed_error = 0.01
# This assertion will pass, and no error will be thrown
assert_near 1.3, 1.301, allowed_error

# This assertion will fail and throw an error
assert_near 1.3, 1.32, allowed_error
# error: Assertion failed, '1.3' and '1.32' are not within 0.01 of each other

run_tests

|Map| -> ()

Runs the tests contained in the map.

Example

my_tests =
  @pre_test: |self| self.test_data = 1, 2, 3
  @post_test: |self| self.test_data = ()

  @test data_size: |self| assert_eq self.test_data.size(), 3
  @test failure: |self| assert not self.test_data.is_empty()

try
  run_tests my_tests
catch error
  print "An error occurred while running my_tests: {}", error

thread

Utilities for working with threads.

Reference

create

|Function| -> Thread

Creates a new thread and executes the provided function.

See also

Example

threads = 0..4
  .each |i| thread.create(|| "thread {}".format i)
  .to_tuple()

threads
  .each |t| t.join()
  .to_tuple()
# ("thread 0", "thread 1", "thread 2", thread 3")

assert_eq data, [10..18]

sleep

|Thread, Number| -> ()

Suspends the current thread for a specified number of seconds.

The duration must be positive and finite.

Thread

A thread, created with thread.create.

Thread.join

|Thread| -> Value

Waits for the thread to finish, and then returns the result of the thread's function.

If the thread finished due to an error being thrown, then the error is propagated to the joining thread.

Example

t = thread.create || "hello"
t.join()
# hello

tuple

Tuples in Koto are fixed contiguous arrays of values.

In contrast to Lists (which contains data that can modified), once a tuple is created its data can't be modified.

Nested Lists and Maps in the Tuple can themselves be modified, but the Tuple itself can be thought of as 'read-only'.

Creating a Tuple

Tuples are created with comma-separated values:

x = "hello", -1, 99, [1, 2, 3]
# ("hello", -1, 99, [1, 2, 3])

x[2]
# 99

x[3]
# [1, 2, 3]

Parentheses are used when necessary for disambiguation:

x, y = (1, 2, 3), (4, 5, 6)
# ((1, 2, 3), (4, 5, 6))

x[1], y[2]
# (2, 6)

Reference

contains

|Tuple, Value| -> Bool

Returns true if the tuple contains a value that matches the input value.

Matching is performed with the == equality operator.

Example

(1, "hello", [99. -1]).contains "hello"
# true

("goodbye", 123).contains "hello"
# false

deep_copy

first

|Tuple| -> Value

Returns the first value in the tuple, or () if the tuple is empty.

Example

x = 99, -1, 42
x.first()
# 99

[].to_tuple().first()
# ()

get

|Tuple, Number| -> Value

Gets the Nth value in the tuple, or () if the tuple doesn't contain a value at that position.

Example

(99, -1, 42).get 1
# -1

(99, -1, 42).get 5
# ()

iter

|Tuple| -> Iterator

Returns an iterator that iterates over the tuple's values.

Tuples are iterable, so it's not necessary to call .iter() to get access to iterator operations, but it can be useful sometimes to make a standalone iterator for manual iteration.

Example

x = (2, 3, 4).iter()
x.skip(1)
x.next()
# 3

last

|Tuple| -> Value

Returns the last value in the tuple, or () if the tuple is empty.

Example

x = 99, -1, 42
x.last()
# 42

[].to_tuple().last()
# ()

size

|Tuple| -> Number

Returns the number of values contained in the tuple.

Example

x = (10, 20, 30, 40, 50)
x.size()
# 5

sort_copy

|Tuple| -> Tuple

Returns a sorted copy of the tuple.

Example

x = (1, -1, 99, 42)
y = x.sort_copy()
y
# (-1, 1, 42, 99)

x # x remains untouched
# (1, -1, 99, 42)

to_list

|Tuple| -> List

Returns a copy of the tuple's data as a list.

Example

(1, 2, 3).to_list()
# [1, 2, 3]