Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

koto-calc Documentation

koto-calc is an interactive calculator and REPL built on the Koto language, extended with Algebraeon for exact arithmetic and symbolic computation.

Algebraeon: exact algebra, no rounding

Factor arbitrary-precision integers, keep rational results as fractions, isolate exact algebraic roots, and compute with polynomials, matrices, finite fields, groups, quaternions, and more. Start with the Algebraeon Overview, then use the Full Reference for the complete API.

Sections

  • About koto-calc — Project overview, features, and current state
  • CLI Reference — Command-line interface reference
  • Language Guide — The Koto language
  • Algebraeon — Exact Algebra
    • Overview — Why exact algebra matters and a quick tour
    • Full Reference — Detailed API and validated examples
  • Core Library — Standard library modules (io, iterator, koto, list, map, number, os, range, string, test, tuple)
  • Library Modules — Extra modules: color, geometry, json, random, regex, tempfile, toml, yaml

Code Examples

The code examples in the docs make use of print! and check! placeholders used by preprocessor tools:

  • Scripts like scripts/doccheck.py validate that the code examples work correctly by checking the example’s output against expectations defined by the check! commands.
  • The CLI’s help command replaces the check! commands with comments showing the expected output.

skip_check and skip_run

Code examples tagged with skip_run will be checked to ensure that they can be compiled, but won’t be executed.

skip_check will check that the script can be compiled and executed, but the script’s output won’t be validated.

About koto-calc

koto-calc is an interactive calculator with a REPL, built in Rust. It runs the Koto language and extends it with an Algebraeon module for exact arithmetic and symbolic computation.

print 'Hello, World!'

square = |n| n * n
print! '8 squared is {square 8}'
check! 8 squared is 64

koto-calc is a fork of the Koto CLI, focused on mathematical computing. The Koto language was created in 2020 as an embeddable scripting language for Rust applications; koto-calc inherits its syntax and runtime while adding algebraic types.

Features

  • Koto language — Simple, expressive syntax with fast compilation and a rich iterator model. See the language guide for details.
  • Exact algebra — Arbitrary-precision integers, rationals, algebraic numbers, polynomials, matrices, quaternions, finite fields, group theory, and more via the algebraeon module.
  • REPL — Interactive read-eval-print loop with syntax highlighting, tab completion, and the help command for built-in documentation.
  • Tests in the language — First-class test support: @test functions, assert statements, and the --tests / --import_tests flags.
  • Scripting — Run .koto scripts with arguments accessible via os.args.

Current State

koto-calc is in early development. The Koto language itself is maturing but has not yet reached 1.0. Early adopter feedback is welcome.

Attribution

koto-calc is built on Koto (MIT license), originally designed as an extension language for Rust applications. The core language, runtime, standard library modules, and library modules (color, geometry, json, random, regex, tempfile, toml, yaml) are derived from Koto.

The algebraeon module and the calculator orientation are koto-calc additions.

koto-calc CLI

koto-calc provides a command-line interface for running .koto scripts and an interactive REPL.

Installation

Build from source with the Rust toolchain (see rustup.sh for installation instructions):

cargo build --release
cargo install --path .

This provides the koto_calc command.

Usage

koto_calc [FLAGS] [script] [<args>...]

Flags

FlagDescription
-e, --evalEvaluate the argument as a script string instead of loading from disk
-i, --show_instructionsShow compiled instructions annotated with source lines
-b, --show_bytecodeShow the script’s compiled bytecode
-t, --testsRun the script’s tests before running the script
-T, --import_testsRun the script’s tests, plus tests in imported modules
-f, --formatFormat the input (from script path or stdin)
-c, --config PATHConfig file to load
-C, --print_configPrint the default config
-v, --versionPrint version information
-h, --helpPrint help information

Arguments

Arguments following the script name are available to the script via os.args.

Running Scripts

# Run a script from a file
koto_calc examples/fibonacci.koto

# Evaluate an expression directly
koto_calc -e "print (1..10).sum()"
# Output: 55

# Run tests in a script
koto_calc -t tests/example.koto

# Pass arguments to the script
koto_calc print_args.koto a b c

Using the REPL

Running koto_calc without arguments starts the REPL:

> koto_calc
Welcome to Koto

» 1 + 1
➝ 2

» 'hello!'
➝ hello!

» size [1, 2, 3]
➝ 3

The help command in the REPL provides access to the language guide and core library reference:

» help number
Numbers and Arithmetic
======================
...

Derived from the Koto Language Guide (MIT, github.com/koto-lang/koto), maintained for koto-calc.

See the neighboring readme for an explanation of the print! and check! commands used in the following example.


The Koto Language Guide

Language Basics

Koto programs contain a series of expressions that are evaluated in top-to-bottom order by Koto’s runtime.

As an example, this simple script prints a friendly greeting.

name = 'World'
print 'Hello, {name}!'

Comments

Single-line comments start with a #.

# This is a comment, everything until the end of the line is ignored.

Multi-line comments start with #- and end with -#.

#-
This is a
multi-line
comment.
-#

Numbers and Arithmetic

Numbers and arithmetic are expressed in a familiar way.

print! 1
check! 1

print! 1 + 1
check! 2

print! -1 - 10
check! -11

print! 3 * 4
check! 12

print! 9 / 2
check! 4.5

print! 12.5 % 5
check! 2.5

Underscores can be used as separators to aid readability in long numbers.

print! 1_000_000
check! 1000000

Parentheses

Arithmetic operations follow the conventional order of precedence. Parentheses can be used to group expressions as needed.

# Without parentheses, multiplication is performed before addition
print! 1 + 2 * 3 + 4
check! 11
# With parentheses, the additions are performed first
print! (1 + 2) * (3 + 4)
check! 21

Non-decimal Numbers

Numbers can be expressed with non-decimal bases.

# Hexadecimal numbers begin with 0x
print! 0xcafe
check! 51966

# Octal numbers begin with 0o
print! 0o7060
check! 3632

# Binary numbers begin with 0b
print! 0b1001
check! 9

Booleans

Booleans are declared with the true and false keywords, and combined using the and and or operators.

print! true and false
check! false

print! true or false
check! true

Booleans can be negated with the not operator.

print! not true
check! false

print! not false
check! true

Values can be compared for equality with the == and != operators.

print! 1 + 1 == 2
check! true

print! 99 != 100
check! true

Null

The null keyword is used to declare a value of type Null, which indicates the absence of a value.

print! null
check! null

Truthiness

In boolean contexts (such as logical operations), null is treated as being equivalent to false. Every other value in Koto evaluates as true.

print! not null
check! true

print! null or 42
check! 42

Assigning Variables

Values are assigned to named identifiers with =, and can be freely reassigned. Named values like this are known as variables.

# Assign the value `42` to `x`
x = 42
print! x
check! 42

# Replace the existing value of `x`
x = true
print! x
check! true

The result of an assignment is the value that’s being assigned, so chained assignments are possible.

print! x = 1
check! 1

print! a = b = 100
check! 100
print! a + b
check! 200

Compound assignment operators are also available. For example, x *= y is a simpler way of writing x = x * y.

a = 100
print! a += 11
check! 111
print! a
check! 111

print! a *= 10
check! 1110
print! a
check! 1110

Debug

The debug keyword allows you to quickly display a value while working on a program.

It prints the result of an expression, prefixed with its line number and the original expression as a string.

x = 10 + 20
debug x / 10
check! [2] x / 10: 3.0

When using debug, the displayed value is also the result of the expression, which can be useful if you want to quickly get feedback during development.

x = debug 2 + 2
check! [1] 2 + 2: 4
print! x
check! 4

Semicolons

Expressions are typically placed on separate lines, but if necessary they can be separated with semicolons.

a = 1; b = 2; c = a + b
print! c
check! 3

Lists

Lists in Koto are created with [] square brackets and can contain a mix of different value types.

Access list elements by index using square brackets, starting from 0.

x = [99, null, true]
print! x[0]
check! 99
print! x[1]
check! null

x[2] = false
print! x[2]
check! false

Once a list has been created, its underlying data is shared between other instances of the same list. Changes to one instance of the list are reflected in the other.

# Assign a list to x
x = [10, 20, 30]

# Assign another instance of the list to y
y = x

# Modify the list through y
y[1] = 99

# The change to y is also reflected in x
print! x
check! [10, 99, 30]

If no value is given between commas then null is added to the list at that position.

print! [10, , 30, , 50]
check! [10, null, 30, null, 50]

Joining Lists

The + operator allows lists to be joined together, creating a new list that contains their concatenated elements.

a = [98, 99, 100]
b = a + [1, 2, 3]
print! b
check! [98, 99, 100, 1, 2, 3]

Tuples

Tuples in Koto are similar to lists, but are designed for sequences of values that have a fixed structure.

Unlike lists, tuples can’t be resized after creation, and values that are contained in the tuple can’t be replaced.

Tuples are declared with a series of expressions separated by commas.

x = 100, true, -1
print! x
check! (100, true, -1)

Parentheses can be used for grouping to avoid ambiguity.

print! (1, 2, 3), (4, 5, 6)
check! ((1, 2, 3), (4, 5, 6))

You can access tuple elements by index using square brackets, starting from 0.

print! x = false, 10
check! (false, 10)
print! x[0]
check! false
print! x[1]
check! 10

print! y = true, 20
check! (true, 20)
print! x, y
check! ((false, 10), (true, 20))

If no value is given between commas then null is added to the tuple at that position.

x = 10, , 20, , 30
print! x
check! (10, null, 20, null, 30)

Empty and Single Element Tuples

An empty tuple (a tuple that contains zero elements) is created using empty parentheses.

x = ()
print! x
check! ()

To create a tuple that contains a single element, then a trailing comma must be included.

# An expression inside parentheses simply resolves to the result of the expression
print! (1 + 2)
check! 3

# To place the result of the expression in a tuple, use a trailing comma
print! (1 + 2,)
check! (3)

# Single element tuples can also be created without parentheses
x = 1 + 2,
print! x
check! (3)

Joining Tuples

The + operator allows tuples to be joined together, creating a new tuple containing their concatenated elements.

a = 1, 2, 3
b = a + (4, 5, 6)
print! b
check! (1, 2, 3, 4, 5, 6)

Tuple Mutability

While tuples have a fixed structure and its contained elements can’t be replaced, mutable value types (like lists) can be modified while they’re contained in tuples.

# A Tuple containing two lists
x = ([1, 2, 3], [4, 5, 6])

# Modify the second list in the tuple
x[1][0] = 99
print! x
check! ([1, 2, 3], [99, 5, 6])

Strings

Strings in Koto contain a sequence of UTF-8 encoded characters, and can be declared using ' or " quotes.

print! 'Hello, World!'
check! Hello, World!

print! "Welcome to Koto 👋"
check! Welcome to Koto 👋

Strings can start on one line and finish on another.

print! 'This is a string
that spans
several lines.'
check! This is a string
check! that spans
check! several lines.

Strings can be joined together with the + operator.

print! 'a' + 'Bc' + 'Def'
check! aBcDef

String Interpolation

Variables can be easily included in a string by surrounding them with {} curly braces.

xyz = 123
print! 'The value of xyz is {xyz}'
check! The value of xyz is 123

Including variables in a string this way is known as string interpolation.

Simple expressions can also be interpolated using the same syntax.

print! '2 plus 3 is {2 + 3}.'
check! 2 plus 3 is 5.

String Escape Codes

Strings can contain the following escape codes to define special characters, all of which start with a \.

  • \n: Newline
  • \r: Carriage Return
  • \t: Tab
  • \': Single quote
  • \": Double quote
  • \\: Backslash
  • \{: Interpolation start
  • \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.
print! '\{\'\"}'
check! {'"}
print! 'Hi \u{1F44B}'
check! Hi 👋

String Indexing

Individual bytes of a string can be accessed via indexing with [] braces.

print! 'abcdef'[3]
check! d
print! 'xyz'[1..]
check! yz

Care must be taken when using indexing with strings that could contain non-ASCII data. If the indexed bytes would produce invalid UTF-8 data then an error will be thrown. To access Unicode characters see string.chars.

Continuing a Long Line

The end of a line can be escaped with a \, which will skip the newline and any leading whitespace on the next line.

foo = "This string \
       doesn't contain \
       newlines."
print! foo
check! This string doesn't contain newlines.

Single or Double Quotes

Both single ' and double " quotes are valid for defining strings in Koto and have the same meaning.

A practical reason to choose one over the other is that the alternate quote type can be used in a string without needing to use escape characters.

print 'This string has to escape its \'single quotes\'.'
check! This string has to escape its 'single quotes'.

print "This string contains unescaped 'single quotes'."
check! This string contains unescaped 'single quotes'.

Raw Strings

When a string contains a lot of special characters, it can be preferable to use a raw string.

Raw strings ignore escape characters and interpolated expressions, providing the raw contents of the string between its delimiters.

Raw strings use single or double quotes as the delimiter, prefixed with an r.

print r'This string contains special characters: {foo}\n\t.'
check! This string contains special characters: {foo}\n\t.

For more complex string contents, the delimiter can be extended using up to 255 # characters after the r prefix,

print r#'This string contains "both" 'quote' types.'#
check! This string contains "both" 'quote' types.

print r##'This string also includes a '#' symbol.'##
check! This string also includes a '#' symbol.

Functions

Functions in Koto are created using a pair of vertical bars (||), with the function’s arguments listed between the bars. The body of the function follows the vertical bars.

hi = || 'Hello!'
add = |x, y| x + y

Functions are called with arguments contained in () parentheses. The body of the function is evaluated and the result is returned to the caller.

hi = || 'Hello!'
print! hi()
check! Hello!

add = |x, y| x + y
print! add(50, 5)
check! 55

A function’s body can be an indented block, where the last expression in the body is evaluated as the function’s result.

f = |x, y, z|
  x *= 100
  y *= 10
  x + y + z
print! f(2, 3, 4)
check! 234

Optional Call Parentheses

The parentheses for arguments when calling a function are optional and can be omitted in simple expressions.

square = |x| x * x
print! square 8
check! 64

add = |x, y| x + y
print! add 2, 3
check! 5

# Equivalent to square(add(2, 3))
print! square add 2, 3
check! 25

Something to watch out for is that whitespace is important in Koto, and because of optional parentheses, f(1, 2) is not the same as f (1, 2). The former is parsed as a call to f with two arguments, whereas the latter is a call to f with a tuple as the single argument.

Return

When the function should be exited early, the return keyword can be used.

f = |n|
  return 42
  # This expression won't be reached
  n * n
print! f -1
check! 42

If a value isn’t provided to return, then the returned value is null.

f = |n|
  return
  n * n
print! f 123
check! null

Function Piping

The arrow operator (->) can be used to pass the result of one function to another, working from left to right. This is known as function piping, and can aid readability when working with a long chain of function calls.

add = |x, y| x + y
multiply = |x, y| x * y
square = |x| x * x

# Chained function calls can be a bit hard to follow for the reader.
print! x = multiply 2, square add 1, 3
check! 32

# Parentheses don't help all that much...
print! x = multiply(2, square(add(1, 3)))
check! 32

# Piping allows for a left-to-right flow of results.
print! x = add(1, 3) -> square -> multiply 2
check! 32

# Call chains can also be broken across lines.
print! x = add 1, 3
  -> square
  -> multiply 2
check! 32

Maps

Maps in Koto are associative containers that contain a series of entries with keys that correspond to associated values.

The . dot operator returns the value associated with a particular key.

Maps can be created using inline syntax with {} braces:

m = {apples: 42, oranges: 99, lemons: 63}

# Get the value associated with the `oranges` key
print! m.oranges
check! 99

…Or using block syntax with indented entries:

m =
  apples: 42
  oranges: 99
  lemons: 63
print! m.apples
check! 42

Once a map has been created, its underlying data is shared between other instances of the same map. Changes to one instance are reflected in the other.

# Create a map and assign it to `a`.
a = {foo: 99}
print! a.foo
check! 99

# Assign a new instance of the map to `z`.
z = a

# Modifying the data via `z` is reflected in `a`.
z.foo = 'Hi!'
print! a.foo
check! Hi!

Entry Order

A map’s entries are maintained in a consistent order, representing the sequence in which its entries were added.

You can access map entries by index using square brackets, starting from 0.

The entry is returned as a tuple containing the key and its associated value.

m = {apples: 42, oranges: 99, lemons: 63}
print! m[1]
check! ('oranges', 99)

Entries can also be replaced by assigning a key/value tuple to the entry’s index.

m = {apples: 42, oranges: 99, lemons: 63}
m[1] = ('pears', 123)
print! m
check! {apples: 42, pears: 123, lemons: 63}

Shorthand Values

When creating maps with inline syntax, Koto supports a shorthand notation that simplifies adding existing values to the map.

If a value isn’t provided for a key, then Koto will look for a value that matches the key’s name, and if one is found then it will be used as that entry’s value.

hi, bye = 'hi!', 'bye!'
print! m = {hi, x: 42, bye}
check! {hi: 'hi!', x: 42, bye: 'bye!'}

Maps and Self

Maps can store any type of value, including functions, which provides a convenient way to group functions together.

m =
  hello: |name| 'Hello, {name}!'
  bye: |name| 'Bye, {name}!'

print! m.hello 'World'
check! Hello, World!
print! m.bye 'Friend'
check! Bye, Friend!

self is a special identifier that refers to the instance of the container in which the function is contained.

In maps, self allows functions to access and modify data from the map, enabling object-like behaviour.

m =
  name: 'World'
  say_hello: || 'Hello, {self.name}!'

print! m.say_hello()
check! Hello, World!

m.name = 'Friend'
print! m.say_hello()
check! Hello, Friend!

Joining Maps

The + operator allows maps to be joined together, creating a new map that combines their entries.

a = {red: 100, blue: 150}
b = {green: 200, blue: 99}
c = a + b
print! c
check! {red: 100, blue: 99, green: 200}

Quoted Map Keys

Map keys are usually defined and accessed without quotes, but they are stored in the map as strings. Quotes can be used if a key needs to be defined that would be otherwise be disallowed by Koto syntax rules (e.g. a keyword, or using characters that aren’t allowed in an identifier). Quoted keys also allow key names to be generated dynamically by using string interpolation.

x = 99
m =
  'true': 42
  'key{x}': x
print! m.'true'
check! 42
print! m.key99
check! 99

Map Key Types

Map keys are typically strings, but any immutable value can be used as a map key by using the map.insert and map.get functions.

The immutable value types in Koto are strings, numbers, booleans, ranges, and null. Tuples are also considered to be immutable when their contained elements are all immutable.

m = {}

m.insert 0, 'zero'
print! m.get 0
check! zero

m.insert (1, 2, 3), 'xxx'
print! m.get (1, 2, 3)
check! xxx

Core Library

The Core Library provides a collection of fundamental functions and values for working with the Koto language, organized within modules.

# Convert a string to lowercase
print! string.to_lowercase 'HELLO'
check! hello

# Return the first element of a list
print! list.first [99, -1, 3]
check! 99

Values in Koto automatically have access to their corresponding core modules via . access.

print! 'xyz'.to_uppercase()
check! XYZ

print! ['abc', 123].first()
check! abc

print! (7 / 2).round()
check! 4

print! {apples: 42, pears: 99}.contains_key 'apples'
check! true

The documentation for the Core library (along with this guide) is available in the help command of the Koto CLI.

Prelude

Koto’s prelude is a collection of core library items that are automatically made available in a Koto script without the need for first calling import.

The modules that make up the core library are all included by default in the prelude. The following functions are also added to the prelude by default:

print 'io.print is available without needing to be imported'
check! io.print is available without needing to be imported

Conditional Expressions

Koto includes several ways of producing values that depend on conditions.

if

if expressions come in two flavors; single-line:

x = 99
if x % 2 == 0 then print 'even' else print 'odd'
check! odd

…And multi-line using indented blocks:

x = 24
if x < 0
  print 'negative'
else if x > 24
  print 'no way!'
else
  print 'ok'
check! ok

The result of an if expression is the final expression in the branch that gets executed.

x = if 1 + 1 == 2 then 3 else -1
print! x
check! 3

# Assign the result of the if expression to foo
foo = if x > 0
  y = x * 10
  y + 3
else
  y = x * 100
  y * y

print! foo
check! 33

switch

switch expressions can be used as a cleaner alternative to if/else if/else cascades.

fib = |n|
  switch
    n <= 0 then 0
    n == 1 then 1
    else (fib n - 1) + (fib n - 2)

print! fib 7
check! 13

match

match expressions can be used to match a value against a series of patterns, with the matched pattern causing a specific branch of code to be executed.

Patterns can be literals or identifiers. An identifier will accept any value, so they’re often used with if conditions to refine the match.

print! match 40 + 2
  0 then 'zero'
  1 then 'one'
  x if x < 10 then 'less than 10: {x}'
  x if x < 50 then 'less than 50: {x}'
  x then 'other: {x}'
check! less than 50: 42

The _ wildcard match can be used to match against any value (when the matched value itself can be ignored), and else can be used for fallback branches.

fizz_buzz = |n|
  match n % 3, n % 5
    0, 0 then "Fizz Buzz"
    0, _ then "Fizz"
    _, 0 then "Buzz"
    else n

print! (10, 11, 12, 13, 14, 15)
  .each |n| fizz_buzz n
  .to_tuple()
check! ('Buzz', 11, 'Fizz', 13, 14, 'Fizz Buzz')

List and tuple entries can be matched against by using parentheses, with ... available for capturing the rest of the sequence.

print! match ['a', 'b', 'c'].extend [1, 2, 3]
  ('a', 'b') then
    "A list containing 'a' and 'b'"
  (1, ...) then
    "Starts with '1'"
  (..., 'y', last) then
    "Ends with 'y' followed by '{last}'"
  ('a', x, others...) then
    "Starts with 'a', followed by '{x}', then {size others} others"
  unmatched then "other: {unmatched}"
check! Starts with 'a', followed by 'b', then 4 others

Optional Chaining

The ? operator can be used to short-circuit expression chains where null might be encountered as an intermediate value. The ? checks the current value in the expression chain and if null is found then the chain is short-circuited with null given as the expression’s result.

This makes it easier to check for null when you want to avoid runtime errors.

info = {town: 'Hamburg', country: 'Germany'}

# `info` contains a value for 'town', which is then passed to to_uppercase():
print! info.get('town')?.to_uppercase()
check! HAMBURG

# `info` doesn't contain a value for 'state',
# so the `?` operator short-circuits the expression, resulting in `null`:
print! info.get('state')?.to_uppercase()
check! null

# Without the `?` operator an intermediate step is necessary:
country = info.get('country')
print! if country then country.to_uppercase()
check! GERMANY

Multiple ? checks can be performed in an expression chain:

get_data = || {nested: {maybe_string: null}}
print! get_data()?
  .get('nested')?
  .get('maybe_string')?
  .to_uppercase()
check! null

Loops

Koto includes several ways of evaluating expressions repeatedly in a loop.

for

for loops are repeated for each element in a sequence, such as a list or tuple.

for n in [10, 20, 30]
  print n
check! 10
check! 20
check! 30

while

while loops continue to repeat while a condition is true.

x = 0
while x < 5
  x += 1
print! x
check! 5

until

until loops continue to repeat until a condition is true.

z = [1, 2, 3]
until z.is_empty()
  # Remove the last element of the list
  print z.pop()
check! 3
check! 2
check! 1

continue

continue skips the remaining part of a loop’s body and proceeds with the next repetition of the loop.

for n in (-2, -1, 1, 2)
  # Skip over any values less than 0
  if n < 0
    continue
  print n
check! 1
check! 2

break

Loops can be terminated with the break keyword.

x = 0
while x < 100000
  if x >= 3
    # Break out of the loop when x is greater or equal to 3
    break
  x += 1
print! x
check! 3

A value can be provided to break, which is then used as the result of the loop.

x = 0
y = while x < 100000
  if x >= 3
    # Break out of the loop, providing x + 100 as the loop's result
    break x + 100
  x += 1
print! y
check! 103

loop

loop creates a loop that will repeat indefinitely.

x = 0
y = loop
  x += 1
  # Stop looping when x is greater than 4
  if x > 4
    break x * x
print! y
check! 25

Iterators

The elements of a sequence can be accessed sequentially with an iterator, created using the .iter() function.

An iterator yields values via .next() until the end of the sequence is reached, when null is returned.

i = [10, 20].iter()

print! i.next()
check! IteratorOutput(10)
print! i.next()
check! IteratorOutput(20)
print! i.next()
check! null

Iterator Generators

The iterator module contains iterator generators like once and repeat that generate output values lazily during iteration.

# Create an iterator that repeats ! twice
i = iterator.repeat('!', 2)
print! i.next()
check! IteratorOutput(!)
print! i.next()
check! IteratorOutput(!)
print! i.next()
check! null

Iterator Adaptors

The output of an iterator can be modified using adaptors from the iterator module.

The iterator module is available to any value which is declared to be iterable (which includes Koto’s containers like lists and strings), so it’s not necessary to call .iter() before using an adaptor.

# Create an iterator that outputs any value in the list above 3
x = [1, 2, 3, 4, 5].keep |n| n > 3

print! x.next()
check! IteratorOutput(4)
print! x.next()
check! IteratorOutput(5)
print! x.next()
check! null

Using iterators with for

for loops accept any iterable value as input, including adapted iterators.

for x in 'abacad'.keep |c| c != 'a'
  print x
check! b
check! c
check! d

Iterator Chains

Any iterator can be passed into an adaptor, including other adaptors, creating iterator chains that act as data processing pipelines.

i = (1, 2, 3, 4, 5)
  .skip 1
  .each |n| n * 10
  .keep |n| n <= 40
  .intersperse '--'

for x in i
  print x
check! 20
check! --
check! 30
check! --
check! 40

Iterator Consumers

Iterators can also be consumed using functions like .to_list() and .to_tuple(), allowing the output of an iterator to be easily captured in a container.

print! [1, 2, 3]
  .each |n| n * 2
  .to_tuple()
check! (2, 4, 6)

print! (1, 2, 3, 4)
  .keep |n| n % 2 == 0
  .each |n| n * 11
  .to_list()
check! [22, 44]

Iterator consumers are also available for creating strings and maps, as well as operations like counting the number of values yielded from an iterator, or getting the total sum of an iterator’s output.

Value Unpacking

Multiple assignments can be performed in a single expression by separating the variable names with commas.

a, b = 10, 20
print! a, b
check! (10, 20)

If there’s a single value being assigned, and the value is iterable, then it gets unpacked into the target variables.

my_tuple = 1, 2
x, y = my_tuple
print! y, x
check! (2, 1)

Unpacking works with any iterable value, including adapted iterators.

a, b, c = [1, 2, 3, 4, 5]
print! a, b, c
check! (1, 2, 3)

x, y, z = 'a-b-c'.split '-'
print! x, y, z
check! ('a', 'b', 'c')

If the value being unpacked doesn’t contain enough values for the assignment, then null is assigned to any remaining variables.

a, b, c = [-1, -2]
print! a, b, c
check! (-1, -2, null)

x, y, z = 42
print! x, y, z
check! (42, null, null)

Unpacking can also be used in for loops, which is particularly useful when looping over the contents of a map.

my_map = {foo: 42, bar: 99}
for key, value in my_map
  print key, value
check! ('foo', 42)
check! ('bar', 99)

Ignoring Unpacked Values

_ can be used as a placeholder for unpacked values that aren’t needed elsewhere in the code and can be ignored.

If you would like to add a name to the ignored value as a reminder, then the name can be appended to _. Ignored values (any variables starting with _) can be written to, but can’t be accessed.

a, _, c = 10..20
print! a, c
check! (10, 12)

_first, second = 'xyz'
print! second
check! y

Generators

Generators are iterators that are made by calling generator functions, which are any functions that contain a yield expression.

The generator is paused each time yield is encountered, waiting for the caller to continue execution.

my_first_generator = ||
  yield 1
  yield 2

x = my_first_generator()
print! x.next()
check! IteratorOutput(1)
print! x.next()
check! IteratorOutput(2)
print! x.next()
check! null

Generator functions can accept arguments like any other function, and each time they’re called a new generator is created.

As with any other iterable value, the iterator module’s functions are made available to generators.

make_generator = |x|
  for y in (1, 2, 3)
    yield x + y

print! make_generator(0).to_tuple()
check! (1, 2, 3)
print! make_generator(10)
  .keep |n| n % 2 == 1
  .to_list()
check! [11, 13]

Custom Iterator Adaptors

Generators can also serve as iterator adaptors by modifying the output of another iterator.

Inserting a generator into the iterator module makes it available in any iterator chain.

# Make an iterator adaptor that yields every
# other value from the adapted iterator
iterator.every_other = |iter = null|
  n = 0
  # If the iterator to be adapted is provided as an argument then use it,
  # otherwise defer to `self`, which is set by the runtime when the
  # generator is used in an iterator chain.
  for output in iter or self
    # If n is even, then yield a value
    if n % 2 == 0
      yield output
    n += 1

# The adaptor can be called directly...
print! iterator.every_other('abcdef').to_string()
check! ace

# ...or anywhere in an iterator chain
print! (1, 2, 3, 4, 5)
  .each |n| n * 10
  .every_other()
  .to_list()
check! [10, 30, 50]

Ranges

Ranges of integers can be created with .. or ..=.

.. creates a non-inclusive range, which defines a range up to but not including the end of the range.

# Create a range from 10 to 20, not including 20
print! r = 10..20
check! 10..20
print! r.start()
check! 10
print! r.end()
check! 20
print! r.contains 20
check! false

..= creates an inclusive range, which includes the end of the range.

# Create a range from 10 to 20, including 20
print! r = 10..=20
check! 10..=20
print! r.contains 20
check! true

If a value is missing from either side of the range operator then an unbounded range is created.

# Create an unbounded range starting from 10
r = 10..
print! r.start()
check! 10
print! r.end()
check! null

# Create an unbounded range up to and including 100
r = ..=100
print! r.start()
check! null
print! r.end()
check! 100

Bounded ranges are declared as iterable, so they can be used in for loops and with the iterator module.

for x in 1..=3
  print x
check! 1
check! 2
check! 3

print! (0..5).to_list()
check! [0, 1, 2, 3, 4]

Slices

Ranges can be used to create a slice of a container’s data.

x = (10, 20, 30, 40, 50)
print! x[1..=3]
check! (20, 30, 40)

For immutable containers like tuples and strings, slices share the original value’s data, with no copies being made.

For mutable containers like lists, creating a slice makes a copy of the sliced portion of the underlying data.

x = 'abcdef'
# No copies are made when a string is sliced
print! y = x[3..6]
check! def

a = [1, 2, 3]
# When a list is sliced, the sliced elements get copied into a new list
print! b = a[0..2]
check! [1, 2]
print! b[0] = 42
check! 42
print! a[0]
check! 1

When creating a slice with an unbounded range, if the start of the range if omitted then the slice starts from the beginning of the container. If the end of the range is omitted, then the slice includes all remaining elements in the container.

z = 'Hëllø'.to_tuple()
print! z[..2]
check! ('H', 'ë')
print! z[2..]
check! ('l', 'l', 'ø')

Type Checks

Koto is a primarily a dynamically typed language, however in more complex programs you might find it beneficial to add type checks.

These checks can help in catching errors earlier, and can also act as documentation for the reader.

One way to add type checks to your program is to use the type function, which returns a value’s type as a string.

x = 123
assert_eq (type x), 'Number'

Checking types this way is rather verbose, so Koto offers type hints as a more ergonomic alternative.

let

You can declare variables with type hints using a let expression.

If a value is assigned that doesn’t match the declared type then an error will be thrown.

let x: String = 'hello'
print! x
check! hello

let a: Number, _, c: Bool = 123, x, true
print! a, c
check! (123, true)

for arguments

Type hints can also be added to for loop arguments. The type will be checked on each iteration of the loop.

for i: Number, s: String in 'abc'.enumerate()
  print i, s
check! (0, 'a')
check! (1, 'b')
check! (2, 'c')

Functions

Function arguments can also be given type hints, and the type of the return value can be checked with the -> operator.

f = |s: String| -> Tuple
  s.to_tuple()
print! f 'abc'
check! ('a', 'b', 'c')

For generator functions, the -> type hint is used to check the generator’s yield expressions.

g = || -> Number
  yield 1
  yield 2
  yield 3
print! g().to_tuple()
check! (1, 2, 3)

match patterns

Type hints can be used in match patterns to check the type of the a value. Rather than throwing an error, if a type check fails then the next match pattern will be attempted.

print! match 'abc'
  x: Tuple then x
  x: String then x.to_tuple()
check! ('a', 'b', 'c')

Optional Values

Sometimes a value can either be of a particular type, or otherwise it should null.

These kinds of values are referred to as optional, and are useful for functions or expressions that return either a valid value, or nothing at all.

Optional value types are expressed by appending ? to the type hint.

m = {foo: 'hi!'}

print! let foo: String? = m.get('foo')?.to_uppercase()
check! HI!

print! let bar: String? = m.get('bar')?.to_uppercase()
check! null

Special Types

Any

The Any type hint will result in a successful check with any value.

print! let x: Any = 'hello'
check! hello

Callable

The Callable type hint will accept functions, or any object that can behave like a function.

let say_hello: Callable = || 'hello'
print! say_hello()
check! hello

Indexable

The Indexable type hint will accept any value that supports [] indexing.

add_first_two = |x: Indexable| x[0] + x[1]
print! add_first_two (100, 99, -1)
check! 199

Iterable

The Iterable type hint is useful when any iterable value can be accepted.

let a: Iterable, b: Iterable = [1, 2], 3..=5
print! a.chain(b).to_tuple()
check! (1, 2, 3, 4, 5)

String Formatting

Interpolated string expressions can be formatted using formatting options similar to Rust’s.

Inside an interpolated expression, options are provided after a : separator.

print! '{number.pi:𝜋^8.2}'
check! 𝜋𝜋3.14𝜋𝜋

Minimum Width and Alignment

A minimum width can be specified, ensuring that the formatted value takes up at least that many characters.

foo = "abcd"
print! '_{foo:8}_'
check! _abcd    _

The minimum width can be prefixed with an alignment modifier:

  • < - left-aligned
  • ^ - centered
  • > - right-aligned
foo = "abcd"
print! '_{foo:^8}_'
check! _  abcd  _

All values are left-aligned if an alignment modifier isn’t specified, except for numbers which are right-aligned by default.

x = 1.2
print! '_{x:8}_'
check! _     1.2_

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 ).

x = 1.2
print! '_{x:~<8}_'
check! _1.2~~~~~_

For numbers, the minimum width can be prefixed with 0, which will pad the number to the specified width with zeroes.

x = 1.2
print! '{x:06}'
check! 0001.2

Maximum Width / Precision

A maximum width for the interpolated expression can be specified following a . character.

foo = "abcd"
print! '{foo:_^8.2}'
check! ___ab___

For numbers, the maximum width acts as a ‘precision’ value, or in other words, the number of decimal places that will be rendered for the number.

x = 1 / 3
print! '{x:.4}'
check! 0.3333

Representation

Values can be formatted with alternative representations, with representations chosen with a character at the end of the format options.

  • ? - The value will be formatted with additional debug information when available.

The following representations are only supported for numbers:

  • e - exponential (lower-case)
  • E - exponential (upper-case)

The following representations are only supported for integers:

  • b - binary
  • o - octal
  • x - hexadecimal (lower-case)
  • X - hexadecimal (upper-case)
z = 60
print! '{z:?}'
check! 60
print! '{z:x}'
check! 3c
print! '0x{z:X}'
check! 0x3C
print! '{z:o}'
check! 74
print! '0b{z:08b}'
check! 0b00111100
print! '{z * 1000:e}'
check! 6e4
print! '{z * 1_000_000:E}'
check! 6E7

Advanced Functions

Functions in Koto have some advanced features that are worth exploring.

Captured Variables

When a variable is accessed in a function that wasn’t declared locally, then it gets captured by copying it into the function.

x = 1

my_function = |n|
  # x is assigned outside the function,
  # so it gets captured when the function is created.
  n + x

# Reassigning x here doesn't modify the value
# of x that was captured when my_function was created.
x = 100

print! my_function 2
check! 3

This behavior is different to many other languages, where captures are often taken by reference rather than by copy.

It’s also worth noting that captured variables will have the same starting value each time the function is called.

x = 99
f = ||
  # Modifying x only happens with a local copy during a function call.
  # The value of x at the start of the call matches when the value it had when
  # it was captured.
  x += 1

print! f(), f(), f()
check! (100, 100, 100)

To modify captured values, use a container (like a map) to hold on to mutable data.

data = {x: 99}

f = ||
  # The data map gets captured by the function,
  # and its contained values can be modified between calls.
  data.x += 1

print! f(), f(), f()
check! (100, 101, 102)

Variadic Functions

A variadic function can be created by appending ... to the last argument. When the function is called, any extra arguments will be collected into a tuple.

f = |a, b, others...|
  print "a: {a}, b: {b}, others: {others}"

f 1, 2, 3, 4, 5
check! a: 1, b: 2, others: (3, 4, 5)
f 10, 20
check! a: 10, b: 20, others: ()

Optional Arguments

Arguments can be made optional by assigning default values.

f = |a, b = 2, c = 3|
  print a, b, c

f 1
check! (1, 2, 3)
f 1, -2
check! (1, -2, 3)
f 1, -2, -3
check! (1, -2, -3)

Default argument values behave like captured variables, with the same value being applied each time the function is called.

f = |x = 10|
  x += 1
  x

print! f()
check! 11
print! f()
check! 11

All arguments following an optional argument must also be optional, unless the last argument is variadic.

# f = |a = 1, b| a, b
#             ^ Error!

f = |a = 1, b...| a, b
#           ^ Ok!

print! f()
check! (1, ())
print! f(1, 2, 3)
check! (1, (2, 3))

Container Argument Unpacking

Functions that expect containers as arguments can unpack the contained elements directly in the argument declaration by using parentheses.

# A function that sums a container with three contained values
f = |(a, b, c)| a + b + c

x = [100, 10, 1]
print! f x
check! 111

Any container that supports indexing operations (like lists and tuples) with a matching number of elements will be unpacked, otherwise an error will be thrown.

Unpacked arguments can also be nested.

# A function that sums elements from nested containers
f = |((a, b), (c, d, e))|
  a + b + c + d + e
x = ([1, 2], [3, 4, 5])
print! f x
check! 15

Ellipses can be used to unpack any number of elements at the start or end of a container.

f = |(..., last)| last * last
x = (1, 2, 3, 4)
print! f x
check! 16

A name can be added to ellipses to assign the unpacked elements.

f = |(first, others...)| first * others.sum()
x = (10, 1, 2, 3)
print! f x
check! 60

Ignoring Arguments

As with assignments, _ can be used to ignore function arguments.

# A function that sums the first and third elements of a container
f = |(a, _, c)| a + c

print! f [100, 10, 1]
check! 101
my_map = {foo1: 1, bar1: 2, foo2: 3, bar2: 4}

print! my_map
  .keep |(key, _value)| key.starts_with 'foo'
  .to_tuple()
check! (('foo1', 1), ('foo2', 3))

Packed Call Arguments

When calling a function, a packed argument is any argument to which ... is appended. The runtime will replace the packed argument with the output of iterating over the argument’s contents. Any iterable value can be unpacked.

f = |a, b, c| a + b + c

x = 10, 20, 30
print! f x...
check! 60

print! f (1..10).take(3)...
check! 6

This is especially useful when variadic arguments need to be forwarded to another variadic function.

f = |args...|
  for i, arg in args.enumerate()
    print '{i}: {arg}'

g = |args...| f args...
g 2, 4, 6, 8
check! 0: 2
check! 1: 4
check! 2: 6
check! 3: 8

More than one argument can be unpacked during a call.

f = |args...|
  for i, arg in args.enumerate()
    print '{i}: {arg}'

x = 10, 20
y = 99, 100
f x..., -1, y...
check! 0: 10
check! 1: 20
check! 2: -1
check! 3: 99
check! 4: 100

Objects and Metamaps

Value types with custom behaviour can be defined in Koto through the concept of objects.

An object is any map that includes one or more metakeys (keys prefixed with @), that are stored in the object’s metamap. Whenever operations are performed on the object, the runtime checks its metamap for corresponding metakeys.

In the following example, the addition and multiply-assignment operators are implemented for a custom Foo object:

# Declare a function that makes Foo objects
foo = |n|
  data: n

  # Declare the object's type
  @type: 'Foo'

  # Implement the addition operator
  @+: |other|
    # A new Foo is made using the result
    # of adding the two data values together
    foo self.data + other.data

  # Implement the multiply-assignment operator
  @*=: |other|
    self.data *= other.data
    self

a = foo 10

print! type a
check! Foo

b = foo 20

print! (a + b).data
check! 30

a *= b
print! a.data
check! 200

Arithmetic Operators

Arithmetic operators used in binary expressions can all be implemented in an object’s metamap by implementing functions for the appropriate metakeys.

When the object is on the left-hand side (LHS) of the expression the metakeys are @+, @-, @*, @/, and @%.

If the value on the LHS of the expression doesn’t support the operation and the object is on the right-hand side (RHS), then the metakeys are @r+, @r-, @r*, @r/, and @r%.

If your type only supports an operation when the input has a certain type, then throw a koto.unimplemented error to let the runtime know that the RHS value should be checked. The runtime will catch the error and then attempt the operation with the implementation provided by the RHS value.

foo = |n|
  data: n

  @type: 'Foo'

  # The * operator when the object is on the LHS
  @*: |rhs|
    match type rhs
      'Foo' then foo self.data * rhs.data
      'Number' then foo self.data * rhs
      else throw koto.unimplemented

  # The * operator when the object is on the RHS
  @r*: |lhs| foo lhs * self.data

a = foo 2
b = foo 3

print! (a * b).data
check! 6

print! (10 * a).data
check! 20

Comparison Operators

Comparison operators can also be implemented in an object’s metamap by using the metakeys @==, @!=, @<, @<=, @>, and @>=.

By default, @!= will invert the result of calling @==, so it’s only necessary to implement it for types with special equality properties.

Types that represent a total order only need to implement @< and @==, and the runtime will automatically derive results for @<=, @>, and @>=.

foo = |n|
  data: n

  @==: |other| self.data == other.data
  @<: |other| self.data < other.data

a = foo 100
b = foo 200

print! a == a
check! true

# The result of != is derived by inverting the result of @==
print! a != a
check! false

print! a < b
check! true

# The result of > is derived from the implementations of @< and @==
print! a > b
check! false

Metakeys

@negate

The @negate metakey overrides the negation operator.

foo = |n|
  data: n
  @negate: || foo -self.data

x = -foo(100)
print! x.data
check! -100

@size and @index

The @size metakey defines how an object should report its size, while the @index metakey defines which values should be returned when indexing is performed.

If @size is implemented, then @index should also be implemented.

foo = |data|
  data: data
  @size: || size self.data
  @index: |index| self.data[index]

x = foo ('a', 'b', 'c')
print! size x
check! 3
print! x[1]
check! b

Implementing @size and @index allows an object to participate in argument unpacking.

The @index implementation can support indexing by any input values that make sense for your object type, however for argument unpacking to work correctly, the runtime expects that indexing should be supported for at least single indices and ranges.

foo = |data|
  data: data
  @size: || size self.data
  @index: |index| self.data[index]

x = foo (10, 20, 30, 40, 50)

# Unpack the first two elements in the value passed to the function and multiply them
multiply_first_two = |(a, b, ...)| a * b
print! multiply_first_two x
check! 200

# Inspect the first element in the object
print! match x
  (first, others...) then 'first: {first}, remaining: {size others}'
check! first: 10, remaining: 4

@index_mut

The @index_mut metakey defines how an object should behave when index-assignment is used.

The given value should be a function that takes an index as the first argument, with the second argument being the value to be assigned.

foo = |data|
  data: data
  @index: |index| self.data[index]
  @index_mut: |index, value| self.data[index] = value

x = foo ['a', 'b', 'c']
x[1] = 'hello'
print! x[1]
check! hello

@call

The @call metakey defines how an object should behave when its called as a function.

foo = |n|
  data: n
  @call: ||
    self.data *= 2
    self.data

x = foo 2
print! x()
check! 4
print! x()
check! 8

@iterator

The @iterator metakey defines how iterators should be created when an object is used in an iterable context.

When called, @iterator should return an iterable value that will then be used for iterator operations.

foo = |n|
  # Return a generator that yields the three numbers following n
  @iterator: ||
    yield n + 1
    yield n + 2
    yield n + 3

print! foo(0).to_tuple()
check! (1, 2, 3)

print! foo(100).to_list()
check! [101, 102, 103]

Note that this key will be ignored if the object also implements @next, which implies that the object is already an iterator.

@next

The @next metakey allows for objects to behave as iterators.

Whenever the runtime needs to produce an iterator from an object, it will first check the metamap for an implementation of @next, before looking for @iterator.

The @next function will be called repeatedly during iteration, with the returned value being used as the iterator’s output. When the returned value is null then the iterator will stop producing output.

foo = |start, end|
  start: start
  end: end
  @next: ||
    if self.start < self.end
      result = self.start
      self.start += 1
      result
    else
      null

print! foo(10, 15).to_tuple()
check! (10, 11, 12, 13, 14)

@next_back

The @next_back metakey is used by iterator.reversed when producing a reversed iterator.

The runtime will only look for @next_back if @next is implemented.

foo =
  n: 0
  @next: || self.n += 1
  @next_back: || self.n -= 1

print! foo
  .skip 3 # 0, 1, 2
  .reversed()
  .take 3 # 2, 1, 0
  .to_tuple()
check! (2, 1, 0)

@display

The @display metakey defines how an object should be represented when displaying the object as a string.

foo = |n|
  data: n
  @display: || 'Foo({self.data})'

print! foo 42
check! Foo(42)

x = foo -1
print! "The value of x is '{x}'"
check! The value of x is 'Foo(-1)'

@debug

The @debug metakey defines how an object should be represented when displaying the object in a debug context, e.g. when using debug, or when the ? representation is used in an interpolated expression.

foo = |n|
  data: n
  @display: || 'Foo({self.data})'
  @debug: || '!!{self}!!'

print! "{foo(123):?}"
check! !!Foo(123)!!

If @debug isn’t defined, then @display will be used as a fallback.

@type

The @type metakey takes a string which is used when checking a value’s type, e.g. with type checks or koto.type.

foo = |n|
  data: n
  @type: "Foo"

let x: Foo = foo 42
print! koto.type x
check! Foo

@base

Objects can inherit properties and behavior from other values, establishing a base value through the @base metakey. This allows objects to share common functionality while maintaining their own unique attributes.

In the following example, two kinds of animals are created that share the speak function from their base value.

animal = |name|
  @type: 'Animal'
  name: name
  speak: || '{self.noise}! My name is {self.name}!'

dog = |name|
  @base: animal name
  @type: 'Dog'
  noise: 'Woof'

cat = |name|
  @base: animal name
  @type: 'Cat'
  noise: 'Meow'

let fido: Dog = dog 'Fido'
print! fido.speak()
check! Woof! My name is Fido!

let smudge: Cat = cat 'Smudge'
print! smudge.speak()
check! Meow! My name is Smudge!

# Type checks will refer to base class @type entries when needed
let an_animal: Animal = if true then fido else smudge
print! an_animal.name
check! Fido

@meta

The @meta metakey allows named metakeys to be added to the metamap. Metakeys defined with @meta are accessible via . access, similar to regular object keys, but they don’t appear as part of the object’s main data entries when treated as a regular map.

foo = |n|
  data: n
  @meta hello: "Hello!"
  @meta get_info: ||
    info = match self.data
      0 then "zero"
      n if n < 0 then "negative"
      else "positive"
    "{self.data} is {info}"

x = foo -1
print! x.hello
check! Hello!

print x.get_info()
check! -1 is negative

print map.keys(x).to_tuple()
check! ('data')

Sharing Metamaps

Metamaps can be shared between objects by using Map.with_meta, which helps to avoid inefficient duplication when creating a lot of objects.

In the following example, behavior is overridden in a single metamap, which is then shared between object instances.

# Create an empty map for global values
global = {}

# Define a function that makes a Foo object
foo = |data|
  # Make a new map that contains `data`,
  # and then attach a shared copy of the metamap from foo_meta.
  {data}.with_meta global.foo_meta

# Define some metakeys in foo_meta
global.foo_meta =
  # Declare the object's type
  @type: 'Foo'

  # Override the + operator
  @+: |other| foo self.data + other.data

  # Define how the object should be displayed
  @display: || "Foo({self.data})"

print! (foo 10) + (foo 20)
check! Foo(30)

Error Handling

Errors can be thrown in the Koto runtime, which then cause the runtime to stop execution.

A try / catch expression can be used to catch any thrown errors, allowing execution to continue. An optional finally block can be used for cleanup actions that need to performed whether or not an error was caught.

x = [1, 2, 3]
try
  # Accessing an invalid index will throw an error
  print x[100]
catch error
  print "Caught an error"
finally
  print "...and finally"
check! Caught an error
check! ...and finally

throw can be used to explicitly throw an error when an exceptional condition has occurred.

throw accepts strings or objects that implement @display.

f = || throw "!Error!"

try
  f()
catch error
  print "Caught an error: '{error}'"
check! Caught an error: '!Error!'

Type checks on catch blocks

Type hints can also be used in try expressions to implement different error handling logic depending on the type of error that has been thrown. A series of catch blocks can be added to the try expression, each catching an error that has a particular type.

The final catch block needs to not have a type check so that it can catch any errors that were missed by the other blocks.

f = || throw 'Throwing a String'

try
  f()
catch n: Number
  print 'An error occurred: {n}'
catch error: String
  print error
catch other
  print 'Some other error occurred: {other}'
check! Throwing a String

Modules

Koto includes a module system that helps you to organize and re-use your code when your program grows too large for a single file.

import

Values from other modules can be brought into the current scope using import.

from list import last
from number import abs

x = [1, 2, 3]
print! last x
check! 3

print! abs -42
check! 42

Multiple values from a single module can be imported at the same time.

from tuple import contains, first, last

x = 'a', 'b', 'c'
print! first x
check! a
print! last x
check! c
print! contains x, 'b'
check! true

Imported values can be renamed using as for clarity or to avoid conflicts.

from list import first as list_first
from tuple import first as tuple_first
print! list_first [1, 2]
check! 1
print! tuple_first (3, 2, 1)
check! 3

export

A value can only be imported from a module if the module has exported it.

export is used to add values to the current module’s exports map, making them available to be imported by other modules.

##################
# my_module.koto #
##################

# hello is a local variable, and isn't exported
hello = 'Hello'

# export say_hello to make it available to other modules
export say_hello = |name| '{hello}, {name}!'

##################
#   other.koto   #
##################

from my_module import say_hello

say_hello 'Koto'
check! 'Hello, Koto!'

To add a type check to an exported assignment, use a let expression:

export let foo: Number = -1

export also accepts maps, or any other iterable value that yields a series of key/value pairs. This is convenient when exporting a lot of values, or generating exports programatically.

##################
# my_module.koto #
##################

# Define some local values
a, b, c = 1, 2, 3

# Inline maps allow for shorthand syntax
export { a, b, c, foo: 42 }

# Map blocks can also be used with export
export
  bar: 99
  baz: 'baz'

# Any iterable value that yields key/value pairs can be used with export
export (1..=3).each |i| 'generated_{i}', i

Once a value has been exported, it becomes available anywhere in the module.

get_x = ||
  # x hasn't been created yet. When the function is called, the runtime
  # will check the exports map for a matching value.
  x

export x = 123

print! get_x()
check! 123

# A function that exports `y` with the given value
export_y = |value|
  export y = value

# y hasn't been exported yet, so attempting to access it now throws an error.
print! try
  y
catch _
  'y not found'
check! y not found

# Calling export_y adds y to the exports map
export_y 42
print! y
check! 42

Assigning a new value locally to a previously exported variable won’t change the exported value. If you need to update the exported value, then it needs to be re-exported.

export x = 99

# Reassigning a new value to x locally doesn't affect the previously exported value
print! x = 123
check! 123

# x has a local value of 123, but the exported value of x is still 99.
export x = -1
# x now has an exported and local value of -1
print! x
check! -1

@main

A module can export a @main function, which will be called after the module has been compiled and successfully initialized.

The use of export is optional when assigning to module metakeys like @main.

##################
# my_module.koto #
##################

export say_hello = |name| 'Hello, {name}!'

# Equivalent to `export @main = ...`
@main = || print '`my_module` initialized'

##################
#   other.koto   #
##################

from my_module import say_hello
check! `my_module` initialized

say_hello 'Koto'
check! 'Hello, Koto!'

Module Paths

When looking for a module, import will look for a .koto file with a matching name, or for a folder with a matching name that contains a main.koto file.

E.g. When an import foo expression is run, then a foo.koto file will be looked for in the same location as the current script, and if foo.koto isn’t found then the runtime will look for foo/main.koto.

Testing

Koto includes a simple testing framework that allows you to automatically check that your code is behaving as you would expect.

Assertions

The core library includes a collection of assertion functions which throw errors if a given condition isn’t met.

The assertion functions are found in the test module, and are included by default in the prelude.

try
  assert 1 + 1 == 3
catch error
  print 'An assertion failed'
check! An assertion failed

try
  assert_eq 'hello', 'goodbye'
catch error
  print 'An assertion failed'
check! An assertion failed

Module Tests

Tests can be added to a module by exporting @test functions. A test function is considered to have failed if it throws an error (e.g. from an assertion).

If Koto is configured to run tests, then the tests will be run after a module has been successfully initialized. If the module also exports @main then it will be called after all tests have run successfully.

The CLI doesn’t enable tests by default when running scripts, but they can be enabled via a flag.

##################
# my_module.koto #
##################

export say_hello = |name| 'Hello, {name}!'

@main = || print '`my_module` initialized'

@test say_hello = ||
  print 'Running @test say_hello'
  assert_eq say_hello('Test'), 'Hello, Test!'

##################
#   other.koto   #
##################

from my_module import say_hello
check! Running @test say_hello
check! `my_module` initialized

@pre_test and @post_test functions can be implemented alongside tests for setup and cleanup operations. @pre_test will be run before each @test, and @post_test will be run after.

##################
# my_module.koto #
##################

export say_hello = |name| 'Hello, {name}!'

@main = || print '`my_module` initialized'

@pre_test = ||
  print 'In @pre_test'

@post_test = ||
  print 'In @post_test'

@test say_hello_1 = ||
  print 'Running @test say_hello_1'
  assert_eq say_hello('One'), 'Hello, One!'

@test say_hello_2 = ||
  print 'Running @test say_hello_2'
  assert_eq say_hello('Two'), 'Hello, Two!'

##################
#   other.koto   #
##################

from my_module import say_hello
check! In @pre_test
check! Running @test say_hello_1
check! In @post_test
check! In @pre_test
check! Running @test say_hello_2
check! In @post_test
check! `my_module` initialized

Running Tests Manually

Tests can be run manually by calling test.run_tests with a map that contains @test functions.

my_tests =
  @test add: || assert_eq 1 + 1, 2
  @test subtract: || assert_eq 1 - 1, 0

test.run_tests my_tests

Algebraeon — Exact Algebra

Algebraeon is koto-calc’s exact-algebra toolkit. It keeps mathematical values as integers, reduced fractions, algebraic roots, and symbolic structures rather than silently rounding them to floating-point approximations. That means you can factor large integers, compare algebraic numbers, invert rational matrices, and calculate in finite fields without losing information.

Start here, then go deeper: the Full Reference documents every available constructor, method, and module-level function.

Import the types you need from the built-in algebraeon module:

from algebraeon import N, Z, Q, Zn, Poly, Mat, Alg

Naming convention

Basic number domains use their ASCII mathematical symbols: N for natural numbers, Z for integers, and Q for rationals. Zn(n) denotes the residue ring ℤ/nℤ. Other structures use descriptive PascalCase names or established acronyms such as Poly, Mat, FF, and CF.

For compatibility with 0.1 scripts, NN, ZZ, and ZZn remain aliases for N, Z, and Zn; values created through an alias still have the canonical runtime type name.

The exact-algebra toolbox

Type or familyWhat it does
NArbitrary-precision natural numbers, with primes, factorization, divisors, and combinatorics.
ZArbitrary-precision signed integers and integer number theory.
QReduced rational numbers, so values such as 1/3 stay exact.
QSqrtElements of quadratic fields Q(sqrt(d)), including exact conjugates, norms, and inverses.
AlgExact real algebraic numbers represented as isolated roots of polynomials.
ComplexAlgExact complex algebraic numbers, including polynomial roots and the imaginary unit.
CFFinite and periodic continued fractions, convergents, and exact rational values.
FFPrime and extension finite fields GF(p^k) with exact field arithmetic.
Ideal / ZnIdeals of the integers and residue rings such as Z/12Z.
PolyUnivariate polynomials over Z or Q, with evaluation, gcd, derivatives, and factorization.
MultiPolySymbolic multivariate integer polynomials with evaluation and symmetric-polynomial tools.
PolyQuotExact number fields presented as quotient rings Q[x]/(f).
MatInteger and rational matrices with determinants, exact inverses, and LLL reduction.
PermPermutations with composition, inverses, signs, and cycle decomposition.
GroupFinite groups represented by multiplication tables, with standard constructors.
QuatHamilton quaternions over the rationals, with conjugate, norm, and inverse.
Stirling numbersExact first- and second-kind Stirling numbers via N and Z.

A quick tour

Each example below is ready to paste into a koto-calc script or REPL.

Fractions stay fractions

from algebraeon import Q

third = Q(1, 3)
print third + Q(1, 6) # 1/2

There is no intermediate binary floating-point value: the result is the reduced fraction 1/2.

Factor arbitrary-precision integers

from algebraeon import N

print N(12345).factor() # [(3, 1), (5, 1), (823, 1)]

Work with an exact square root

from algebraeon import Alg, Q

sqrt2 = Alg([-2, 0, 1])[1]
print sqrt2.min_poly() # -2 + x^2
print sqrt2 > Q(7, 5) # true

sqrt2 is stored as an isolated root of x^2 - 2. Its usual display, 1.414213562, is only a readable approximation; the minimal polynomial and comparisons remain exact.

Factor polynomials

Coefficients are listed from the constant term upwards, so the polynomial below is 6 - 5x + x^2.

from algebraeon import Poly

f = Poly([6, -5, 1])
print f.factor() # [(-2 + x, 1), (-3 + x, 1)]

Invert a matrix without rounding

from algebraeon import Mat

m = Mat([[1, 2], [3, 4]])
print m.inverse()     # [[-2, 1], [3/2, -1/2]]
print m.inverse() * m # [[1, 0], [0, 1]]

Explore a finite group

from algebraeon import Group

c4 = Group.cyclic(4)
print c4          # C4 (size 4)
print c4.order(1) # 4

Group also constructs dihedral, symmetric, alternating, Klein four, and quaternion groups, all backed by finite multiplication tables.

Calculate in a finite field

from algebraeon import FF

gf7 = FF(7)
x = gf7.of(3)
print x.inverse()     # 5
print x * x.inverse() # 1

Prime fields are only the beginning: FF(p, k) constructs extension fields using Algebraeon’s Conway-polynomial database.

Where to go next

The Algebraeon Full Reference contains detailed type signatures and validated examples for the complete API. You can also read about the underlying Rust library on the Algebraeon crate page.

algebraeon

Algebraeon support for Koto: arbitrary precision arithmetic, number theory, polynomials, and matrices.

The module provides the types N (natural numbers), Z (integers), Q (rationals), Poly (univariate polynomials), Mat (matrices), Quat (Hamilton quaternions) and Alg (real algebraic numbers), plus the module-level functions gcd and lcm.

Naming convention

Basic number domains use ASCII forms of their mathematical symbols: N (naturals), Z (integers), and Q (rationals). Zn(n) is the residue ring ℤ/nℤ. Other structures use PascalCase names or established acronyms, for example Poly, Mat, ComplexAlg, FF, and CF.

The former constructors NN, ZZ, and ZZn remain compatibility aliases during the 0.2 transition. They construct the same canonical N, Z, and Zn runtime types.

N

|| -> Iterator
|Number| -> N

Natural (non-negative integer) values with arbitrary precision.

Called with no arguments, N() returns an iterator over the natural numbers 0, 1, 2, ....

Example

print! N(5).factorial()
check! 120

print! N(5) - N(3)
check! 2

print! N().take(4).to_list()
check! [0, 1, 2, 3]

N.bitcount

|N| -> Number

Returns the number of bits needed to represent the value.

Example

print! N(5).bitcount()
check! 3

N.is_prime

|N| -> Bool

Returns true if the value is prime.

Example

print! N(17).is_prime()
check! true

print! N(12).is_prime()
check! false

N.is_squarefree

|N| -> Bool

Returns true if the value has no repeated prime factors.

Example

print! N(10).is_squarefree()
check! true

print! N(12).is_squarefree()
check! false

N.factor

|N| -> [(N, N)]

Returns the prime factorization of the value as a list of (prime, exponent) tuples.

Example

print! N(60).factor()
check! [(2, 2), (3, 1), (5, 1)]

N.factorial

|N| -> N

Returns the factorial of the value.

Example

print! N(5).factorial()
check! 120

N.divisors

|N| -> [N]

Returns the value’s divisors in ascending order.

Example

print! N(12).divisors()
check! [1, 2, 3, 4, 6, 12]

N.euler_totient

|N| -> N

Returns the value of Euler’s totient function, the count of positive integers up to the value that are coprime to it.

Example

print! N(10).euler_totient()
check! 4

N.is_square

|N| -> Bool

Returns true if the value is a perfect square.

Example

print! N(16).is_square()
check! true

print! N(18).is_square()
check! false

N.sqrt_floor

|N| -> N

Returns the floor of the square root of the value.

Example

print! N(17).sqrt_floor()
check! 4

N.sqrt_ceil

|N| -> N

Returns the ceiling of the square root of the value.

Example

print! N(17).sqrt_ceil()
check! 5

N.is_power_test

|N| -> (Bool, N?, N?)

Returns (true, base, exponent) if the value can be written as base^exponent with exponent > 1, otherwise (false, null, null).

Example

print! N(8).is_power_test()
check! (true, 2, 3)

print! N(6).is_power_test()
check! (false, null, null)

N.primality_test

|N| -> String

Returns 'prime' or 'composite' (both 0 and 1 are reported as 'composite').

Example

print! N(17).primality_test()
check! prime

print! N(12).primality_test()
check! composite

N.primes

|| -> Iterator

Returns an iterator over the prime numbers.

Example

print! N.primes().take(6).to_list()
check! [2, 3, 5, 7, 11, 13]

Z

|Number| -> Z

Integer values with arbitrary precision.

Z supports arithmetic (+ - *), comparisons, and assignment operators (+= -= *=) with other Z values, N values, and plain numbers.

Example

print! Z(5) + Z(-3)
check! 2

print! Z(4) * Z(-2)
check! -8

print! Z(5) + N(3)
check! 8

Z.abs

|Z| -> N

Returns the absolute value of the integer as an N.

Example

print! Z(-9).abs()
check! 9

Z.is_irreducible

|Z| -> Bool

Returns true if the value is irreducible (i.e. a prime, up to sign).

Example

print! Z(7).is_irreducible()
check! true

print! Z(9).is_irreducible()
check! false

Z.is_square

|Z| -> Bool

Returns true if the value is a perfect square.

Example

print! Z(9).is_square()
check! true

print! Z(10).is_square()
check! false

Z.factor

|Z| -> [(Z, N)]

Returns the prime factorization of the value as a list of (prime, exponent) tuples. The sign is ignored.

Example

print! Z(-12).factor()
check! [(2, 2), (3, 1)]

Z.divmod

|Z, Z| -> (Z, Z)

Returns the quotient and remainder of a floor division, with a non-negative remainder.

Example

print! Z(-7).divmod(Z(3))
check! (-3, 2)

Z.div_floor

|Z, Z| -> Z

Returns the quotient of a floor division.

Example

print! Z(-7).div_floor(Z(3))
check! -3

print! Z(-13).div_floor(Z(5))
check! -3

Z.mod

|Z, Z| -> Z

Returns the non-negative remainder of a floor division, coherent with div_floor.

Example

print! Z(-7).mod(Z(3))
check! 2

print! Z(-13).mod(Z(5))
check! 2

Q

|Number| -> Q
|Number, Number| -> Q

Rational numbers, stored as reduced fractions num / den.

The denominator must be non-zero. A single argument is treated as a whole number. Q supports arithmetic (+ - * /), comparisons, and assignment operators (+= -= *= /=) with other Q values, N values, Z values and plain numbers.

The display form is the reduced fraction num/den, or just num when the denominator is 1.

Example

print! Q(6, 4)
check! 3/2

print! Q(1, 2) + Q(1, 3)
check! 5/6

print! Q(1, 2) / Q(3, 4)
check! 2/3

print! Q(0.5)
check! 1/2

print! Q(3)
check! 3

Q.num

|Q| -> Z

Returns the numerator of the reduced fraction.

Example

print! Q(3, 2).num()
check! 3

print! Q(-3, 2).num()
check! -3

Q.den

|Q| -> N

Returns the denominator of the reduced fraction.

Example

print! Q(3, 2).den()
check! 2

Q.is_integer

|Q| -> Bool

Returns true if the value is a whole number.

Example

print! Q(4, 2).is_integer()
check! true

print! Q(3, 2).is_integer()
check! false

Q.is_square

|Q| -> Bool

Returns true if the value is a perfect square.

Example

print! Q(4, 9).is_square()
check! true

print! Q(2, 3).is_square()
check! false

Q.sqrt_if_square

|Q| -> Q?

Returns the square root of the value if it is a perfect square, otherwise null.

Example

print! Q(4, 9).sqrt_if_square()
check! 2/3

print! Q(2, 3).sqrt_if_square()
check! null

Q.height

|Q| -> N

Returns the height of the value: max(|num|, den) of the reduced fraction.

Example

print! Q(3, 2).height()
check! 3

print! Q(-2, 5).height()
check! 5

Q.to_float

|Q| -> Number

Converts the value to a floating point number.

Example

print! Q(3, 2).to_float()
check! 1.5

Q.to_zz

|Q| -> Z

Converts the value to a Z (the value must be a whole number).

Example

print! Q(3).to_zz()
check! 3

print! Q(4, 2).to_zz()
check! 2

Q.to_nn

|Q| -> N

Converts the value to an N (the value must be a non-negative whole number).

Example

print! Q(4, 2).to_nn()
check! 2

print! Q(0).to_nn()
check! 0

Poly

|List| -> Poly

Univariate polynomials over Z or Q.

The constructor takes a list of coefficients in ascending order, with the first element being the constant term: Poly([6, -5, 1]) represents 6 - 5x + x^2.

The coefficients are stored as Z when all of them are integers, and promoted to Q when any of them is a fraction. Arithmetic (+ - *) works with other polynomials and with N/Z/Q scalars, promoting Z to Q when needed.

The display form shows the terms in ascending order of degree, e.g. 6 - 5x + x^2.

Example

a = Poly([6, -5, 1])
print! a
check! 6 - 5x + x^2

print! a + Poly([1, 1])
check! 7 - 4x + x^2

print! a * Q(1, 2)
check! 3 - (5/2)x + (1/2)x^2

Poly.degree

|Poly| -> N

Returns the degree of the polynomial (the zero polynomial has degree 0).

Example

print! Poly([6, -5, 1]).degree()
check! 2

print! Poly([7]).degree()
check! 0

Poly.coeffs

|Poly| -> [Z] | [Q]

Returns the coefficients in ascending order, starting with the constant term.

Example

print! Poly([6, -5, 1]).coeffs()
check! [6, -5, 1]

print! Poly([3, Q(-5, 2), Q(1, 2)]).coeffs()
check! [3, -5/2, 1/2]

Poly.eval

|Poly, x: Number| -> Z | Q

Evaluates the polynomial at x (which may be a Number, N, Z or Q).

Example

a = Poly([6, -5, 1])
print! a.eval(2)
check! 0

print! a.eval(Q(1, 2))
check! 15/4

Poly.derivative

|Poly| -> Poly

Returns the derivative of the polynomial.

Example

print! Poly([6, -5, 1]).derivative()
check! -5 + 2x

print! Poly([5]).derivative()
check! 0

Poly.gcd

|Poly, Poly| -> Poly

Returns the monic greatest common divisor of two polynomials, promoting to Q if needed.

Example

print! Poly([6, -5, 1]).gcd(Poly([2, -3, 1]))
check! -2 + x

Poly.factor

|Poly| -> [(Poly, N)]

Returns the irreducible factorization of the polynomial as a list of (factor, exponent) tuples.

Example

print! Poly([6, -5, 1]).factor()
check! [(-2 + x, 1), (-3 + x, 1)]

print! Poly([1, 0, 1]).factor()
check! [(1 + x^2, 1)]

Mat

|List of lists| -> Mat

Matrices over Z or Q, given row by row: Mat([[1, 2], [3, 4]]) is the 2x2 matrix with rows [1, 2] and [3, 4].

The entries are stored as Z when all of them are integers, and promoted to Q when any of them is a fraction. Arithmetic (+ - *) works with other matrices and with N/Z/Q scalars.

The display form is a list of rows, e.g. [[1, 2], [3, 4]].

Example

m = Mat([[1, 2], [3, 4]])
print! m
check! [[1, 2], [3, 4]]

print! m * Mat([[5, 6], [7, 8]])
check! [[19, 22], [43, 50]]

print! m.det()
check! -2

Mat.rows

|Mat| -> N

Returns the number of rows.

Example

print! Mat([[1, 2], [3, 4]]).rows()
check! 2

Mat.cols

|Mat| -> N

Returns the number of columns.

Example

print! Mat([[1, 2], [3, 4]]).cols()
check! 2

Mat.at

|Mat, row: Number, col: Number| -> Z | Q

Returns the entry at the given row and column (zero-based).

Example

print! Mat([[1, 2], [3, 4]]).at(1, 0)
check! 3

Mat.transpose

|Mat| -> Mat

Returns the transposed matrix.

Example

print! Mat([[1, 2], [3, 4]]).transpose()
check! [[1, 3], [2, 4]]

Mat.mul

|Mat, Mat| -> Mat

Returns the matrix product (also available as the * operator).

Example

m = Mat([[1, 2], [3, 4]])
print! m.mul(Mat([[5, 6], [7, 8]]))
check! [[19, 22], [43, 50]]

Mat.det

|Mat| -> Z | Q

Returns the determinant (only defined for square matrices).

Example

print! Mat([[1, 2], [3, 4]]).det()
check! -2

Mat.inverse

|Mat| -> Mat

Returns the inverse of the matrix over Q (a Z matrix is promoted to Q). An error is thrown if the matrix is singular.

Example

m = Mat([[1, 2], [3, 4]])
print! m.inverse()
check! [[-2, 1], [3/2, -1/2]]

print! m.inverse() * m
check! [[1, 0], [0, 1]]

Mat.lll

|Mat| -> Mat

Returns the LLL-reduced basis of the lattice generated by the rows of an integer matrix. An error is thrown if the matrix contains fractions.

Example

print! Mat([[1, 1], [1, 2]]).lll()
check! [[-1, 0], [0, 1]]

gcd

|N, N| -> N

Returns the greatest common divisor of two natural numbers.

Example

print! gcd(N(12), N(18))
check! 6

lcm

|N, N| -> N

Returns the least common multiple of two natural numbers.

Example

print! lcm(N(4), N(6))
check! 12

Quat

|Number, Number, Number, Number| -> Quat

Hamilton quaternions over Q, constructed from four coefficients a + bi + cj + dk (each Number/N/Z/Q argument is promoted to Q).

Multiplication is the Hamilton product, defined by i^2 = j^2 = k^2 = ijk = -1 with i*j = k, j*k = i and k*i = j (the cross terms anti-commute: j*i = -k, …), so multiplication is not commutative. The product is computed directly on the coefficients: the wrapper works around a bug in algebraeon 0.0.17 (upstream issue #244) that produced wrong signs in the i/j cross terms of QuaternionAlgebraStructure::mul.

Quat supports arithmetic (+ - *) with other Quat values and with scalars (Number/N/Z/Q, on either side), negation, and equality (==, !=).

The display form is e.g. 1 + 2i - 3j + (1/2)k: zero terms are omitted, the coefficient 1 is dropped on i/j/k, and fractional coefficients are parenthesized.

Example

q = Quat(1, 2, 3, 4)
print! q
check! 1 + 2i + 3j + 4k

print! q + Quat(1, -2, -3, -4)
check! 2

print! q * 2
check! 2 + 4i + 6j + 8k

print! 1 - q
check! -2i - 3j - 4k

print! Quat(1, 2, 0, 0) * Quat(3, 4, 0, 0)
check! -5 + 10i

# Hamilton rules: i*j = k, j*i = -k, i*i = -1
i = Quat(0, 1, 0, 0)
j = Quat(0, 0, 1, 0)
k = Quat(0, 0, 0, 1)
print! i * j
check! k

print! j * i
check! -k

print! i * i
check! -1

# Associativity: (i*j)*k = -1
print! (i * j) * k
check! -1

Quat.conjugate

|Quat| -> Quat

Returns the conjugate a - bi - cj - dk.

Example

print! Quat(1, 2, 3, 4).conjugate()
check! 1 - 2i - 3j - 4k

print! Quat(1, 2, 3, 4).conjugate().conjugate()
check! 1 + 2i + 3j + 4k

Quat.norm

|Quat| -> Q

Returns the reduced norm a^2 + b^2 + c^2 + d^2.

Example

print! Quat(1, 2, 3, 4).norm()
check! 30

print! Quat(3, -4, 0, 0).norm()
check! 25

Quat.trace

|Quat| -> Q

Returns the reduced trace 2a.

Example

print! Quat(1, 2, 3, 4).trace()
check! 2

print! Quat(3, -4, 0, 0).trace()
check! 6

Quat.coeffs

|Quat| -> (Q, Q, Q, Q)

Returns the four coefficients as a tuple (a, b, c, d).

Example

print! Quat(1, 2, 3, 4).coeffs()
check! (1, 2, 3, 4)

print! Quat(Q(1, 2), 0, 0, 0).coeffs()
check! (1/2, 0, 0, 0)

Quat.to_float

|Quat| -> (Number, Number, Number, Number)

Converts the four coefficients to floating point numbers.

Example

print! Quat(1, 2, 3, 4).to_float()
check! (1.0, 2.0, 3.0, 4.0)

Alg

|Poly | List| -> [Alg]

Real algebraic numbers: exact real roots of polynomials. The constructor takes a Poly (over Z or Q) or a coefficient list (as in Poly([...])), and returns the list of isolated real roots in increasing order, with multiplicity. Polynomials of degree 0 (including the zero polynomial) and polynomials without real roots give an empty list.

Each Alg value is a root with an isolating interval, so comparisons are exact: <, <=, >, >= and == work between two Alg values and between an Alg and a scalar (Q/N/Z/Number, compared exactly as a rational). Arithmetic between algebraic numbers is not exposed.

The display form is a decimal approximation with 9 significant decimals (e.g. 1.414213562), or the exact reduced fraction for rational values (e.g. 6).

Example

roots = Alg(Poly([-2, 0, 1]))  # roots of x^2 - 2
print! roots
check! [-1.414213562, 1.414213562]

print! size(roots)
check! 2

print! roots[0] < roots[1]
check! true

print! Alg(Poly([1, -2, 1]))  # (x - 1)^2, multiplicity kept
check! [1, 1]

print! Alg(Poly([1, 0, 1]))  # x^2 + 1 has no real roots
check! []

print! Alg([-2, 0, 1])  # coefficient list form
check! [-1.414213562, 1.414213562]

Alg.cmp

|Alg, Alg | Number| -> Number

Exact comparison: -1 if smaller, 0 if equal, 1 if greater. The argument may be another Alg or a scalar (Number/N/Z/Q), compared exactly as a rational.

Example

roots = Alg(Poly([-2, 0, 1]))
print! roots[0].cmp(roots[1])
check! -1

sqrt2 = roots[1]
print! sqrt2.cmp(Q(141, 100))  # sqrt(2) > 141/100
check! 1

Alg.accuracy

|Alg| -> Q

Returns the width of the isolating interval (an exact rational). Rational values have accuracy 0.

Example

print! Alg(Poly([-2, 0, 1]))[0].accuracy() > Q(0)
check! true

print! Alg(Poly([-6, 1]))[0].accuracy()  # rational root
check! 0

Alg.refine

|Alg, accuracy: Q | Number| -> Alg

Returns a new Alg whose isolating interval has been refined to the requested (positive) accuracy. Rational values are returned unchanged.

Example

sqrt2 = Alg(Poly([-2, 0, 1]))[1]
r = sqrt2.refine(Q(1, 1000))
print! r.accuracy() < Q(1, 1000)
check! true

print! r.cmp(sqrt2)
check! 0

Alg.min_poly

|Alg| -> Poly

Returns the minimal polynomial of the algebraic number (a Poly over Q). For a rational value n/d it is d*x - n.

Example

sqrt2 = Alg(Poly([-2, 0, 1]))[1]
print! sqrt2.min_poly()
check! -2 + x^2

print! Alg(Poly([-6, 1]))[0].min_poly()
check! -6 + x

Alg.to_float

|Alg| -> Number

Returns a floating point approximation; the isolating interval is refined to accuracy 10^-15 before converting the midpoint.

Example

sqrt2 = Alg(Poly([-2, 0, 1]))[1]
print! sqrt2.to_float()
check! 1.4142135623730951

print! Alg(Poly([-6, 1]))[0].to_float()
check! 6.0

Ideal

|Number, ...| -> Ideal

Ideals of Z are principal. Ideal(a, b, ...) is the ideal generated by its integer arguments; its canonical non-negative generator is displayed with suffix Z. Thus Ideal(4, 6) is 2Z.

Example

print! Ideal(4, 6)
check! 2Z

print! Ideal(6).generator()
check! 6

print! Ideal(6).contains(12)
check! true

print! Ideal(6).contains(5)
check! false

Ideal.contains

|Ideal, Number| -> Bool

Tests whether an integer belongs to the ideal. N and Z values are also accepted.

Example

print! Ideal(6).contains(Z(-12))
check! true

print! Ideal(0).contains(3)
check! false

Ideal.generator

|Ideal| -> N

Returns the canonical non-negative generator.

Example

print! Ideal(-4, 6).generator()
check! 2

Ideal.sum

|Ideal, Ideal| -> Ideal

Returns the sum of two ideals. In Z, this is the ideal generated by the greatest common divisor of their generators.

Example

print! Ideal(6).sum(Ideal(15))
check! 3Z

Ideal.intersect

|Ideal, Ideal| -> Ideal

Returns the intersection of two ideals. In Z, its generator is the least common multiple of the two generators.

Example

print! Ideal(6).intersect(Ideal(15))
check! 30Z

Ideal.product

|Ideal, Ideal| -> Ideal

Returns the product of two ideals.

Example

print! Ideal(6).product(Ideal(15))
check! 90Z

Ideal.quotient

|Ideal, Ideal| -> Ideal

Returns the ideal quotient (I : J) = {x in Z : xJ subset I}.

Example

print! Ideal(6).quotient(Ideal(2))
check! 3Z

Ideal.equals

|Ideal, Ideal| -> Bool

Tests equality of ideals by comparing their canonical generators. The == operator can be used as well.

Example

print! Ideal(4, 6).equals(Ideal(2))
check! true

print! Ideal(6) == Ideal(-6)
check! true

Zn

|Number| -> Zn

The ring Zn(n) is ℤ/nℤ, the ring of integers modulo a positive modulus n. Call .of(x) to create the residue class of an integer. Classes from the same ring support +, -, * and unary -; their display is [x] mod n with a canonical representative.

Example

print! Zn(6)
check! Zn

print! Zn(6).of(7) + Zn(6).of(5)
check! [0] mod 6

print! -Zn(6).of(1)
check! [5] mod 6

Zn.of

|Zn, Number| -> ZnElement

Creates a residue class, reducing the argument modulo the ring modulus. N and Z values are accepted too.

Example

print! Zn(7).of(-1)
check! [6] mod 7

print! Zn(6).of(7) * Zn(6).of(5)
check! [5] mod 6

ZnElement.inverse

|ZnElement| -> ZnElement

Returns a multiplicative inverse. It errors when the residue is not coprime to the modulus.

Example

print! Zn(7).of(5).inverse()
check! [3] mod 7

FF

|Number| -> FF
|Number, Number| -> FF

Finite fields are written GF(p) or GF(p^k), where p is prime. FF(p) constructs the prime field, while FF(p, k) constructs the extension field using the Conway polynomial from Algebraeon’s database. Elements are made with .of(x).

Example

print! FF(7)
check! GF(7)

print! FF(7).char(), FF(7).degree()
check! (7, 1)

print! FF(2, 2)
check! GF(2^2)

FF.of

|FF, Number| -> FFElement
|FF, List| -> FFElement

For GF(p), .of(x) reduces an integer modulo p. For GF(p^k), a list contains coefficients in ascending degree order, [c0, c1, ...], and is reduced modulo the Conway polynomial.

Example

print! FF(7).of(-1)
check! 6

x = FF(2, 2).of([0, 1])
print! x * x
check! x + 1

print! x.coeffs()
check! [0, 1]

FFElement.inverse

|FFElement| -> FFElement

Returns the multiplicative inverse of a non-zero finite-field element.

Example

print! FF(7).of(3).inverse()
check! 5

FFElement.order

|FFElement| -> N

Returns the multiplicative order of a non-zero element.

Example

print! FF(7).of(3).order()
check! 6

FFElement.pow

|FFElement, Number| -> FFElement

Raises an element to an integer power. Negative powers use the multiplicative inverse.

Example

print! FF(7).of(2).pow(-1)
check! 4

print! FF(2, 2).of([0, 1]).pow(3)
check! 1

CF

|List| -> CF

A simple continued fraction. CF([a0, a1, ...]) is finite and coefficients after the first must be positive. Its display uses [a0, a1, ...].

Example

print! CF([3, 7])
check! [3, 7]

print! CF([3, 7]).value()
check! 22/7

print! CF([3, 7]).convergent(1)
check! 22/7

CF.periodic

|List, List| -> CF

Constructs an infinite periodic continued fraction from its initial and repeating parts. For example, [1; 2, 2, ...] represents the continued fraction expansion of the square root of 2.

Example

s2 = CF.periodic([1], [2])
print! s2
check! [1; 2]

print! s2.convergent(3)
check! 17/12

CF.value

|CF| -> Q

Returns the exact rational value of a finite continued fraction. It is not defined for periodic or infinite continued fractions.

Example

print! CF([3, 7]).value()
check! 22/7

CF.convergent

|CF, N| -> Q

Returns the convergent at index n, starting at index zero. Convergents also work for periodic and infinite continued fractions.

Example

print! CF.periodic([1], [2]).convergent(4)
check! 41/29

CF.take

|CF, N| -> [Z]

Returns the first n coefficients as a list of Z values. A finite continued fraction stops when its coefficients run out.

Example

print! CF([3, 7]).take(4)
check! [3, 7]

CF.to_float

|CF| -> Number

Returns a floating-point approximation. Finite fractions are evaluated exactly before conversion; infinite fractions use a convergent.

Example

print! CF([3, 7]).to_float()
check! 3.142857142857143

Perm

|List| -> Perm

A permutation is given by its list of images, indexed from zero. For example, Perm([1, 2, 0]) maps 0 -> 1, 1 -> 2 and 2 -> 0. Composition p * q applies q first and then p.

Example

p = Perm([1, 2, 0])
q = Perm([0, 2, 1])
print! p
check! [1, 2, 0]

print! p * q
check! [1, 0]

print! p.inverse()
check! [2, 0, 1]

Perm.compose

|Perm, Perm| -> Perm

Composes two permutations with the same convention as *: the argument is applied first.

Example

print! Perm([1, 2, 0]).compose(Perm([0, 2, 1]))
check! [1, 0]

Perm.inverse

|Perm| -> Perm

Returns the inverse permutation.

Example

p = Perm([1, 2, 0])
print! p.compose(p.inverse())
check! []

Perm.sign

|Perm| -> Number

Returns 1 for an even permutation and -1 for an odd permutation.

Example

print! Perm([0, 2, 1]).sign()
check! -1

print! Perm([1, 2, 0]).sign()
check! 1

Perm.cycles

|Perm| -> [[Number]]

Returns the disjoint cycles. Fixed points are omitted.

Example

print! Perm([2, 0, 1, 4, 3]).cycles()
check! [[0, 2, 1], [3, 4]]

Perm.cycle_shape

|Perm| -> [Number]

Returns the sorted lengths of the non-trivial disjoint cycles.

Example

print! Perm([2, 0, 1, 4, 3]).cycle_shape()
check! [2, 3]

Perm.call

|Perm, Number| -> Number

Returns the image of an index under the permutation.

Example

p = Perm([1, 2, 0])
print! p.call(2)
check! 0

Perm.all

|Number| -> [Perm]

Returns all permutations in S_n.

Example

print! size(Perm.all(3))
check! 6

Group

|| -> Group

Group provides finite groups represented by multiplication tables. The constructors are cyclic(n), dihedral(n), symmetric(n), alternating(n), klein4(), quaternion() and trivial().

Example

print! Group.cyclic(4)
check! C4 (size 4)

print! Group.symmetric(3)
check! S3 (size 6)

Group.size

|Group| -> Number

Returns the number of elements in the group.

Example

print! Group.dihedral(3).size()
check! 6

Group.order

|Group, Number| -> Number

Returns the order of the element at the given table index. The identity is index 0.

Example

print! Group.cyclic(4).order(1)
check! 4

print! Group.cyclic(4).order(2)
check! 2

Group.is_abelian

|Group| -> Bool

Returns whether the group operation is commutative.

Example

print! Group.cyclic(4).is_abelian()
check! true

print! Group.dihedral(3).is_abelian()
check! false

Group.conjugacy_classes

|Group| -> [[Number]]

Returns the conjugacy classes as sorted lists of element indices.

Example

print! Group.dihedral(3).conjugacy_classes()
check! [[0], [1, 2, 4], [3, 5]]

ComplexAlg

|Poly | List| -> [ComplexAlg]
|Number| -> ComplexAlg
|Number, Number| -> ComplexAlg

Complex algebraic numbers are exact roots of polynomials. With a Poly or a coefficient list, ComplexAlg(...) returns all complex roots with multiplicity. With one scalar it constructs a rational real value; with two scalars ComplexAlg(a, b) constructs a + b*i. The imaginary unit is ComplexAlg.i().

Example

i = ComplexAlg.i()
print! i
check! i

print! i * i
check! -1

print! size(ComplexAlg(Poly([1, 0, 1])))
check! 2

ComplexAlg.real

|ComplexAlg| -> Alg

Returns the exact real part as an Alg value.

Example

print! ComplexAlg(Q(1), Q(2)).real()
check! 1

ComplexAlg.imag

|ComplexAlg| -> Alg

Returns the exact imaginary part as an Alg value.

Example

print! ComplexAlg(Q(1), Q(2)).imag()
check! 2

ComplexAlg.conjugate

|ComplexAlg| -> ComplexAlg

Returns the complex conjugate.

Example

print! ComplexAlg(Q(1), Q(2)).conjugate()
check! 1 - 2i

ComplexAlg.min_poly

|ComplexAlg| -> Poly

Returns the minimal polynomial over Q.

Example

print! ComplexAlg(Q(1), Q(2)).min_poly()
check! 5 - 2x + x^2

ComplexAlg.degree

|ComplexAlg| -> N

Returns the degree of the minimal polynomial.

Example

print! ComplexAlg.i().degree()
check! 2

ComplexAlg.to_float

|ComplexAlg| -> [Number, Number]

Returns a floating-point approximation as [real, imag].

Example

print! ComplexAlg(Q(1), Q(2)).to_float()
check! [1.0, 2.0]

legendre

|Number, Number| -> Z

Returns the Legendre symbol (a / p) as -1, 0 or 1. The bottom argument must be an odd prime.

Example

print! legendre(2, 7)
check! 1

print! legendre(3, 7)
check! -1

jacobi

|Number, Number| -> Z

Returns the Jacobi symbol (a / n) for an odd positive n; n need not be prime.

Example

print! jacobi(2, 9)
check! 1

print! jacobi(3, 9)
check! 0

kronecker

|Number, Number| -> Z

Returns the Kronecker symbol (a / n), extending the Jacobi symbol to even, negative and zero bottom arguments.

Example

print! kronecker(2, 8)
check! 0

print! kronecker(3, 8)
check! -1

eulers_constant

|| -> CF

Returns the infinite continued fraction for Euler’s number e: [2; 1, 2, 1, 1, 4, ...].

Example

e_cf = eulers_constant()
print! e_cf.take(9)
check! [2, 1, 2, 1, 1, 4, 1, 1, 6]

print! e_cf.convergent(5)
check! 87/32

Z.ideal

|Z| -> Ideal

Returns the principal ideal generated by the integer. The generator is canonicalized, so negative integers produce the same ideal as their absolute values.

Example

print! Z(-6).ideal()
check! 6Z

print! Z(0).ideal()
check! 0Z

io

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

A collection of utilities for working with the local filesystem.

create

|path: 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 null if the current directory can’t be retrieved.

exists

|path: 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

extend_path

|path: String, nodes: Any...| -> String

Takes an initial path as a string, and extends it with the provided nodes, inserting a platform-appropriate separator between each node.

Example

# On Windows
io.extend_path ".", "foo", "bar", "baz.txt"
# .\foo\bar\baz.txt

# On Linux
io.extend_path ".", "foo", "bar", "baz.txt"
# ./foo/bar/baz.txt

open

|path: 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

```kototype
|Any| -> Null

Prints a single value to the active output.

|Any, Any...| -> Null

Prints a series of values to the active output as a tuple.

Note

  • To print formatted strings, see string.format.
  • The output for print depends on the configuration of the runtime. The default output is stdout.

read_to_string

|path: 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

|path: String| -> Null

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

An object that represents a file handle.

File.flush

|File| -> Null

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

See Also

File.is_terminal

|File| -> Bool

Returns true if the file refers to a terminal/tty.

Example

next_line = if io.stdin().is_terminal()
  print 'Please provide some input'
  io.stdin().read_line()
else
  io.stdin().read_line()

File.path

|File| -> String

Returns the file’s path.

File.read_line

|File| -> String?

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

When the end of the file is reached, null 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, position: Number| -> Null

Seeks within the file to the specified position in bytes.

File.write

|File, Any| -> Null

Writes the formatted value as a string to the file.

File.write_line

|File, Any| -> Null

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

iterator

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

advance

|Iterable, n: Number| -> Number

Advances an iterator by calling next by n times, or until the end of the iterator is encountered.

The number of remaining steps is returned, with 0 indicating that n steps were successfully advanced.

If advancing the iterator causes an error to be thrown, then the error will be rethrown by advance.

Example

i = (1..=10).iter()
print! i.advance 5
check! 0
print! i.next().get()
check! 6

# The iterator has 4 elements left, so advance will have 1 remaining step
print! i.advance 5
check! 1

See Also

all

|Iterable, test: |Any| -> Bool| -> Bool

Checks the Iterable’s values against a test function.

The test function should return true if the value passes the test, otherwise it should return false.

all will return true if all values pass the test, otherwise it will return false.

all stops running as soon as it finds a value that fails the test.

Example

print! (1..9).all |x| x > 0
check! true

print! ('', '', 'foo').all string.is_empty
check! false

print! [10, 20, 30]
  .each |x| x / 10
  .all |x| x < 10
check! true

See Also

any

|Iterable, test: |Any| -> Bool| -> Bool

Checks the Iterable’s values against a test function.

The test function should return true if the value passes the test, otherwise it should return false.

any will return true if any of the values pass the test, otherwise it will return false.

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

Example

print! (1..9).any |x| x == 5
check! true

print! ('', '', 'foo').any string.is_empty
check! true

print! [10, 20, 30]
  .each |x| x / 10
  .any |x| x == 2
check! true

See Also

chain

|first: Iterable, second: Iterable| -> Iterator

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

Example

print! [1, 2]
  .chain 'abc'
  .to_tuple()
check! (1, 2, 'a', 'b', 'c')

chunks

|Iterable, size: Number| -> Iterator

Returns an iterator that splits up the input data into chunks of size N, where each chunk is provided as a Tuple. The final chunk may have fewer than N elements.

Example

print! 1..=10
  .chunks 3
  .to_list()
check! [(1, 2, 3), (4, 5, 6), (7, 8, 9), (10)]

consume

|Iterable| -> Null

Consumes the output of the iterator.

|Iterable, |Any| -> Any| -> Null

Consumes the output of the iterator, calling the provided function with each iterator output value.

Example

result = []
1..=10
  .keep |n| n % 2 == 0
  .each |n| result.push n
  .consume()
print! result
check! [2, 4, 6, 8, 10]

# Alternatively, calling consume with a function is equivalent to having an
# `each` / `consume` chain
result = []
1..=10
  .keep |n| n % 2 == 1
  .consume |n| result.push n
print! result
check! [1, 3, 5, 7, 9]

count

|Iterable| -> Number

Counts the number of items yielded from the iterator.

Example

print! (5..15).count()
check! 10

print! 0..100
  .keep |x| x % 2 == 0
  .count()
check! 50

cycle

|Iterable| -> Iterator

Takes an Iterable and returns a new iterator that endlessly repeats the iterable’s output.

The iterable’s output gets cached, which may result in a large amount of memory being used if the cycle has a long length.

Example

print! (1, 2, 3)
  .cycle()
  .take 10
  .to_list()
check! [1, 2, 3, 1, 2, 3, 1, 2, 3, 1]

each

|Iterable, function: |Any| -> Any| -> Iterator

Creates a new iterator that yields the result of calling the provided function with each value from the input iterator.

Example

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

enumerate

|Iterable| -> Iterator

Creates an iterator that yields each value along with an associated index.

Example

print! ('a', 'b', 'c').enumerate().to_list()
check! [(0, 'a'), (1, 'b'), (2, 'c')]

find

|Iterable, test: |Any| -> Bool| -> Any?

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

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

The first matching value will cause iteration to stop.

If no match is found then null is returned.

Example

print! (10..20).find |x| x > 14 and x < 16
check! 15

print! (10..20).find |x| x > 100
check! null

flatten

|Iterable| -> Iterator

Returns the output of the input iterator, with any nested iterable values flattened out.

Note that only one level of flattening is performed, so any double-nested containers will still be present in the output.

Example

print! [(2, 4), [6, 8, (10, 12)]]
  .flatten()
  .to_list()
check! [2, 4, 6, 8, (10, 12)]

See Also

fold

|
  input: Iterable,
  initial_value: Any,
  accumulator: |accumulated: Any, next: Any| -> Any
| -> Any

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

print! ('a', 'b', 'c')
  .fold [], |result, x|
    result.push x
    result.push '-'
check! ['a', '-', 'b', '-', 'c', '-']

See Also

generate

|generator: || -> Any| -> Iterator

Creates an iterator that yields the result of repeatedly calling the generator function.

Warning: This version of generate will iterate endlessly, so consider using an adaptor like iterator.take to produce an iterator that has an end.

|n: Number, generator: || -> Any| -> Any

Creates an iterator that yields the result of calling the generator function n times.

Example

from iterator import generate

state = {x: 0}
f = || state.x += 1

print! generate(f)
  .take(5)
  .to_list()
check! [1, 2, 3, 4, 5]

print! generate(f, 3).to_tuple()
check! (6, 7, 8)

See Also

intersperse

|Iterable, value: Any| -> Iterator

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

|Iterable, generator: || -> Any| -> Iterator

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

Example

print! ('a', 'b', 'c').intersperse('-').to_string()
check! a-b-c

separators = (1, 2, 3).iter()
print! ('a', 'b', 'c')
  .intersperse || separators.next().get()
  .to_tuple(),
check! ('a', 1, 'b', 2, 'c')

iter

|Iterable| -> Iterator

Returns an iterator that yields the provided iterable’s values.

Iterable values will be automatically accepted by most iterator operations, so it’s usually not necessary to call .iter(), however it can be usefult sometimes to make a standalone iterator for manual iteration.

Note that calling .iter with an Iterator will return the iterator without modification. If a copy of the iterator is needed then see koto.copy and koto.deep_copy.

Example

i = (1..10).iter()
i.advance 5
print! i.next().get()
check! 6

See Also

keep

|Iterable, test: |Any| -> 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 should return either true if the value should be kept in the iterator output, or false if it should be discarded.

Example

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

last

|Iterable| -> Any?

Consumes the iterator, returning the last yielded value.

Example

print! (1..100).take(5).last()
check! 5

print! (0..0).last()
check! null

max

|Iterable| -> Any

Returns the maximum value found in the iterable.

|Iterable, key: |Any| -> Any| -> Any

Returns the maximum value found in the iterable, based on first calling a ‘key’ function with the value, and then using the resulting keys for the comparisons.

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

print! (8, -3, 99, -1).max()
check! 99

See Also

min

|Iterable| -> Any

Returns the minimum value found in the iterable.

|Iterable, key: |Any| -> Any| -> Any

Returns the minimum value found in the iterable, based on first calling a ‘key’ function with the value, and then using the resulting keys for the comparisons.

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

print! (8, -3, 99, -1).min()
check! -3

See Also

min_max

|Iterable| -> (Any, Any)

Returns the minimum and maximum values found in the iterable.

|Iterable, key: |Any| -> Any| -> Any

Returns the minimum and maximum values found in the iterable, based on first calling a ‘key’ function with the value, and then using the resulting keys for the comparisons.

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

print! (8, -3, 99, -1).min_max()
check! (-3, 99)

See Also

next

|Iterable| -> IteratorOutput?

Returns the next value from the iterator wrapped in an IteratorOutput, or null if the iterator has been exhausted.

Example

x = (1, null, 'x').iter()
print! x.next()
check! IteratorOutput(1)
print! x.next()
check! IteratorOutput(null)
print! x.next()
check! IteratorOutput(x)
print! x.next()
check! null

# Call .get() to access the value from an IteratorOutput
print! 'abc'.next().get()
check! a

See Also

next_back

|Iterable| -> IteratorOutput?

Returns the next value from the end of the iterator wrapped in an IteratorOutput, or null if the iterator has been exhausted.

This only works with iterators that have a defined end, so attempting to call next_back on endless iterators like iterator.generate will result in an error.

Example

x = (1..=5).iter()
print! x.next_back()
check! IteratorOutput(5)
print! x.next_back()
check! IteratorOutput(4)

# calls to next and next_back can be mixed together
print! x.next()
check! IteratorOutput(1)
print! x.next_back()
check! IteratorOutput(3)
print! x.next_back()
check! IteratorOutput(2)

# 1 has already been produced by the iterator, so it's now exhausted
print! x.next_back()
check! null

See Also

once

|Any| -> Iterator

Returns an iterator that yields the given value a single time.

Example

print! iterator.once(99)
  .chain('abc')
  .to_tuple()
check! (99, 'a', 'b', 'c')

See Also

peekable

|Iterable| -> Peekable

Wraps the given iterable value in a peekable iterator.

Peekable.peek

Returns the next value from the iterator without advancing it. The peeked value is cached until the iterator is advanced.

Example

x = 'abc'.peekable()
print! x.peek()
check! IteratorOutput(a)
print! x.peek()
check! IteratorOutput(a)
print! x.next()
check! IteratorOutput(a)
print! x.peek()
check! IteratorOutput(b)
print! x.next(), x.next()
check! (IteratorOutput(b), IteratorOutput(c))
print! x.peek()
check! null

See Also

Peekable.peek_back

Returns the next value from the end of the iterator without advancing it. The peeked value is cached until the iterator is advanced.

Example

x = 'abc'.peekable()
print! x.peek_back()
check! IteratorOutput(c)
print! x.next_back()
check! IteratorOutput(c)
print! x.peek()
check! IteratorOutput(a)
print! x.peek_back()
check! IteratorOutput(b)
print! x.next_back(), x.next_back()
check! (IteratorOutput(b), IteratorOutput(a))
print! x.peek_back()
check! null

See Also

position

|Iterable, test: |Any| -> Bool| -> Any

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 should return 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 null is returned.

Example

print! (10..20).position |x| x == 15
check! 5

print! (10..20).position |x| x == 99
check! null

See Also

product

|Iterable| -> Any

Returns the result of multiplying each value in the iterable together, using 1 as the initial value.

Example

# Find the product of a sequence of numbers
print! (2, 3, 4).product()
check! 24

my_type = |n|
  n: n
  @*: |other| my_type self.n * other.n
  @display: || 'my_type({self.n})'

# Find the sum of a sequence of my_type values
print! (my_type(2), my_type(3)).product my_type(10)
check! my_type(60)

See also

repeat

|value: Any| -> Iterator

Creates an iterator that endlessly yields the provided value.

Warning: This version of repeat will iterate endlessly, so consider using an adaptor like iterator.take to produce an iterator with an end.

|value: Any, n: Number| -> Iterator

Creates an iterator that yields n repeats of the provided value.

Example

from iterator import repeat

print! repeat(42).take(5).to_list()
check! [42, 42, 42, 42, 42]

print! repeat('x', 3).to_tuple()
check! ('x', 'x', 'x')

See Also

reversed

|Iterable| -> Iterator

Reverses the order of the iterator’s output.

This only works with iterators that have a defined end, so attempting to reverse endless iterators like iterator.generate will result in an error.

Example

print! 'Héllö'.reversed().to_tuple()
check! ('ö', 'l', 'l', 'é', 'H')

print! (1..=10).reversed().skip(5).to_tuple()
check! (5, 4, 3, 2, 1)

skip

|Iterable, steps: Number| -> Iterator

Returns an iterator that will skip over the given number of output steps.

Note that skipping only occurs lazily when the iterator is consumed. To skip iterator output immediately see [iterator.advance].

Example

print! (100..200).skip(50).next().get()
check! 150

See also

step

|Iterable, step_size: Number| -> Iterator

Steps over the iterable’s output by the provided step size.

Example

print! (0..10).step(3).to_tuple()
check! (0, 3, 6, 9)

print! 'Héllö'.step(2).to_string()
check! Hlö

See also

sum

|Iterable| -> Any

Returns the result of adding each value in the iterable together, using 0 as the initial value.

|Iterable, initial_value: Any| -> Any

Returns the result of adding each value in the iterable together, using the provided initial value as the start of the operation.

Example

# Find the sum of a sequence of numbers
print! (2, 3, 4).sum()
check! 9

my_type = |n|
  n: n
  @+: |other| my_type self.n + other.n
  @display: || 'my_type({self.n})'

# Find the sum of a sequence of my_type values
print! (my_type(1), my_type(2)).sum my_type(100)
check! my_type(103)

See also

take

|Iterable, count: Number| -> Iterator

Creates an iterator that yields a number of values from the input before finishing.

|Iterable, test: |Any| -> Bool| -> Iterator

Creates an iterator that yields values from the input while they pass a test function.

The test function should return true if the iterator should continue to yield values, and false if the iterator should stop yielding values.

Example

print! (100..200).take(3).to_tuple()
check! (100, 101, 102)

print! 'hey!'.take(|c| c != '!').to_string()
check! hey

See also

to_list

|Iterable| -> List

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

Example

print! ('a', 42, (-1, -2)).to_list()
check! ['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 null as the entry’s value.

Example

print! ('a', 'b', 'c').to_map()
check! {a: null, b: null, c: null}

print! ('a', 'bbb', 'cc')
  .each |x| x, size x
  .to_map()
check! {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

print! ('x', 'y', 'z').to_string()
check! xyz

print! (1, 2, 3).intersperse('-').to_string()
check! 1-2-3

See also

to_tuple

|Iterable| -> Tuple

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

Example

print! ('a', 42, (-1, -2)).to_tuple()
check! ('a', 42, (-1, -2))

See also

windows

|Iterable, size: Number| -> Iterator

Returns an iterator that splits up the input data into overlapping windows of the specified size, where each window is provided as a Tuple.

If the input has fewer elements than the window size, then no windows will be produced.

Example

print! 1..=5
  .windows 3
  .to_list(),
check! [(1, 2, 3), (2, 3, 4), (3, 4, 5)]

zip

|first: Iterable, second: Iterable| -> Iterator

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

Example

print! (1, 2, 3)
  .zip ('a', 'b', 'c')
  .to_list()
check! [(1, 'a'), (2, 'b'), (3, 'c')]

IteratorOutput

A wrapper for a single item of iterator output.

This exists to allow functions like iterator.next to return null to indicate that the iterator has been exhausted, while also allowing null to appear in the iterator’s output.

IteratorOutput.get

|IteratorOutput| -> Any

Returns the wrapped iterator output value.

Example

print! x = 'abc'.next()
check! IteratorOutput(a)
print! x.get()
check! a

koto

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

A collection of utilities for working with the Koto runtime.

copy

|value: Any| -> Any

Makes a copy of the provided value.

Shared mutable data

For values that have shared mutable data (i.e., List, Map), unique copies of the data will be made. Note that this only applies to the first level of data, so nested containers will still share their data with their counterparts in the original data. To make a copy where any nested containers are also unique, use koto.deep_copy.

Iterator copies

Copied iterators share the same underlying data as the original, but have a unique iteration position, which is part of an iterator’s shared state by default.

If the iterator is a generator, some effort will be made to make the generator’s copy produce the same output as the original. However, this isn’t guaranteed to be successful. Specifically, the value stack of the copied virtual machine will be scanned for iterators, and each iterator will have a copy made. Iterators that may be used in other ways by the generator (such as being stored in containers or function captures) won’t be copied and will still have shared state.

Examples

# Copying a map
x = {foo: -1, bar: 99}
y = x
y.foo = 42
print! x.foo
check! 42

z = koto.copy x
z.bar = -1
print! x.bar # x.bar remains unmodified due to the copy
check! 99
# Copying a list

x = (1..=10).iter()
y = x # y shares the same iteration position as x.
z = koto.copy x # z shares the same iteration data (the range 1..=10),
                # but has a unique iteration position.

print! x.next().get()
check! 1
print! x.next().get()
check! 2
print! y.next().get() # y shares x's iteration position.
check! 3
print! z.next().get() # z isn't impacted by the advancing of x and y.
check! 1

See also

deep_copy

|value: Any| -> Any

Makes a unique deep copy of the value’s data.

Shared mutable data

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

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

Example

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

See also

hash

|value: Any| -> Number?

Returns the value’s hash as an integer, or null if the value is not hashable.

Example

from koto import hash

print! (hash 'hi') == (hash 'bye')
check! false

# Lists aren't hashable
print! hash [1, 2]
check! null

# Tuples are hashable if they only contain hashable values
print! (hash (1, 2)) == null
check! false

load

|script: String| -> Chunk

Compiles the provided Koto script and returns a compiled Chunk.

Any compilation errors get thrown.

Example

chunk = koto.load '1 + 2'
print! koto.run chunk
check! 3

See also

run

|script: String| -> Any

Compiles and runs the provided Koto script, and returns the resulting value.

Any compilation or runtime errors get thrown.

|Chunk| -> Any

Runs the compiled Chunk, and returns the resulting value.

Any runtime errors encountered during execution get thrown.

Example

print! koto.run '[1, 2, 3, 4].sum()'
check! 10

See also

script_dir

|| -> String?

Returns the path of the directory containing the current script, if available.

script_path

|| -> String?

Returns the path of the file containing the current script, if available.

size

|value: Any| -> Number

Returns the size of a value.

The size of a value is typically defined as the number of elements in a container, with some notable exceptions:

  • For strings, the size is the number of bytes in the string data.
  • For ranges, the size is the number of integers in the range.
    • For non-inclusive ranges, this is equivalent to range.end() - range.start().
    • For inclusive ranges, this is equivalent to range.end() + 1 - range.start().
    • If the range is unbounded then an error will be thrown.
  • An error will be thrown if the value doesn’t have a defined size.

Example

from koto import size

print! (size [1, 2, 3]), (size ())
check! (3, 0)

print! (size 'hello'), (size 'héllø'), (size '')
check! (5, 7, 0)

print! (size 10..20), (size 10..=20), (size 20..0)
check! (10, 11, 20)

type

|value: Any| -> String

Returns the type of the input value as a String.

Example

print! koto.type true
check! Bool

x = 42
print! koto.type x
check! Number

foo =
  @type: "Foo"
print! koto.type foo
check! Foo

unimplemented

Unimplemented

An instance of Unimplemented, which should be thrown from overridden arithmetic operators when the operation isn’t supported with the given input type.

Example

foo = |n|
  data: n
  @type: 'Foo'
  @display: || 'Foo({self.data})'
  @+: |other|
    # Throw an `unimplemented` error if the rhs isn't a Foo
    match type other
      'Foo' then foo self.data + other.data
      else throw koto.unimplemented

bar = |n|
  data: n
  @type: 'Bar'
  @display: || 'Bar({self.data})'
  @r+: |other|

    match type other
      'Foo' or 'Bar' then bar other.data + self.data
      else throw koto.unimplemented

print! (foo 10) + (foo 20)
check! Foo(30)

print! (foo 2) + (bar 3)
check! Bar(5)

list

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

clear

|List| -> List

Clears the list by removing all of its elements, and returns the cleared list.

Example

x = [1, 2, 3]
print! x.clear()
check! []

contains

|List, value: Any| -> Bool

Returns true if the list contains an element that matches the input value.

Matching is performed with the == equality operator.

Example

print! [1, 'hello', (99, -1)].contains 'hello'
check! true

extend

|List, new_elements: Iterable| -> List

Extends the list with the output of the iterator, and returns the list.

Example

x = [1, 2, 3]
print! x.extend 'abc'
check! [1, 2, 3, 'a', 'b', 'c']
print! x.last()
check! c
print! x.extend [10, 20, 30]
check! [1, 2, 3, 'a', 'b', 'c', 10, 20, 30]
print! x.last()
check! 30

See also

fill

|List, value: Any| -> List

Fills the list with copies of the provided value, and returns the list.

Example

x = [1, 2, 3]
print! x.fill 99
check! [99, 99, 99]
print! x
check! [99, 99, 99]

first

|List| -> Any?

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

Example

print! [99, -1, 42].first()
check! 99

print! [].first()
check! null

See also

get

|List, index: Number| -> Any?
|List, index: Number, default: Any| -> Any?

Gets the element at the given index in the list.

If the list doesn’t contain a value at that position then the provided default value is returned. If no default value is provided then null is returned.

Example

x = [99, -1, 42]

print! x.get 1
check! -1

print! x.get -1
check! null

print! x.get 5, 123
check! 123

See also

insert

|List, position: Number, value: Any| -> List

Inserts the value into the list at the given index position, and returns the list.

Elements in the list at or after the given position will be shifted to make space for the new value.

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

Example

x = [99, -1, 42]
print! x.insert 2, 'hello'
check! [99, -1, 'hello', 42]
print! x
check! [99, -1, 'hello', 42]

See also

is_empty

|List| -> Bool

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

Example

print! [].is_empty()
check! true

print! [1, 2, 3].is_empty()
check! false

last

|List| -> Any?

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

Example

print! [99, -1, 42].last()
check! 42

print! [].last()
check! null

See also

pop

|List| -> Any?

Removes the last value from the list and returns it.

If the list is empty then null is returned.

Example

x = [99, -1, 42]
print! x.pop()
check! 42

print! x
check! [99, -1]

print! [].pop()
check! null

See also

push

|List, value: Any| -> List

Adds the value to the end of the list, and returns the list.

Example

x = [99, -1]
print! x.push 'hello'
check! [99, -1, 'hello']
print! x
check! [99, -1, 'hello']

See also

remove

|List, position: Number| -> Any

Removes the value at the given position from the list, and returns the removed value.

An error is thrown if the position isn’t a valid index in the list.

Example

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

See also

resize

|List, new_size: Number| -> List
|List, new_size: Number, default: Any| -> List

Grows or shrinks the list to the specified size, and returns the list. If the new size is larger, then copies of the default value (or null if no value is provided) are used to fill the new space.

Example

x = [1, 2]
print! x.resize 4, 'x'
check! [1, 2, 'x', 'x']

print! x.resize 3
check! [1, 2, 'x']

print! x.resize 4
check! [1, 2, 'x', null]

resize_with

|List, new_size: Number, generator: || -> Any| -> List

Grows or shrinks the list to the specified size, and returns the list. If the new size is larger, then the provided function will be called repeatedly to fill the remaining space, with the result of the function being added to the end of the list.

Example

new_entries = (5, 6, 7, 8).iter()
x = [1, 2]
print! x.resize_with 4, || new_entries.next().get()
check! [1, 2, 5, 6]

print! x.resize_with 2, || new_entries.next().get()
check! [1, 2]

retain

|List, test: Any| -> List

Retains matching values in the list (discarding values that don’t match), and returns the list.

If test 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).to_list()
print! x.retain |n| n < 5
check! [1, 2, 3, 4]
print! x
check! [1, 2, 3, 4]

x = [1, 3, 8, 3, 9, -1]
print! x.retain 3
check! [3, 3]
print! x
check! [3, 3]

reverse

|List| -> List

Reverses the order of the list’s contents, and returns the list.

Example

x = ['hello', -1, 99, 'world']
print! x.reverse()
check! ['world', 99, -1, 'hello']
print! x
check! ['world', 99, -1, 'hello']

sort

|List| -> List

Sorts the list in place, and returns the list.

|List, key: |Any| -> Any| -> List

Sorts the list in place, based on the output of calling a key function for each of the list’s elements, and returns the list.

The key function’s result is cached, so it’s only called once per element.

Example

x = [1, -1, 99, 42]
print! x.sort()
check! [-1, 1, 42, 99]
print! x
check! [-1, 1, 42, 99]

x = ['bb', 'ccc', 'a']
print! x.sort size
check! ['a', 'bb', 'ccc']
print! x
check! ['a', 'bb', 'ccc']

x = [2, 1, 3]
# Sort in reverse order by using a key function
print! x.sort |n| -n
check! [3, 2, 1]
print! x
check! [3, 2, 1]

swap

|first: List, second: List| -> Null

Swaps the contents of the two input lists.

Example

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

print! x
check! [7, 8, 9]

print! y
check! [1, 2, 3]

to_tuple

|List| -> Tuple

Returns a copy of the list data as a tuple.

Example

print! [1, 2, 3].to_tuple()
check! (1, 2, 3)

transform

|List, transformer: |Any| -> Any| -> List

Transforms the list data in place by replacing each value with the result of calling the provided transformer function, and then returns the list.

Example

x = ['aaa', 'bb', 'c']
print! x.transform size
check! [3, 2, 1]
print! x
check! [3, 2, 1]

print! x.transform |n| '{n}!'
check! ['3!', '2!', '1!']
print! x
check! ['3!', '2!', '1!']

map

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

clear

|Map| -> Map

Clears the map by removing all of its elements, and returns the map.

Example

x = {x: -1, y: 42}
print! x.clear()
check! {}
print! x
check! {}

contains_key

|Map, key: Any| -> Bool

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

extend

|Map, new_entries: Iterable| -> Map

Extends the map with the output of the iterator, and returns the map.

Example

x = {foo: 42, bar: 99}
print! x.extend {baz: 123}
check! {foo: 42, bar: 99, baz: 123}
print! x.baz
check! 123

x = {}
print! x.extend 'abc'.each |c| c, '{c}!'
check! {a: 'a!', b: 'b!', c: 'c!'}
print! x.c
check! c!

See also

get

|Map, key: Any| -> Any
|Map, key: Any, default: Any| -> Any

Returns the value corresponding to the given key, or the provided default value if the map doesn’t contain the key.

If no default value is provided then null is returned.

Example

x = {hello: -1}
print! x.get 'hello'
check! -1

print! x.get 'goodbye'
check! null

print! x.get 'goodbye', 'byeeee'
check! byeeee

x.insert 99, 'xyz'
print! x.get 99
check! xyz

See also

get_index

|Map, index: Number| -> Tuple
|Map, index: Number, default: Any| -> Tuple

Returns the entry at the given index as a key/value tuple, or the provided default value if the map doesn’t contain an entry at that index.

If no default value is provided then null is returned.

Example

x = {foo: -1, bar: -2}
print! x.get_index 1
check! ('bar', -2)

print! x.get_index -99
check! null

print! x.get_index 99, 'xyz'
check! xyz

See also

get_meta

|Map| -> Map

Returns a Map that contains the input’s Meta Map, and no data.

Example

my_map =
  data: 42
  @type: 'My Map'

meta = map.get_meta my_map

print! map.keys(my_map).count()
check! 1
print! map.keys(meta).count()
check! 0

print! koto.type meta
check! My Map

See also

insert

|Map, key: Any, value: Any| -> Any

Inserts an entry into the map with the given key and value.

|Map, key: Any| -> Any

Inserts an entry into the map with the given key, and null as its value.

If the key already existed in the map, then the old value is returned. If the key didn’t already exist, then null is returned.

See the language guide for a description of the types of values that can be used as map keys.

Example

x = {hello: -1}

print! x.insert 'hello', 99 # -1 already exists at `hello`, so it's returned here
check! -1

print! x.hello # hello is now 99
check! 99

print! x.insert 'goodbye', 123 # No existing value at `goodbye`, so null is returned
check! null

print! x.goodbye
check! 123

print! x.insert 123, 'hi!' # Numbers can be used as map keys
check! null

print! x.get 123
check! hi!

print! x.insert ('a', 'b'), -1 # Tuples can be used as map keys
check! null

print! x.get ('a', 'b')
check! -1

See also

is_empty

|Map| -> Bool

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

Example

print! {}.is_empty()
check! true

print! {hello: -1}.is_empty()
check! false

keys

|Map| -> Iterator

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

Example

m =
  hello: -1
  goodbye: 99

x = m.keys()

print! x.next().get()
check! hello

print! x.next().get()
check! goodbye

print! x.next()
check! null

See also

remove

|Map, key: Any| -> Any

Removes the entry that matches the given key.

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

Example

x =
  hello: -1
  goodbye: 99

print! x.remove 'hello'
check! -1

print! x.remove 'xyz'
check! null

print! x.remove 'goodbye'
check! 99

print! x.is_empty()
check! true

See also

sort

|Map| -> Map

Sorts the map’s entries in place by key, and then returns the map.

|
  Map,
  sort_key: |key: Any, value: Any| -> Any
| -> Null

Sorts the map’s entries in place based on the output of calling a ‘sort’ function for each entry, and then returns the map.

The entry’s key and value are passed into the sort_key function as separate arguments.

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

Example

x =
  hello: 123
  bye: -1
  tschüss: 99

# Sort the map by key
print! x.sort()
check! {bye: -1, hello: 123, tschüss: 99}

# Sort the map by value
print! x.sort |_, value| value
check! {bye: -1, tschüss: 99, hello: 123}

# Sort the map by reversed key length
print! x.sort |key, _| -(size key)
check! {tschüss: 99, hello: 123, bye: -1}

update

|Map, key: Any, updater: |Any| -> Any| -> Any

Updates the value associated with a given key by calling the updater function.

If an entry exists with the given key, then updater will be called with the existing entry’s value, and the result of the function will replace the existing value.

If no entry exists with the given key, then updater will be called with null, and the result will be inserted into the map as a new entry.

The return value is the result of calling the updater function.

|Map, key: Any, default: Any, updater: |Any| -> Any| -> Any

This variant of update takes a default value that is provided to the updater function if no entry exists with the given key.

Example

x =
  hello: -1
  goodbye: 99

print! x.update 'hello', |n| n * 2
check! -2
print! x.hello
check! -2

print! x.update 'tschüss', 10, |n| n * 10
check! 100
print! x.tschüss
check! 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()

print! x.next().get()
check! -1

print! x.next().get()
check! 99

print! x.next()
check! null

See also

with_meta

|data: Map, meta: Map| -> Map

Returns a new Map that contains the data from the first argument, along with the Meta Map from the second argument.

Example

my_meta =
  @type: 'MyMeta'

x = {foo: 42}.with_meta my_meta

print! koto.type x
check! MyMeta

See also

number

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

abs

|Number| -> Number

Returns the absolute value of the number.

Example

print! -1.abs()
check! 1

print! 1.abs()
check! 1

acos

|Number| -> Number

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

Example

from number import pi

assert_near 0.acos(), pi / 2
assert_eq 1.acos(), 0

acosh

|Number| -> Number

Returns the inverse hyperbolic cosine of the number.

Example

assert 0.acosh().is_nan()
assert_eq 1.acosh(), 0
assert_near 2.acosh(), 1.3169578969248166

and

|Number, Number| -> Number

Returns the bitwise combination of the binary representations of two numbers, where a 1 in both of the inputs produces a 1 in the corresponding output position.

Note

If either input is a float then its integer part will be used.

Example

print! 0b1010.and 0b1100
# 0b1000
check! 8

asin

|Number| -> Number

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

Example

from number import pi

assert_eq 0.asin(), 0
assert_near 1.asin(), pi / 2

asinh

|Number| -> Number

Returns the inverse hyperbolic sine of the number.

Example

assert_eq 0.asinh(), 0
assert_near 1.asinh(), 0.8813735870195429

atan

|Number| -> Number

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

Example

from number import pi

assert_eq 0.atan(), 0
assert_near 1.atan(), pi / 4

atanh

|Number| -> Number

Returns the inverse hyperbolic tangent of the number.

Example

print! -1.atanh()
check! -inf

print! 0.atanh()
check! 0.0

print! 1.atanh()
check! inf

atan2

|Number, Number| -> Number

Returns the arc tangent of y and x in radians, using the signs of y and x to determine the correct quadrant.

Example

from number import pi

x, y = 1, 1

assert_near y.atan2(x), pi / 4
assert_near y.atan2(-x), pi - pi / 4

ceil

|Number| -> Number

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

Example

print! 0.5.ceil()
check! 1

print! 2.ceil()
check! 2

print! -0.5.ceil()
check! 0

See Also

clamp

|input: Number, min: Number, max: Number| -> Number

Returns the input number restricted to the range defined by min and max.

Example

print! 0.clamp 1, 2
check! 1

print! 1.5.clamp 1, 2
check! 1.5

print! 3.0.clamp 1, 2
check! 2

cos

|Number| -> Number

Returns the cosine of the number.

Example

print! 0.cos()
check! 1.0

print! number.pi.cos()
check! -1.0

cosh

|Number| -> Number

Returns the hyperbolic cosine of the number.

Example

assert_eq 0.cosh(), 1
assert_near 1.cosh(), 1.5430806348152437

degrees

|Number| -> Number

Converts radians into degrees.

Example

from number import pi, tau

print! pi.degrees()
check! 180.0

print! tau.degrees()
check! 360.0

e

Number

Provides the e constant.

exp

|Number| -> Number

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

Example

assert_eq 0.exp(), 1
assert_eq 1.exp(), number.e

exp2

|Number| -> Number

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

Example

print! 1.exp2()
check! 2.0

print! 3.exp2()
check! 8.0

flip_bits

|Number| -> Number

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

Example

print! 1.flip_bits()
check! -2

floor

|Number| -> Number

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

Example

print! 0.5.floor()
check! 0

print! 2.floor()
check! 2

print! -0.5.floor()
check! -1

See Also

infinity

Number

Provides the constant.

is_int

|Number| -> Bool

Returns true if the number is an integer.

Example

print! 1.is_int()
check! true

print! 1.5.is_int()
check! false

See Also

is_nan

|Number| -> Bool

Returns true if the number is NaN.

Example

print! 1.is_nan()
check! false

print! (0 / 0).is_nan()
check! true

See Also

lerp

|a: Number, b: Number, t: Number| -> Number

Linearly interpolates between a and b using the interpolation factor t.

The range (a -> b) corresponds to the value range of (0 -> 1) for t.

e.g.

  • At t == 0, the result is equal to a.
  • At t == 1, the result is equal to b.
  • At other values of t, the result is a proportional mix of a and b.
  • Values for t outside of (0 -> 1) will extrapolate from the (a -> b) range.

Example

a, b = 1, 2

print! a.lerp b, 0
check! 1
print! a.lerp b, 0.5
check! 1.5
print! a.lerp b, 1
check! 2

print! a.lerp b, -0.5
check! 0.5
print! a.lerp b, 1.5
check! 2.5

ln

|Number| -> Number

Returns the natural logarithm of the number.

Example

print! 1.ln()
check! 0.0

print! number.e.ln()
check! 1.0

log2

|Number| -> Number

Returns the base-2 logarithm of the number.

Example

print! 2.log2()
check! 1.0

print! 4.log2()
check! 2.0

log10

|Number| -> Number

Returns the base-10 logarithm of the number.

Example

print! 10.log10()
check! 1.0

print! 100.log10()
check! 2.0

max

|Number, Number| -> Number

Returns the larger of the two numbers.

Example

print! 1.max 2
check! 2

print! 4.5.max 3
check! 4.5

min

|Number, Number| -> Number

Returns the smaller of the two numbers.

Example

print! 1.min 2
check! 1

print! 4.5.min 3
check! 3

nan

Number

Provides the NaN (Not a Number) constant.

negative_infinity

Number

Provides the -∞ constant.

or

|Number, Number| -> Number

Returns the bitwise combination of the binary representations of two numbers, where a 1 in either of the inputs produces a 1 in the corresponding output position.

Note

If either input is a float then its integer part will be used.

Example

print! 0b1010.or 0b1100
# 0b1110
check! 14

pi

Number

Provides the π constant.

pi_2

Number

Provides the π constant divided by 2.

pi_4

Number

Provides the π constant divided by 4.

pow

|Number, Number| -> Number

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

Example

print! 2.pow 3
check! 8

radians

|Number| -> Number

Converts degrees into radians.

Example

from number import pi

assert_near 90.radians(), pi / 2
assert_near 360.radians(), pi * 2

recip

|Number| -> Number

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

Example

print! 2.recip()
check! 0.5

round

|Number| -> Number

Returns the nearest integer to the input number. Half-way values round away from zero.

Example

print! 0.5.round()
check! 1

print! 2.round()
check! 2

print! -0.5.round()
check! -1

See Also

shift_left

|Number, shift_amount: Number| -> Number

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

Note

If either input is a float then its integer part will be used.

Note

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

Example

print! 0b1010.shift_left 2
# 0b101000
check! 40

shift_right

|Number, shift_amount: Number| -> Number

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

Note

If either input is a float then its integer part will be used.

Note

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

Example

print! 0b1010.shift_right 2
# 0b0010
check! 2

sin

|Number| -> Number

Returns the sine of the number.

Example

from number import pi

print! (pi * 0.5).sin()
check! 1.0

print! (pi * 1.5).sin()
check! -1.0

sinh

|Number| -> Number

Returns the hyperbolic sine of the number.

Example

assert_eq 0.sinh(), 0
assert_near 1.sinh(), 1.1752011936438014

sqrt

|Number| -> Number

Returns the square root of the number.

Example

print! 64.sqrt()
check! 8.0

tan

|Number| -> Number

Returns the tangent of the number.

Example

assert_eq 0.tan(), 0
assert_near 1.tan(), 1.557407724654902

tanh

|Number| -> Number

Returns the hyperbolic tangent of the number.

Example

assert_near 1.tanh(), 1.sinh() / 1.cosh()

tau

Number

Provides the τ constant, equivalent to .

to_int

|Number| -> Number

Returns the integer part of the input number.

This is often called trunc in other languages.

Example

print! 2.9.to_int()
check! 2

print! 1.5.to_int()
check! 1

print! -0.5.to_int()
check! 0

print! -1.9.to_int()
check! -1

See Also

xor

|Number, Number| -> Number

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

Note

If either input is a float then its integer part will be used.

Example

print! 0b1010.xor 0b1100
# 0b0110
check! 6

os

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

A collection of utilities for working with the operating system.

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"`
size os.args
# 3
os.args.first()
# 1
os.args.last()
# hello

command

|program: String| -> Command

Creates a new Command, which supports executing external programs in separate processes.

Builder methods allow configuration of properties like command arguments or environment variables before spawning the program in a new process.

Example

print! os.command('ls')
  .args('-al', '/tmp')
  .wait_for_output()
  .stdout()
check! ...

name

|| -> String

Returns a string containing the name of the current operating system, e.g. “linux”, “macos”, “windows”, etc.

process_id

|| -> Number

Returns the ID associated with the current process.

start_timer

|| -> Timer

Returns a timer that can be used to measure how much time has passed while a script is running.

Example

t = os.start_timer()

# ...after some time...
print "Time taken: ${t.elapsed()}s"

t2 = os.start_timer()
print "Seconds between then and now: ${t2 - t}"

time

|| -> DateTime

Returns a DateTime set to the current time, using the local timezone.

|timestamp: Number| -> DateTime

Returns a DateTime set to the provided timestamp in seconds, using the local timezone.

|timestamp: Number, offset: Number| -> DateTime

Returns a DateTime set to the provided timestamp in seconds, using an offset in seconds.

Example

print! now = os.time()
# e.g. 2021-12-11 21:51:14

print! now.year()
# e.g. 2021

print! now.hour()
# e.g. 21

print! now.timestamp()
# e.g. 1639255874.53419

Command

See os.command

Command.args

|Command, args...| -> Command

Adds the given arguments to the command, and returns the command.

Example

print! os.command('ls')
  .args('-al', '/tmp')
  .wait_for_output()
  .stdout()
check! ...

Command.current_dir

|Command, path: String| -> Command

Sets the command’s working directory, and returns the command.

Example

print! os.command('ls')
  .current_dir('/tmp')
  .wait_for_output()
  .stdout()
check! ...

Command.env

|Command, key: String, value: String| -> Command

Sets an environment variable, and returns the command.

Example

assert os.command('env')
  .env 'FOO', '123'
  .wait_for_output()
  .stdout()
  .contains 'FOO=123'

Command.env_clear

|Command| -> Command

Clears all environment variables for the command, and returns the command.

This prevents the command from inheriting any environment variables from the parent process.

Example

assert os.command('env')
  .env_clear()
  .wait_for_output()
  .stdout()
  .is_empty()

Command.env_remove

|Command, key: String| -> Command

Removes the environment variable matching the given key, and returns the command.

Example

assert os.command('env')
  .env_clear()
  .env 'FOO', '123'
  .env_remove 'FOO'
  .wait_for_output()
  .stdout()
  .is_empty()

Command.stdin

|Command, stream_config: String| -> Command

Configures the command’s stdin stream.

Valid values of stream_config are:

  • inherit: the stream will be inherited from the parent process.
  • piped: a pipe will be created to connect the parent and child processes.
  • null: the stream will be ignored.

The default stream behavior is inherit when the command is used with spawn or wait_for_exit, and piped when used with wait_for_output.

Command.stdout

|Command, stream_config: String| -> Command

Configures the command’s stdout stream.

See Command.stdin for valid values of stream_config.

Command.stderr

|Command, stream_config: String| -> Command

Configures the command’s stderr stream.

See Command.stdin for valid values of stream_config.

Command.spawn

|Command| -> Child

Executes the command, returning the command’s Child process.

Example

spawned = os.command('ls')
  .stdout('piped')
  .spawn()

print! spawned
  .wait_for_output()
  .stdout()
check! ...

Command.wait_for_output

|Command| -> CommandOutput

Executes the command and waits for it to exit, returning its captured output.

Example

print! os.command('ls').wait_for_output().stdout()
check! ...

Command.wait_for_exit

|Command| -> Number?

Executes the command and waits for it to exit, returning its exit code if the command exited normally, or null if it was interrupted.

Example

print! os.command('ls').wait_for_exit()
check! 0

CommandOutput

Contains captured output from a command, and information about how the command exited.

See Command.wait_for_output and Child.wait_for_output.

CommandOutput.exit_code

|CommandOutput| -> Number?

Returns the command’s exit code if available.

CommandOutput.success

|CommandOutput| -> Bool

Returns true if the command exited successfully.

CommandOutput.stdout

|CommandOutput| -> String?

Returns the contents of the command’s stdout stream if it contains valid unicode, or null otherwise.

See also

CommandOutput.stderr

|CommandOutput| -> String?

Returns the contents of the command’s stderr stream if it contains valid unicode, or null otherwise.

CommandOutput.stdout_bytes

|CommandOutput| -> Iterator

Returns an iterator that yields the bytes contained in the command’s stdout stream.

CommandOutput.stderr_bytes

|CommandOutput| -> Iterator

Returns an iterator that yields the bytes contained in the command’s stderr stream.

Child

A handle to a child process, see Command.spawn.

Child.stdin

|Child| -> File

Returns the child process’s stdin standard input stream as a File that supports write operations.

Example

spawned = os.command('cat')
  .stdin 'piped'
  .spawn()

stdin = spawned.stdin()
stdin.write_line 'hello'
stdin.write_line 'one two three'

print! spawned.wait_for_output().stdout()
check! hello
check! one two three

Child.stdout

|Child| -> File

Returns the child process’s stdout standard output stream as a File that supports read operations.

Calling this function will prevent the stream from being included in wait_for_output.

Child.stderr

|Child| -> File

Returns the child process’s stderr standard error stream as a File that supports read operations.

Calling this function will prevent the stream from being included in wait_for_output.

Child.has_exited

|Child| -> Bool

Returns true without blocking if the child process has exited, and false otherwise.

Child.wait_for_output

|Child| -> CommandOutput

Closes all input and output streams, waits for the command to exit, and then returns the captured output.

Note that if the stdout or stderr streams were manually retrieved via Child.stdout/Child.stderr then they won’t be included in the captured output.

Child.wait_for_exit

|Child| -> Number?

Closes all input and output streams, waits for the command to exit, and then returns the command’s exit code if available.

DateTime

See os.time.

DateTime.year

|DateTime| -> Number

Returns the year component of the provided DateTime.

DateTime.month

|DateTime| -> Number

Returns the month component of the provided DateTime.

DateTime.day

|DateTime| -> Number

Returns the day component of the provided DateTime.

DateTime.hour

|DateTime| -> Number

Returns the hour component of the provided DateTime.

DateTime.minute

|DateTime| -> Number

Returns the minute component of the provided DateTime.

DateTime.nanosecond

|DateTime| -> Number

Returns the nanosecond component of the provided DateTime.

DateTime.timestamp

|DateTime| -> Number

Returns the number of seconds since 00:00:00 UTC on January 1st 1970.

DateTime.timezone_offset

|DateTime| -> Number

Returns the DateTime’s timezone offset in seconds.

DateTime.timestamp_string

|DateTime| -> String

Returns a string representing the DateTime’s timezone offset in seconds.

Timer

See os.start_timer.

Timer.@- (subtract)

|Timer, Timer| -> Number

Returns the time difference in seconds between two timers.

Example

t1 = os.start_timer()
t2 = os.start_timer()
# t2 was started later than t1, so the time difference is positive
assert (t2 - t1) > 0
# t1 was started earlier than t2, so the time difference is negative
assert (t1 - t2) < 0

Timer.elapsed

|Timer| -> Number

Returns the number of seconds that have elapsed since the timer was started.

Example

t = os.start_timer()

# ...after some time...
print "Time taken: ${t.elapsed()}s"

range

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

contains

|Range, Number| -> Bool

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

|Range, Range| -> Bool

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

Example

print! (10..20).contains 15
check! true

print! (200..=100).contains 100
check! true

x = 1..10
print! x.contains -1
check! false

print! (10..20).contains 14..18
check! true

print! (100..200).contains 50..250
check! false

end

|Range| -> Number

Returns the end value of the range.

Example

print! (50..100).end()
check! 100

print! (10..0).end()
check! 0

See also

expanded

|Range, amount: 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

print! (10..20).expanded 5
check! 5..25

print! (10..20).expanded -2
check! 12..18

print! (5..-5).expanded 5
check! 10..-10

print! (5..-5).expanded -5
check! 0..0

print! (5..-5).expanded -10
check! -5..5

intersection

|Range, Range| -> Range?

Returns a range representing the intersecting region of the two input ranges.

If there is no intersecting region then null is returned.

Example

print! (10..20).intersection 5..15
check! 10..15

print! (100..200).intersection 250..=150
check! 150..200

print! (0..10).intersection 90..99
check! null

is_inclusive

|Range| -> Bool

Returns true if the range has a defined end which is inclusive.

Example

print! (10..20).is_inclusive()
check! false

print! (1..=10).is_inclusive()
check! true

print! (100..).is_inclusive()
check! false

start

|Range| -> Number

Returns the start value of the range.

Example

print! (50..100).start()
check! 50

print! (10..0).start()
check! 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

print! (0..10).union 5
check! 0..10

print! (0..10).union 99
check! 0..100

a = 10..20
b = 40..50
print! a.union b
check! 10..50

string

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

bytes

|String| -> Iterator

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

Example

print! 'Hëy!'.bytes().to_tuple()
check! (72, 195, 171, 121, 33)

See Also

chars

|String| -> Iterator

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

A ‘character’ in Koto is defined as being a unicode grapheme cluster.

It’s worth noting that is the default iteration behaviour for a string, so calling 'hello'.chars() is equivalent to calling iterator.iter('hello').

Example

print! 'Héllø! 👋'.chars().to_tuple()
check! ('H', 'é', 'l', 'l', 'ø', '!', ' ', '👋')

See Also

char_indices

|String| -> Iterator

Returns an iterator that yields the indices of each grapheme cluster in the string.

Each cluster is represented as a range, which can then be used to extract the cluster from the string via indexing.

Example

s = 'Hi 👋'

print! indices = s.char_indices().to_tuple()
check! (0..1, 1..2, 2..3, 3..7)

print! s[indices[3]]
check! 👋

See Also

contains

|String, String| -> Bool

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

Example

print! 'xyz'.contains 'abc'
check! false

print! 'xyz'.contains 'yz'
check! true

print! 'xyz'.contains 'xyz'
check! true

print! 'xyz'.contains ''
check! true

ends_with

|String, String| -> Bool

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

Example

print! 'abcdef'.ends_with 'def'
check! true

print! 'xyz'.ends_with 'abc'
check! 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

print! '👋'.escape()
check! \u{1f44b}

is_empty

|String| -> Bool

Returns true if the string contains no characters.

Example

print! 'abcdef'.is_empty()
check! false

print! ''.is_empty()
check! true

from_bytes

|Iterable| -> String

Returns a string containing the bytes that are produced by the input iterable. The iterable output must contain only Numbers in the 0..=255 range. The resulting sequence of bytes must contain UTF-8 data.

Example

print! string.from_bytes (72, 195, 171, 121, 33)
check! Hëy!

See Also

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

print! 'foo\nbar\nbaz'.lines().to_tuple()
check! ('foo', 'bar', 'baz')

print! '\n\n\n'.lines().to_tuple()
check! ('', '', '')

repeat

|String, n: Number| -> String

Creates a new string by repeating the input n times.

Example

print! 'abc'.repeat 3
check! abcabcabc

replace

|String, match: String, replacement: String| -> String

Returns a copy of the input string with all occurrences of the match string replaced with a replacement string.

Example

print! '10101'.replace '0', 'x'
check! 1x1x1

split

|String, match: String| -> Iterator

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

|String, match: |String| -> Bool| -> Iterator

Returns an iterator that yields strings resulting from splitting the input string based on the result of calling a match function.

The match function will be called for each grapheme in the input string, and splits will occur when the function returns true.

Example

print! 'a,b,c'.split(',').to_tuple()
check! ('a', 'b', 'c')

print! 'O_O'.split('O').to_tuple()
check! ('', '_', '')

print! 'x!y?z'.split(|c| c == '!' or c == '?').to_tuple()
check! ('x', 'y', 'z')

starts_with

|String, match: String| -> Bool

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

Example

print! 'abcdef'.starts_with 'abc'
check! true

print! 'xyz'.starts_with 'abc'
check! false

strip_prefix

|input: String, prefix: String| -> String?

Returns the input string with the given prefix removed, or null if the input string doesn’t start with prefix.

Example

print! 'abc: xyz'.strip_prefix 'abc: '
check! xyz

print! 'xxxxx'.strip_prefix 'abc: '
check! null

See Also

strip_suffix

|input: String, suffix: String| -> String?

Returns the input string with the given suffix removed, or null if the input string doesn’t end with suffix.

Example

print! 'abc: xyz'.strip_suffix ' xyz'
check! abc:

print! 'xxxxx'.strip_suffix ' xyz'
check! null

See Also

to_lowercase

|String| -> String

Returns a lowercase version of the input string.

Example

print! 'HÉLLÖ'.to_lowercase()
check! héllö

print! 'O_o'.to_lowercase()
check! o_o

to_number

|String| -> Number?

Returns the string converted into a number.

  • 0x, 0o, and 0b prefixes will cause the parsing to treat the input as containing a hexadecimal, octal, or binary number respectively.
  • Otherwise the number is assumed to be base 10, and the presence of a decimal point will produce a float instead of an integer.

If a number can’t be produced then null is returned.

|String, base: Number| -> Number?

Returns the string converted into a number given the specified base.

The base must be an integer in the range 2..=36, otherwise an error will be thrown.

If the string contains non-numerical digits then null is returned.

Example

print! '123'.to_number()
check! 123

print! '-8.9'.to_number()
check! -8.9

print! '0x7f'.to_number()
check! 127

print! '0b10101'.to_number()
check! 21

print! '2N9C'.to_number(36)
check! 123456

to_uppercase

|String| -> String

Returns an uppercase version of the input string.

Example

print! 'héllö'.to_uppercase()
check! HÉLLÖ

print! 'O_o'.to_uppercase()
check! O_O

trim

|input: String| -> String

Returns a string with any whitespace removed from the start and end of the input.

|input: String, pattern: String| -> String

Returns a string with all occurrences of the pattern removed from the start and end of the input.

Example

print! '   x   '.trim()
check! x

print! '     !'.trim()
check! !

print! '----O_o----'.trim '-'
check! O_o

print! 'abcabc!!!abcabc'.trim 'abc'
check! !!!

See Also

trim_start

|input: String| -> String

Returns a string with any whitespace removed from the start of the input.

|input: String, pattern: String| -> String

Returns a string with all occurrences of the pattern removed from the start of the input.

Example

trimmed = '   x   '.trim_start()
print! (trimmed,)
check! ('x   ')

print! '----O_o----'.trim_start '-'
check! O_o----

print! 'abcabc!!!abcabc'.trim_start 'abc'
check! !!!abcabc

See Also

trim_end

|input: String| -> String

Returns a string with any whitespace removed from the end of the input.

|input: String, pattern: String| -> String

Returns a string with all occurrences of the pattern removed from the end of the input.

Example

print! '   x   '.trim_end()
check!    x

print! '     !     '.trim_end()
check!      !

print! '----O_o----'.trim_end '-'
check! ----O_o

print! 'abcabc!!!abcabc'.trim_end 'abc'
check! abcabc!!!

See Also

test

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

A collection of utilities for writing tests.

assert

|Bool| -> Null

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
try
  assert 1 > 2
catch error
  print error

assert_eq

|a: Any, b: Any| -> Null

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
try
  assert_eq 2 + 2, 5
catch error
  print error

assert_ne

|a: Any, b: Any| -> Null

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
try
  assert_ne 2 + 2, 4
catch error
  print error

assert_near

|a: Number, b: Number| -> Null
|a: Number, b: Number, error_margin: Number| -> Null

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.

The margin of error is optional, defaulting to 1.0e-12.

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
try
  assert_near 1.3, 1.32, allowed_error
catch error
  print error
# error: Assertion failed, '1.3' and '1.32' are not within 0.01 of each other

# The allowed margin of error is optional, defaulting to a very small value
assert_near 1 % 0.2, 0.2

run_tests

|tests: Map| -> Null

Runs the @test functions contained in the map.

@pre_test and @post_test functions can be implemented in the same way as when exporting module tests. @pre_test will be run before each @test, and @post_test will be run after.

Example

make_x = |n|
  data: n
  @+: |other| make_x self.data + other.data
  @-: |other| make_x self.data - other.data

x_tests =
  @pre_test: ||
    self.x1 = make_x 100
    self.x2 = make_x 200

  @post_test: ||
    print 'Test complete'

  @test addition: ||
    print 'Testing addition'
    assert_eq self.x1 + self.x2, make_x 300

  @test subtraction: ||
    print 'Testing subtraction'
    assert_eq self.x1 - self.x2, make_x -100

  @test failing_test: ||
    print 'About to fail'
    assert false

try
  test.run_tests x_tests
catch _
  print 'A test failed'
check! Testing addition
check! Test complete
check! Testing subtraction
check! Test complete
check! About to fail
check! A test failed

tuple

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

contains

|Tuple, value: Any| -> Bool

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

Matching is performed with the == equality operator.

Example

print! (1, "hello", [99, -1]).contains "hello"
check! true

print! ("goodbye", 123).contains "hello"
check! false

first

|Tuple| -> Any?

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

Example

x = 99, -1, 42
print! x.first()
check! 99

print! ().first()
check! null

get

|Tuple, index: Number| -> Any?
|Tuple, index: Number, default: Any| -> Any?

Gets the Nth value in the tuple. If the tuple doesn’t contain a value at that position then the provided default value is returned. If no default value is provided then null is returned.

Example

x = 99, -1, 42

print! x.get 1
check! -1

print! x.get -1
check! null

print! x.get 5, "abc"
check! abc

is_empty

|Tuple| -> Bool

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

Example

print! ().is_empty()
check! true

print! (1, 2, 3).is_empty()
check! false

last

|Tuple| -> Any?

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

Example

x = 99, -1, 42
print! x.last()
check! 42

print! (,).last()
check! null

sort_copy

|Tuple| -> Tuple

Returns a sorted copy of the tuple.

|List, key: |Any| -> Any| -> List

Returns a sorted copy of the tuple, based on the output of calling a key function for each of the tuple’s elements.

The key function’s result is cached, so it’s only called once per value.

Example

x = (1, -1, 99, 42)
y = x.sort_copy()
print! y
check! (-1, 1, 42, 99)

print! x # x remains untouched
check! (1, -1, 99, 42)

# Sort in reverse order by using a key function
print! x.sort_copy |n| -n
check! (99, 42, 1, -1)

to_list

|Tuple| -> List

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

Example

print! (1, 2, 3).to_list()
check! [1, 2, 3]

color

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

Utilities for working with color in Koto.

At the core of the library is the Color type, with various initializers available.

For convenience, the color module itself is callable as a shorthand for some standard initializers:

|String| -> Color

Equivalent to calling color.named, or color.hex if no matching name is found.

|Number| -> Color

Equivalent to calling color.hex with a number.

|r: Number, g: Number, b: Number| -> Color
|r: Number, g: Number, b: Number, a: Number| -> Color

Equivalent to calling color.rgb.

Example:

print! color 'red'
check! Color(RGB, r: 1, g: 0, b: 0, a: 1)

print! color '#00ffff'
check! Color(RGB, r: 0, g: 1, b: 1, a: 1)

print! color 0xff00ff
check! Color(RGB, r: 1, g: 0, b: 1, a: 1)

print! color 0, 0.5, 1, 0.5
check! Color(RGB, r: 0, g: 0.5, b: 1, a: 0.5)

hex

|String| -> Color

Creates a color from the given hex triplet string, e.g. '#7fee80'.

The # prefix is optional, and the 3 digit shorthand version (e.g. '#7e8') can also be used.

If the string can’t be parsed as a hex triplet then null will be returned.

|String| -> Color

Example

print! color.hex '#ff00ff'
check! Color(RGB, r: 1, g: 0, b: 1, a: 1)

print! color.hex 'f0f'
check! Color(RGB, r: 1, g: 0, b: 1, a: 1)

print! color.hex 0x00ff00
check! Color(RGB, r: 0, g: 1, b: 0, a: 1)

hsl

|h: Number, s: Number, l: Number| -> Color
|h: Number, s: Number, l: Number, a: Number| -> Color

Returns a color produced from hue, saturation, lightness, and optional alpha components.

The hue component is specified in degrees.

The saturation, lightness, and alpha components are specified as numbers between 0 and 1.

Example

print! color.hsl 180, 1, 0.25
check! Color(HSL, h: 180, s: 1, l: 0.25, a: 1)

hsv

|h: Number, s: Number, v: Number| -> Color
|h: Number, s: Number, v: Number, a: Number| -> Color

Returns a color produced from hue, saturation, value, and optional alpha components.

The hue component is specified in degrees.

The saturation, value, and alpha components are specified as numbers between 0 and 1.

Example

print! color.hsv 90, 0.5, 1
check! Color(HSV, h: 90, s: 0.5, v: 1, a: 1)

named

|name: String| -> Color?

Returns a color corresponding to one of the named colors listed in the SVG color keywords specification.

If no name is found then null will be returned.

Example

print! color.named 'yellow'
check! Color(RGB, r: 1, g: 1, b: 0, a: 1)

oklab

|l: Number, a: Number, b: Number| -> Color
|l: Number, a: Number, b: Number, alpha: Number| -> Color

Returns a color produced from lightness, a, b, and optional alpha components, using the Oklab color space.

The lightness and alpha components are specified as numbers between 0 and 1.

The a (green/red) and b (blue/yellow) components are numbers with values typically between -0.4 and 0.4.

Example

print! color.oklab 0.5, 0.1, -0.2
check! Color(Oklab, l: 0.5, a: 0.1, b: -0.2, a: 1)

oklch

|l: Number, c: Number, h: Number| -> Color
|l: Number, c: Number, h: Number, a: Number| -> Color

Returns a color produced from lightness, chroma, hue, and optional alpha components, using the Oklab color space.

The lightness and alpha components are specified as numbers between 0 and 1.

The hue component is specified in degrees.

The chroma component is a number between 0 and a maximum that depends on the hue and lightness components.

Example

print! color.oklch 0.6, 0.1, 180
check! Color(Oklch, l: 0.6, c: 0.1, h: 180, a: 1)

rgb

|r: Number, g: Number, b: Number| -> Color

Returns a color produced from red, green, blue, and optional alpha components, using the sRGB color space.

The color components are specified as numbers between 0 and 1.

Example

print! color.rgb 0.5, 0.1, 0.9
check! Color(RGB, r: 0.5, g: 0.1, b: 0.9, a: 1)

print! color.rgb 0.2, 0.4, 0.3, 0.5
check! Color(RGB, r: 0.2, g: 0.4, b: 0.3, a: 0.5)

Color

The color module’s core color type.

The color may belong to various different color spaces, with the space’s components available via iteration or indexing.

The color’s alpha value is always present as the color’s fourth component.

The color space’s components can be modified via index, and the alpha component can also be modified via .set_alpha.

Example

r, g, b = color 'yellow'
print! r, g, b
check! (1.0, 1.0, 0.0)

h, s, v, a = color.hsv 90, 0.5, 0.25
print! h, s, v, a
check! (90.0, 0.5, 0.25, 1.0)

print! color('red')[0]
check! 1.0

print! c = color.oklch 0.5, 0.1, 180
check! Color(Oklch, l: 0.5, c: 0.1, h: 180, a: 1)
c[0] = 0.25 # Set the lightness component to 0.25
c[1] = 0.1 # Set the chroma component to 0.1
print c
check! Color(Oklch, l: 0.25, c: 0.1, h: 180, a: 1)

Color.alpha

|Color| -> Number

Returns the color’s alpha value.

Example

c = color 'red'

print! c.alpha()
check! 1.0

c[3] = 0.5
print! c.alpha()
check! 0.5

Color.set_alpha

|Color, alpha: Number| -> Color

Sets the color’s alpha component to the given value, and returns the color.

Example

c = color 'red'

print! c.set_alpha(0.25).alpha()
check! 0.25

Color.mix

|a: Color, b: Color| -> Color

Returns a new color representing an even mix of the two input colors.

An error is thrown if the colors do not belong to the same color space.

|a: Color, b: Color, weight: Number| -> Color

Returns a new color representing a weighted mix of the two input colors.

The weight argument is a number between 0 and 1, with values closer to 0 producing results closer to the first color, and values closer to 1 producing results closer to the second color.

An error is thrown if the colors do not belong to the same color space.

Example

a, b = color('red'), color('blue')
print! a.mix b
check! Color(RGB, r: 0.5, g: 0, b: 0.5, a: 1)

print! a.mix b, 0.25
check! Color(RGB, r: 0.75, g: 0, b: 0.25, a: 1)

Color.to_hsl

|Color| -> Color

Returns a new color with the input converted into the HSL color space.

Example

print! color('blue').to_hsl()
check! Color(HSL, h: 240, s: 1, l: 0.5, a: 1)

Color.to_hsv

|Color| -> Color

Returns a new color with the input converted into the HSV color space.

Example

print! color('blue').to_hsv()
check! Color(HSV, h: 240, s: 1, v: 1, a: 1)

Color.to_oklab

|Color| -> Color

Returns a new color with the input converted into the Oklab color space.

Example

l, a, b = color('blue').to_oklab()
allowed_error = 1e-3
assert_near l, 0.452, allowed_error
assert_near a, -0.033, allowed_error
assert_near b, -0.312, allowed_error

Color.to_oklch

|Color| -> Color

Returns a new color with the input converted into the Oklch color space.

Example

l, c, h = color('blue').to_oklch()
allowed_error = 1e-3
assert_near l, 0.452, allowed_error
assert_near c, 0.313, allowed_error
assert_near h, 264.052, allowed_error

Color.to_rgb

|Color| -> Color

Returns a new color with the input converted into the sRGB color space.

Example

l, c, h = color('blue').to_oklch()
allowed_error = 1e-3
assert_near l, 0.452, allowed_error
assert_near c, 0.313, allowed_error
assert_near h, 264.052, allowed_error

geometry

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

Utilities for working with geometry in Koto.

The module contains the Vec2, Vec3, and Rect types.

rect

|| -> Rect

Initializes a default Rect with each component set to 0.

|x: Number, y: Number, width: Number, height: Number| -> Rect
|xy: Vec2, size: Vec2| -> Rect

Initializes a Rect with corresponding position and size.

Example

from geometry import rect, vec2

print! rect()
check! Rect{x: 0, y: 0, width: 0, height: 0}

print! rect 10, 20, 30, 40
check! Rect{x: 10, y: 20, width: 30, height: 40}

print! rect (vec2 -1, 2), (vec2 99, 100)
check! Rect{x: -1, y: 2, width: 99, height: 100}

vec2

|| -> Vec2

Initializes a default Vec2 with each component set to 0.

|x: Number| -> Vec2

Initializes a Vec2 with x specified, and y set to 0.

|x: Number, y: Number| -> Vec2
|xy: Vec2| -> Vec2

Initializes a Vec2 with corresponding x and y components.

Example

from geometry import vec2

print! vec2()
check! Vec2{x: 0, y: 0}

print! vec2 99, 100
check! Vec2{x: 99, y: 100}

vec3

|| -> Vec3

Initializes a default Vec3 with each component set to 0.

|x: Number| -> Vec3

Initializes a Vec3 with x specified, and all other components set to 0.

|x: Number, y: Number| -> Vec3
|xy: Vec2| -> Vec3

Initializes a Vec3 with x and y specified, and z set to 0.

|x: Number, y: Number, z: Number| -> Vec3
|xy: Vec2, z: Number| -> Vec3
|xyz: Vec3| -> Vec3

Initializes a Vec3 with specified x, y, and z components.

Example

from geometry import vec2, vec3

print! vec3()
check! Vec3{x: 0, y: 0, z: 0}

print! vec3 -1, 3
check! Vec3{x: -1, y: 3, z: 0}

print! vec3 10, 20, 30
check! Vec3{x: 10, y: 20, z: 30}

print! vec3 (vec2 -1, -2), 5
check! Vec3{x: -1, y: -2, z: 5}

Rect

The Rect type represents a 2-dimensional rectangle, with a defined position and size.

The position is interpreted as being at the center of the rectangle.

Comparison operations are available, and the rect’s components are iterable.

Example

r = geometry.rect 10, 20, 30, 40
x, y, w, h = r
print! x, y, w, h
check! (10.0, 20.0, 30.0, 40.0)

Rect.left

|Rect| -> Number

Returns the position of rectangle’s left edge.

Example

# Create a rectangle centered at 0, 0
r = geometry.rect 0, 0, 200, 100
print! r.left()
check! -100.0

Rect.right

|Rect| -> Number

Returns the position of rectangle’s right edge.

Example

# Create a rectangle centered at 0, 0
r = geometry.rect 0, 0, 200, 100
print! r.right()
check! 100.0

Rect.top

|Rect| -> Number

Returns the position of rectangle’s top edge.

Example

# Create a rectangle centered at 0, 0
r = geometry.rect 0, 0, 200, 100
print! r.top()
check! 50.0

Rect.bottom

|Rect| -> Number

Returns the position of rectangle’s bottom edge.

Example

# Create a rectangle centered at 0, 0
r = geometry.rect 0, 0, 200, 100
print! r.bottom()
check! -50.0

Rect.width

|Rect| -> Number

Returns the width of the rectangle.

Example

r = geometry.rect 0, 0, 200, 100
print! r.width()
check! 200.0

Rect.height

|Rect| -> Number

Returns the width of the rectangle.

Example

r = geometry.rect 0, 0, 200, 100
print! r.height()
check! 100.0

Rect.center

|Rect| -> Vec2

Returns the center point of the rectangle.

Example

r = geometry.rect -100, 42, 200, 100
print! r.center()
check! Vec2{x: -100, y: 42}

Rect.x

|Rect| -> Vec2

Returns the x component of the rectangle’s center point.

Example

r = geometry.rect -100, 42, 200, 100
print! r.x()
check! -100.0

Rect.y

|Rect| -> Vec2

Returns the y component of the rectangle’s center point.

Example

r = geometry.rect -100, 42, 200, 100
print! r.y()
check! 42.0

Rect.contains

|Rect, xy: Vec2| -> Vec2

Returns true if the given Vec2 is located within the rectangle’s bounds.

Example

from geometry import rect, vec2

r = rect 0, 0, 200, 200

print! r.contains vec2 50, 50
check! true
print! r.contains vec2 500, 500
check! false

Rect.set_center

|Rect, x: Number y: Number| -> Rect
|Rect, xy: Vec2| -> Rect

Sets the rect’s center position to the given x and y coordinates, and returns the rect.

Example

from geometry import rect, vec2

r = rect 0, 0, 200, 200

print! r.set_center 10, 10
check! Rect{x: 10, y: 10, width: 200, height: 200}
print! r.set_center vec2()
check! Rect{x: 0, y: 0, width: 200, height: 200}

Vec2

The Vec2 type represents a 2-dimensional vector, with x and y coordinates.

Arithmetic operations are supported, and the vector’s coordinates are iterable.

Example

from geometry import vec2

print! (vec2 10, 20) + (vec2 30, 40)
check! Vec2{x: 40, y: 60}

v = vec2 50, 100
v *= 2 * vec2 0.5, 2
x, y = v
print! x, y
check! (50.0, 400.0)
print! v -= 100
check! Vec2{x: -50, y: 300}

Vec2.angle

|Vec2| -> Number

Returns the angle of the vector, expressed in radians.

Example

from geometry import vec2

print! (vec2 1, 0).angle()
check! 0.0
print '{(vec2 0, 1).angle():.3}'
check! 1.571
print '{(vec2 -1, 0).angle():.3}'
check! 3.142
print '{(vec2 0, -1).angle():.3}'
check! -1.571

Vec2.length

|Vec2| -> Number

Returns the length of the vector.

Example

from geometry import vec2

print! (vec2 0, 0).length()
check! 0.0
print! (vec2 3, 4).length()
check! 5.0
print! (vec2 -4, -3).length()
check! 5.0

Vec2.x

|Vec2| -> Number

Returns the x coordinate of the vector.

Example

from geometry import vec2

print! (vec2 -1, 0).x()
check! -1.0
print! (vec2 3, 4).x()
check! 3.0

Vec2.y

|Vec2| -> Number

Returns the y coordinate of the vector.

Example

from geometry import vec2

print! (vec2 0, -2).y()
check! -2.0
print! (vec2 3, 4).y()
check! 4.0

Vec3

The Vec3 type represents a 3-dimensional vector, with x, y, and z coordinates.

Arithmetic operations are supported, and the vector’s coordinates are iterable.

Example

from geometry import vec3

print! (vec3 10, 20, 30) + (vec3 40, 50, 60)
check! Vec3{x: 50, y: 70, z: 90}

v = 10 * vec3 5, 10, 15
v *= vec3 0.5, 2, -1
x, y, z = v
print! x, y, z
check! (25.0, 200.0, -150.0)

Vec3.x

|Vec3| -> Number

Returns the x coordinate of the vector.

Example

from geometry import vec3

print! (vec3 -1, 0, 1).x()
check! -1.0

Vec3.y

|Vec3| -> Number

Returns the y coordinate of the vector.

Example

from geometry import vec3

print! (vec3 -1, -2, -3).y()
check! -2.0

Vec3.z

|Vec3| -> Number

Returns the z coordinate of the vector.

Example

from geometry import vec3

print! (vec3 10, 20, 30).z()
check! 30.0

Vec3.length

|Vec3| -> Number

Returns the length of the vector.

Example

from geometry import vec3

print! (vec3 0, 0, 10).length()
check! 10.0
print! (vec3 1, 2, 2).length()
check! 3.0

json

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

JSON support for Koto.

from_string

|String| -> Any

Deserializes a string containing JSON data, returning a structured Koto value.

Example

data = r'
{
  "string": "O_o",
  "nested": {
    "number": -1.2
  },
  "entries": [
    {
      "foo": "bar"
    },
    {
      "foo": "baz"
    }
  ]
}'

result = json.from_string data
print! result.string
check! O_o
print! result.nested.number
check! -1.2
print! result.entries[0].foo
check! bar
print! result.entries[1].foo
check! baz

to_string

|Any| -> String

Returns a string containing the input value serialized as JSON data.

Example

data =
  string: '>_>'
  nested:
    number: 99
  entries: (
    {foo: 'bar'},
    {foo: 'baz'},
  )

print! json.to_string data
check! {
check!   "string": ">_>",
check!   "nested": {
check!     "number": 99
check!   },
check!   "entries": [
check!     {
check!       "foo": "bar"
check!     },
check!     {
check!       "foo": "baz"
check!     }
check!   ]
check! }

random

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

Utilities for generating random values in Koto.

At the core of the module is the Rng type, which is a seedable random number generator. Each thread has access to a generator with a randomly selected seed, or unique generators can be created with random.generator.

The xoshiro256++ algorithm is used to generate random values, which is fast and portable, but not cryptographically secure.

bool

|| -> Bool

Generates a random boolean using the current thread’s generator.

Example

# Seed the thread Rng so that we get predictable results
random.seed 99

print! random.bool()
check! false
print! random.bool()
check! true

generator

|| -> Rng

Creates an Rng with a randomly generated seed.

|Number| -> Rng

Creates an Rng with a specified seed.

Example

rng = random.generator 99
print! rng.pick (1, 2, 3)
check! 1
print! rng.bool()
check! true

number

|| -> Number

Generates a random number using the current thread’s generator.

The number will be a floating point value in the range from 0 up to but not including 1.

Example

# Seed the thread Rng so that we get predictable results
random.seed 123

# Print random floats up to 3 decimal places
print '{random.number():.3}'
check! 0.646
print '{random.number():.3}'
check! 0.838

pick

|Indexable| -> Any?

Selects a random value from the input using the current thread’s generator.

  • If the input is empty, then null will be returned.
  • If the input is a map, then a tuple containing the key and value of a randomly selected entry will be returned.
  • If the input is a range, then the result will be an integer within the given range.
  • If the input is some other indexable type (like a list or tuple), then a randomly selected element from the input will be returned.

Example

# Seed the thread Rng so that we get predictable results
random.seed -1

print! random.pick (123, -1, 99)
check! -1
print! random.pick 10..20
check! 19
print! random.pick {foo: 42, bar: 99, baz: 123}
check! ('baz', 123)
print! random.pick []
check! null

seed

|Number| -> Null

Seeds the current thread’s generator so that it produces predictable results.

Example

from iterator import generate
from random import pick, seed

# Returns a tuple containing three numbers from 1 to 10
pick_3 = || generate((|| pick 1..=10), 3).to_tuple()

seed 1
print! pick_3()
check! (9, 8, 2)

seed 2
print! pick_3()
check! (8, 6, 7)

seed 1
print! pick_3()
check! (9, 8, 2)

shuffle

|Indexable| -> Any

Reorders the entries in a container so that they have a new randomly shuffled order, and returns the container.

from random import seed, shuffle

x = [1, 2, 3, 4, 5]

seed 2
print! shuffle x
check! [1, 5, 4, 3, 2]
print! shuffle x
check! [3, 1, 4, 2, 5]

y = {a: 1, b: 2, c: 3}
print! shuffle y
check! {c: 3, a: 1, b: 2}
print! shuffle y
check! {c: 3, b: 2, a: 1}

Rng

Rng is the random module’s core random generator.

The xoshiro256++ algorithm is used to generate random values, which is fast and portable, but not cryptographically secure.

Rng.bool

See random.bool.

Rng.number

See random.number.

Rng.pick

See random.pick.

Rng.shuffle

See random.shuffle.

Rng.seed

See random.seed.

regex

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

Regular expression utilities for Koto.

new

|String| -> Regex

Creates a new Regex from the given string.

Regex

The core regular expression type used by the regex module.

The regex module is a wrapper for the regex crate, please see its documentation for more information, including a guide to the supported syntax.

Regex.is_match

|Regex, input: String| -> Bool

Returns true if the given input string matches against the regular expression.

Example

r = regex.new r'x\d\d'
print! r.is_match 'x42'
check! true
print! r.is_match 'z99'
check! false

Regex.find

|Regex, input: String| -> Match?

If the given input string matches the regular expression, then an instance of Match is returned that allows the first matched region in the string to be inspected. If no matches are found then null is returned.

Example

# Make a regex that will match against any group of 2 or 3 a-z characters
r = regex.new r'[a-z]{2,3}'
found = r.find 'a b xyz jk mno'
print! found.text(), found.range()
check! ('xyz', 4..7)

print! r.find '12345'
check! null

Regex.find_all

|Regex, input: String| -> Matches?

If the given input string matches the regular expression, then an instance of Matches is returned that allows all matches in the input to be inspected. If no matches are found then null is returned.

Example

# Make a regex that will match against any group of 2 or 3 a-z characters
r = regex.new r'[a-z]{2,3}'
matches = r.find_all('a bc def gh')
for found in matches
  print found.text(), found.range()
check! ('bc', 2..4)
check! ('def', 5..8)
check! ('gh', 9..11)

Regex.captures

|Regex, input: String| -> Map?

If the given string matches the regular expression, then a map is returned containing the first match found, along with matches for any capture groups. If no matches are found then null is returned.

Captured groups are entered in the map with their indices as the key, and if the group is named then it’s also inserted with the name. The first entry in the map (index 0) contains the entire match, with subsequent captures starting at index 1.

Example

# Make a regex that will match against two words inside <> braces
r = regex.new r'<(?<group_a>\S+)\s+(\S+)>'
captures = r.captures '!!! <Hello, World!> ???'

# Entry 0 contains the complete match
print! captures.get(0).text()
check! <Hello, World!>

# Named captured groups use the name as the map key
print! captures.group_a.text()
check! Hello,

# Groups without names use their group index as the map key
print! captures.get(2).text()
check! World!

# Named capture groups are also available by index
group_name, capture = captures.get_index(1)
print! group_name, capture.text()
check! ('group_a', 'Hello,')

Regex.replace_all

|Regex, input: String, replacement: String| -> String

Returns a string with each match in the input replaced using rules defined in the replacement string.

# Make a regex that will match against two words inside <> braces
r = regex.new r'<(?<a>\S+)\s+(?<b>\S+)>'
print! r.replace_all '!!! <Replace Me> !!!', '>_>'
check! !!! >_> !!!

# Capture groups can be referred to in the replacement string
print! r.replace_all '!!! <AAA BBB> !!!', '[$a$b $a$b]'
check! !!! [AAABBB AAABBB] !!!

Matches

Matches is an iterator that outputs a Match for each match resulting from a call to Regex.find_all.

Match

Match is a type produced from calls to search functions like Regex.find or Regex.captures that provides access to the matched region of the input string, along with the matched region’s indices.

Match.text

|Match| -> String

Returns the matched region of the input string.

Example

m = regex.new(r'x\d\d').find 'abc def x99 123'
print! m.text()
check! x99

Match.range

|Match| -> Range

Returns the indices of the matched region in the input string.

Example

m = regex.new(r'x\d\d').find 'abc def x99 123'
print! m.range()
check! 8..11

tempfile

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

Utilities for working with temporary files in Koto.

temp_file

|| -> File

Creates and returns a temporary file.

This is a wrapper for NamedTempFile from the tempfile crate, please refer to the documentation for more information.

Example

f = temp_file.tempfile()
print! f.path()
check! /path/to/a/temporary/file

toml

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

TOML support for Koto.

from_string

|String| -> Any

Deserializes a string containing TOML data, returning a structured Koto value.

Example

data = r"
string = 'O_o'

[nested]
number = -1.2

[[entries]]
foo = 'bar'

[[entries]]
foo = 'baz'
"

result = toml.from_string data
print! result.string
check! O_o
print! result.nested.number
check! -1.2
print! result.entries[0].foo
check! bar
print! result.entries[1].foo
check! baz

to_string

|Any| -> String

Returns a string containing the input value serialized as TOML data.

Example

data =
  string: '>_>'
  nested:
    number: 99
  entries: (
    {foo: 'bar'},
    {foo: 'baz'},
  )

print! toml.to_string data
check! string = ">_>"
check!
check! [nested]
check! number = 99
check!
check! [[entries]]
check! foo = "bar"
check!
check! [[entries]]
check! foo = "baz"
check!

yaml

Derived from Koto documentation (MIT, github.com/koto-lang/koto), maintained for koto-calc.

YAML support for Koto.

from_string

|String| -> Any

Deserializes a string containing YAML data, returning a structured Koto value.

Example

data = r'
string: O_o

nested:
  number: -1.2

entries:
- foo: bar
- foo: baz
'

result = yaml.from_string data
print! result.string
check! O_o
print! result.nested.number
check! -1.2
print! result.entries[0].foo
check! bar
print! result.entries[1].foo
check! baz

to_string

|Any| -> String

Returns a string containing the input value serialized as YAML data.

Example

data =
  string: '>_>'
  nested:
    number: 99
  entries: (
    {foo: 'bar'},
    {foo: 'baz'},
  )

print! yaml.to_string data
check! string: '>_>'
check! nested:
check!   number: 99
check! entries:
check! - foo: bar
check! - foo: baz
check!