Skip to content

Types and Values

Simple values look similar to values from JavaScript, Java, C#.

Type names consistently start with a capital letter though:

  • Boolean✓ ; not boolean
  • Int✓ ; not int
  • String✓ ; not string

Value names, like lower-variable names, start with a lower-case letter: false✓ ; not False✗.

class Boolean

A Boolean is a truth value that may be used in if and loop conditions.

BooleanLiteral := "false" | "true" false true

Boolean extends AnyValue, Equatable

methods

Boolean.constructor

: fn(Boolean): Void

Boolean.toString

: fn(Boolean): String

For false, the string "false". For true, the string "true".

class Int

Int is the general-purpose signed integer value type.

Int Syntax Examples

Integers can be runs of decimal digits.

123
// ✅ 123

Zero is a valid number, but C-style octal literals (with zero padding) would be a source of confusion.

0
// ✅ 0
let hundred = 100;
let ten     = 010; // <!-- No
let one     = 001; // <!-- Still no
// ❌ Interpreter encountered error()!

You can't use commas in a number literal to make large numbers readable, but you can use an underscore to separate digit groups.

[123,456,789] == [123 , 456 , 789] &&  // Commas separate elements
[123_456_789] == [123456789]
// ✅

Exponential notation is fine for floating point values, but not for integers.

1e2 == 100.0
// ✅

And feel free to use a base like hexadecimal or binary when that fits what you're modelling.

0x10 == 16 && // Hex
0b10 ==  2 && // Binary
0o10 ==  8
// ✅

Int extends AnyValue, MapKey

methods

Int.constructor

: fn(Int): Void

Int.toFloat64

: fn(Int): Float64 | Bubble

Bubbles if the result isn't within the exponent error of the original.

Int.toFloat64Unsafe

: fn(Int): Float64

If outside bounds, returns a backend-dependent value. Use fuzz testing.

Int.toString

: fn(Int, Int | Null): String

Supports radix 2 through 36. Doesn't prefix + for positive.

Int.min

: fn(Int, Int): Int

Int.max

: fn(Int, Int): Int

class Float64

A Float64 is an IEEE-754 64-bit (aka double-precision) floating point number.

Syntax for Float64Literal

Float64Literal := ((whole number: [0-9]+*("_")) "." (fraction: [0-9]++("_")) (exponent: ("E" | "e") (() | "+" | "-") [0-9]++("_"))? | (whole number: [0-9]+*("_")) (exponent: ("E" | "e") (() | "+" | "-") [0-9]++("_"))) (suffix: ("D" | "d")+)? | (whole number: [0-9]+*("_")) (suffix: ("D" | "d")+) [0-9] _ whole number . [0-9] _ fraction E e + - [0-9] _ exponent [0-9] _ whole number E e + - [0-9] _ exponent D d suffix [0-9] _ whole number D d suffix

Float64 Syntax Examples

A number with a decimal point is a Float64.

123.456
// ✅ 123.456

You can make big numbers more readable by separating digits with underscore(_).

123_456.789 == 123.456_789e3
// ✅

A number with an exponent is a Float64 even if it does not have a decimal point. The exponent follows letter 'e', either upper or lower-case.

123e2 == 12_300.0 &&
123e2 == 123E2
// ✅

Exponents may have a sign.

125e+2     == 12_500.0 &&
1.25e+2    == 125.0 &&
125e-2     == 1.25 &&
125e-0_002 == 1.25  // Exponents are rarely big, but you may break them up.
// ✅

An integer-like number with a decimal suffix is a Float64.

1D == 1.0 &&
1d == 1.0
// ✅

Unlike in C, digits are required after a decimal point.

1.
// ❌ Operator Dot expects at least 2 operands but got 1!, Expected function type, but got Invalid!

Which allows Temper to more flexibly combine numbers with class-use syntax.

64.toString(16 /* hex radix */) == "40"
// That '.' is not a decimal point, so that's an integer.
// ✅

Temper also does not recognize all C's number suffixes.

1F
// ❌ Interpreter encountered error()!

Float64 extends AnyValue, Equatable

methods

Float64.constructor

: fn(Float64): Void

Float64.toInt

: fn(Float64): Int | Bubble

Truncates toward zero. Bubbles if the error isn't less than 1.

Float64.toIntUnsafe

: fn(Float64): Int

If outside bounds, returns a backend-dependent value. Use fuzz testing.

Float64.toString

: fn(Float64): String

Float64.abs

: fn(Float64): Float64

Float64.acos

: fn(Float64): Float64

Float64.asin

: fn(Float64): Float64

Float64.atan

: fn(Float64): Float64

Float64.atan2

: fn(Float64, Float64): Float64

The y coordinate is this object.

Float64.ceil

: fn(Float64): Float64

Float64.cos

: fn(Float64): Float64

Float64.cosh

: fn(Float64): Float64

Float64.exp

: fn(Float64): Float64

Float64.expm1

: fn(Float64): Float64

Float64.floor

: fn(Float64): Float64

Float64.log

: fn(Float64): Float64

Float64.log10

: fn(Float64): Float64

Float64.log1p

: fn(Float64): Float64

Float64.max

: fn(Float64, Float64): Float64

Result is NaN if either is NaN.

Float64.min

: fn(Float64, Float64): Float64

Result is NaN if either is NaN.

Float64.near

: fn(Float64, Float64, Float64 | Null, Float64 | Null): Boolean

Matches semantics of Python's math.isclose.

Float64.round

: fn(Float64): Float64

Float64.sign

: fn(Float64): Float64

Float64.sin

: fn(Float64): Float64

Float64.sinh

: fn(Float64): Float64

Float64.sqrt

: fn(Float64): Float64

Float64.tan

: fn(Float64): Float64

Float64.tanh

: fn(Float64): Float64

statics

Float64.e

static : Float64

Float64.pi

static : Float64

class String

A String is a chunk of textual content.

StringLiteral := "\u0022" (SourceCharacter - (\\n\, \\r\, \\\, \"\) | EscapeSequence | (UTF-16 Code Unit: "\\u" Hex Hex Hex Hex) | (Unicode Scalar Values: "\\u{" HexDigits+(",") "}") | (Interpolation: "\u0024{" Expr "}"))* "\u0022" | (Multi-quoted string may start with 3 or more "\s;: "\u0022\u0022\u0022⁺" "LineBreak" (SourceCharacter - (\\\) | EscapeSequence | (UTF-16 Code Unit: "\\u" Hex Hex Hex Hex) | (Unicode Scalar Values: "\\u{" HexDigits+(",") "}") | (Interpolation: "\u0024{" Expr "}"))* "LineBreak" indentation "\u0022\u0022\u0022⁺") " SourceCharacter - ('\n', '\r', '\', '"') EscapeSequence \u Hex Hex Hex Hex UTF-16 Code Unit \u{ HexDigits , } Unicode Scalar Values ${ Expr } Interpolation " """⁺ LineBreak SourceCharacter - ('\') EscapeSequence \u Hex Hex Hex Hex UTF-16 Code Unit \u{ HexDigits , } Unicode Scalar Values ${ Expr } Interpolation LineBreak indentation """⁺ Multi-quoted string may start with 3 or more "'s;

To encourage code with repeatable behavior, strings do not have lengths; the length of a string depends on the kind of characters, which differ across programming languages. See the documentation for string indices to see how to compare character counts and understand more about semantic consistency of string operations.

String values have a syntax similar to JavaScript, but instead of using backticks ` for multi-line strings with interpolation, use triple-quotes: """ for multi-line strings.

Multi-quoted strings start with 3 or more "'s.

"""
3 quotes
""" == "3 quotes"
// ✅

The end delimiter must match the start. Each delimiter must be on its own line.

""""
A quadruple-quoted string
may embed 3 quotes: """.
"""" == "A quadruple-quoted string\nmay embed 3 quotes: \"\"\"."
// ✅

It's an error to put too many quotes in a row inside a multi-quoted string.

"""
It's illegal to put 3 or more in a triple-quoted string.
Don't do this: """".
"""
// ❌ Overlong multi-quote delimiter not allowed here!, Expected a Expression here!

Escape Sequences

Escape sequences in Temper are roughly like those in C, Java, JavaScript, JSON, etc.

EscapeSequence := "\\0" | "\\\\" | "\\/" | "\\\u0022" | "\\\" | "\\`" | "\\{" | "\\}" | "\\\u0024" | "\\b" | "\\t" | "\\n" | "\\f" | "\\r" | "\\" \0 Encodes NUL \\ Encodes a single backslash \/ Encodes a forward-slash \" Encodes a double-quote \' Encodes a single-quote \` Encodes a back-quote \{ Encodes a left curly-bracket \} Encodes a right curly-bracket \$ Encodes a dollar sign \b Encodes a backspace \t Encodes a tab \n Encodes a line-feed a.k.a. new-line \f Encodes a form-feed \r Encodes a carriage-return \ Broken escape sequence encodes nothing

The following escape sequences are recognized in strings:

Escape Meaning
\0 Codepoint 0, NUL
\\ A single backslash
\/ A forward-slash
\" A double-quote
\' A single-quote
\` A back-quote
\{ A left curly-bracket
\} A right curly-bracket
\$ A dollar sign
\b A backspace
\t A tab
\n A line-feed a.k.a. new-line
\v A vertical tab
\f A form-feed
\r A carriage-return
\u xxxx A four-digit hex escape sequence
\u{ xxxxx , xxx } One or more comma-separated hex code-points
\ Broken escape

Incidental spaces in multi-line strings

When a string spans multiple lines, some space is significant; it contributes to the content of the resulting string value. Spaces that do not contribute to the content are called incidental spaces. Incidental spaces include:

  • those used for code indentation, and
  • those that appear at the end of a line so are invisible to readers, and often automatically stripped by editors, and
  • carriage returns which may be inserted or removed depending on whether a file is edited on Windows or UNIX.

Normalizing incidental space steps include:

  1. Removing leading space on each line that match the indentation of the close quote.
  2. Removing the newline after the open quote, and before the close quote.
  3. Removing space at the end of each line.
  4. Normalizing line break sequences CRLF, CR, and LF to LF.

For the purposes of identifying incidental space, we imagine that any interpolation ${...}, scriptlet {:...:}, or hole ${} contributes 1 or more non-space, non-line-break characters.

Indentation matching the close quote is incidental, hence removed.

"""
    Line 1
    Line 2
    """
== "Line 1\nLine 2"
// ✅

Spaces are stripped equally, so lines may be indented past the close quote.

"""
     Line 1
      Line 2
       Line 3
    """
== " Line 1\n  Line 2\n   Line 3"
// ✅

It's an error if a line is not un-indented from the close quote.

"""
    Line 1
  Line 2 not indented enough
    Line 3
    """
// ❌ Cannot strip indentation from multiline string!

Spaces are removed from the end of a line, but not if there is an interpolation or hole

"""
    Line 1  ${"interpolation"}
    Line 2  ${/*hole*/}
    Line 3
    """ == "Line 1  interpolation\nLine 2  \nLine 3"
// ✅

For the purpose of this, space includes:

  • Space character: U+20 ' '
  • Tab character: U+9 '\t'

A line consists of any maximal sequence of characters other than CR (U+A '\n') and LF (U+D '\r').

A line break is any of the following sequences:

  • LF
  • CR
  • CR LF

Strings may contain embedded expressions. When a string contains a ${ followed by an expression, followed by a }, the resulting string value is the concatenation of the content before, content from the expression, and the content after.

"foo ${ "bar" } baz"
== "foo bar baz"
// ✅

An empty interpolation contributes no characters, which means it may be used to embed meta-characters.

"$${}{}" == "\$\{\}"
// ✅

(This mostly comes in handy with tagged strings to give fine-grained control over what the tag receives.)

Empty interpolations can also be used to wrap a long string across multiple lines.

"A very long string ${
  // Breaking this string across multiple lines.
}that runs on and on"
== "A very long string that runs on and on"
// ✅

Empty interpolations also let you include spaces at the end of a line in a multi-quoted string.

"""
Line 1
Line 2 ${}
"""
== "Line 1\nLine 2 "
// ✅

String extends AnyValue, MapKey

properties

String.isEmpty

: Boolean

True iff this string has no code-points.

String.end

: StringIndex

The index at the end of this string, just past the last character if any.

// An empty string's end is the same as its start.
   "".end.compareTo(String.begin) == 0 &&

// Any other string's end is after its start.
"foo".end.compareTo(String.begin) >  0
// ✅

methods

String.constructor

: fn(String): Void

String.get

: fn(String, StringIndex): Int | Bubble

The integer value of the codepoint at the given index or bubbles if there is no such value.

This may produce an unexpected result if index was derived from operations involving a different string. For example, within a language that uses UTF-16 surrogates to represent supplementary code points, using a string index from another string might point into the middle of a code-point in this string causing this method to return the integer value of the trailing surrogate instead of the whole code point.

"foo"[String.begin] == char'f' &&
   (""[String.begin] orelse 0) == 0
// ✅

String.hasIndex

: fn(String, StringIndex): Boolean

True when index points to a character in this string. False if it is out of bounds.

!"".hasIndex(String.begin) && "foo".hasIndex(String.begin)
// ✅

String.next

: fn(String, StringIndex): StringIndex

An index for the next code-point after the one pointed to by index in this string. Returns end if there is no such code-point.

let s = "abc";
let i = s.next(String.begin);

s[i] == char'b'
// ✅

String.prev

: fn(String, StringIndex): StringIndex

An index for the code-point that preceded the one pointed to by index in this string. Returns start if there is no such code-point.

let s = "abc";
let i = s.prev(s.end);

s[i] == char'c'
// ✅

String.hasAtLeast

: fn(String, StringIndex, StringIndex, Int): Boolean

True if there are at least minCount code-points between begin and end (exclusive). Zero if begin is at or past end.

s.hasAtLeast(begin, end, n) is at least as efficient as s.countBetween(begin, end) >= n and better in many situations.

When the native string representation is UTF-8, and the number of octets between begin and end is minCount×4 or greater, hasAtLeast requires no counting.

When the native string representation is UTF-16, and the number of code units between begin and end is minCount×2 or greater, hasAtLeast requires no counting.

At no point is it worse than O(min(n, minCount)) where n is the actual count between begin and end.

let s = "abcdefghijklmnopqrstuvwxyz";
let i = s.next(String.begin); // Points at 'b'
let j = s.prev(s.end);        // Points at 'y'

// Between i and j in s are 26-2, 24 letters.

 s.hasAtLeast(i, j, 4)  && // Fast path for all native string encodings
 s.hasAtLeast(i, j, 24) && // Might require counting
!s.hasAtLeast(i, j, 25) && // Might require counting
!s.hasAtLeast(i, j, 100)   // Fast path too
// ✅

String.countBetween

: fn(String, StringIndex, StringIndex): Int

The count of codepoints between begin and end. On most backends, this is O(n) on the output size. Use String.hasAtLeast instead when trying to test that there are at least a number of codepoints as it can more efficient for many strings.

let s = "abcdefghijklmnopqrstuvwxyz";
let i = s.next(String.begin); // Points at 'b'
let j = s.prev(s.end);        // Points at 'y'

s.countBetween(i, j) == (26 - 2)
// ✅

String.slice

: fn(String, StringIndex, StringIndex): String

The string containing all and only the code points between the given indices in this string in order. If begin > end, returns the empty string.

let s = "tsubo";
let i = s.next(String.begin);
let j = s.prev(s.end);

"sub" == s.slice(i, j)
// ✅

String.split

: fn(String, String): List<String>

Where splitting by "" returns each code point as a separate string.

String.forEach

: fn(String, fn(Int): Void): Void

Invokes body with each code point value in order.

for (let codePoint: Int of "foo-bar") {
  console.log("U+${codePoint.toString(16)}");
}
//!outputs "U+66"
//!outputs "U+6f"
//!outputs "U+6f"
//!outputs "U+2d"
//!outputs "U+62"
//!outputs "U+61"
//!outputs "U+72"
// ✅

String.toFloat64

: fn(String): Float64 | Bubble

Supports numeric JSON format plus Infinity and NaN.

String.toInt

: fn(String, Int | Null): Int | Bubble

Supports integer JSON format, plus any radix 2 through 36.

String.toString

: fn(String): String

statics

String.fromCodePoint

static : fn(Int): String | Bubble

Convert the single code point to a string if it's valid.

String.fromCodePoints

static : fn(Listed<Int>): String | Bubble

Convert the list of code points to a string if they're valid.

String.begin

static : StringIndex

A string index that works for the start of any string.

interface StringIndexOption

A string index, or a similar value that idiomatically represents no string index.

String helpers like indexOf in many languages can return an integer like value which is either a position within the string or an integer value that represents failure to find a match like -1.

StringIndexOption combines those two options in a type-safe way that typically translates to an idiomatic integer.

StringIndexOption extends AnyValue, Equatable

methods

StringIndexOption.compareTo

: fn(StringIndexOption, StringIndexOption): Int

Sorts StringIndex.none before every valid string index.

Comparing string indices derived from different strings produces a behaviour that is stable within the language but which may differ across translation targets with different internal string representations.

class StringIndex

A string index represents a position within a string, at a character or at its end.

String indices translate to integral types (usually int or size_t) but the exact integer value for any notional index may differ from language to language.

Strings are similar to arrays of "characters" in many languages, but the kind of character differs from language to language.

For example a string like "κόσμε𐆊" can be represented in multiple ways.

↓Encoding/
Glyph→
κσμε𐆊
UTF-8cebae1bdb9cf83cebcceb5f090868a
UTF-163ba1f793c33bc3b5d800dd8a
UTF-323ba1f793c33bc3b51018a

Go, and Rust and many C and C++ codebases use UTF-8 which can have between one to four bytes for a code-point. The start of the n-th code-point could be anywhere between the n-th byte and the 4*n-th byte. Figuring out exactly where involves examining n bytes, an O(n) operation.

C# and Java use UTF-16 which represent most code-points using one 2-byte code unit, but may use a pair of "surrogates."

Python3 presents strings as code-points but uses tricks internally to use fewer than 3-bytes per code-point of storage for most strings.

As can be seen above, O(1) random access won't work across languages. The StringIndex type allows an efficient representation of a position within a string. Each StringIndex is either the start of a string or is derived from an operation that takes into account the string content. This lets Temper provide consistent, efficient string operations regardless of the native character size.

StringIndex extends AnyValue, StringIndexOption

statics

StringIndex.none

static : NoStringIndex

The singleton NoStringIndex value

class NoStringIndex

The absence to find a string index.

As noted in StringIndexOption, this is an integer-like type that represents the failure to find a valid string index.

NoStringIndex extends AnyValue, StringIndexOption

interface Console

A Console provides logging and diagnostic capabilities. It might be configured differently depending on the backend and environment. Don't rely on it being attached to any standard stream.

Use the builtin instance console for default logging needs.

Console extends AnyValue

methods

Console.log

: fn(Console, String): Void

Log a message at default informational level to this console.

class List

The List type is a random-access, immutable sequence of values. List literals using [...] syntax represent immutable List instances. For mutable random-access sequences, see ListBuilder.

See also Listed for recommendations on List vs Listed use.

List <T> extends AnyValue, Listed<T>

typeFormals

List.<T>

out extends AnyValue

properties

List.length

: Int

methods

List.constructor

: fn(List<T>): Void

List.get

: fn(List<T>, Int): T | Bubble

List.toList

: fn(List<T>): List<T>

List.toListBuilder

: fn(List<T>): ListBuilder<T>

List.forEach

: fn(List<T>, fn(T): Void): Void

class ListBuilder

The ListBuilder type is a random-access, mutable, growable sequence of values.

ListBuilder <T> extends AnyValue, Listed<T>

typeFormals

ListBuilder.<T>

extends AnyValue

properties

ListBuilder.length

: Int

methods

ListBuilder.constructor

: fn(ListBuilder<T>): Void

ListBuilder.add

: fn(ListBuilder<T>, T, Int | Null): Void | Bubble

Bubbles for at less than 0 or greater than length.

ListBuilder.addAll

: fn(ListBuilder<T>, Listed<T>, Int | Null): Void | Bubble

Bubbles for at less than 0 or greater than length.

ListBuilder.clear

: fn(ListBuilder<T>): Void

Removes all values, retaining internal capacity where possible.

ListBuilder.removeLast

: fn(ListBuilder<T>): T | Bubble

Removes the last value if any, and returns it. Expected to be constant time.

ListBuilder.reverse

: fn(ListBuilder<T>): Void

Reverses this list builder in place.

ListBuilder.set

: fn(ListBuilder<T>, Int, T): Void

ListBuilder.sort

: fn(ListBuilder<T>, fn(T, T): Int): Void

Sorts in place. Uses stable sorting.

ListBuilder.splice

: fn(ListBuilder<T>, Int | Null, Int | Null, Listed<T> | Null): List<T>

Removes removeCount items starting at index, or until the end of the current items. Returns the removed items, and newValues are put in their place. The index is clamped to current bounds.

ListBuilder.toList

: fn(ListBuilder<T>): List<T>

ListBuilder.toListBuilder

: fn(ListBuilder<T>): ListBuilder<T>

interface Listed

The Listed type is an immutable view of a random-access sequence of values. A Listed<String> may contain strings, a Listed<Boolean> may contain booleans, etc.

The backing object might or might not be mutable. During a function call, an argument of type Listed shouldn't be modified concurrently, although it might be modified after the end of the call, so receivers shouldn't keep any references to a Listed parameter for later use, unless accounting for possible changes or according to specific contracts defined for particular use cases.

If you want to keep a reference to a Listed argument, it's often best to call toList on it and retain that value instead.

Usually, don't return type Listed. It's usually best to return type List.

Listed <T> extends AnyValue

typeFormals

Listed.<T>

extends AnyValue

properties

Listed.length

: Int

The length of a listed is the number of items it contains.

let myList: List<String> = ["foo", "bar"];
myList.length == 2
// ✅

Listed.isEmpty

: Boolean

True if this contains no elements.

methods

Listed.get

: fn(Listed<T>, Int): T | Bubble

The ith element, or Bubble if i is outside the range [0, [length]).

let ls = ["a", "b", "c"]; // List of length 3

// The list is zero-indexed.
console.log(ls[0]); //!outputs "a"
console.log(ls[1]); //!outputs "b"

// Out of bounds results in Bubble.
console.log(ls[-1] orelse "too low");  //!outputs "too low"
console.log(ls[ 3] orelse "too high"); //!outputs "too high"
// ✅

Listed.getOr

: fn(Listed<T>, Int, T): T

Listed.slice

: fn(Listed<T>, Int, Int): List<T>

A new list containing all and only elements from [begin] inclusive to just before [end] exclusive.

Listed.toList

: fn(Listed<T>): List<T>

Provides an immutable List with the values of this Listed. If this is already a List, typically just returns this without any copying.

Listed.toListBuilder

: fn(Listed<T>): ListBuilder<T>

Always creates a new object and backing buffer even if this is already a ListBuilder.

Listed.mapDropping

: fn<O>(Listed<T>, fn(T): O | Bubble): List<O>

Maps elements using [transform] and filtering out elements for which [transform] fails.

Listed.map

: fn<O>(Listed<T>, fn(T): O): List<O>

Maps elements using [transform].

Listed.filter

: fn(Listed<T>, fn(T): Boolean): List<T>

Listed.reduce

: fn(Listed<T>, fn(T, T): T): T | Bubble

Reduce to a single value, starting from the first item in this list. Bubble if empty.

Listed.reduceFrom

: fn<O>(Listed<T>, O, fn(O, T): O): O

Reduce to a single value, starting from the given initial value.

Listed.reduceFromIndex

: fn<O>(Listed<T>, O, Int, fn(O, T): O): O

Listed.join

: fn(Listed<T>, String, fn(T): String): String

Listed.sorted

: fn(Listed<T>, fn(T, T): Int): List<T>

Returns a sorted version of this listed. Uses stable sorting.

class Map

A Map is a read-only key-value collection.

Map <K, V> extends AnyValue, Mapped<K, V>

typeFormals

Map.<K>

in extends AnyValue \& MapKey

Map.<V>

out extends AnyValue

methods

Map.constructor

: fn(Map<K, V>, List<Pair<K, V>>): Void

Preserves entry order when accessed from Temper code.

class MapBuilder

A MapBuilder is a read-write key-value collection for building Map objects. Preserves insertion order when accessed from Temper code.

MapBuilder <K, V> extends AnyValue, Mapped<K, V>

typeFormals

MapBuilder.<K>

extends AnyValue \& MapKey

MapBuilder.<V>

extends AnyValue

methods

MapBuilder.constructor

: fn(MapBuilder<K, V>): Void

MapBuilder.clear

: fn(MapBuilder<K, V>): Void

Removes all entries, retaining internal capacity where possible.

MapBuilder.remove

: fn(MapBuilder<K, V>, K): V | Bubble

Removes the given entry if found, and returns its value.

MapBuilder.set

: fn(MapBuilder<K, V>, K, V): Void

interface Mapped

A Mapped is a read-only view to key-value mapped data, such as a Map or MapBuilder.

Mapped <K, V> extends AnyValue

typeFormals

Mapped.<K>

in extends AnyValue \& MapKey

Mapped.<V>

extends AnyValue

properties

Mapped.length

: Int

The number of key value pairs in the Mapped.

methods

Mapped.get

: fn(Mapped<K, V>, K): V | Bubble

Get the value associated with [key], or bubble()s if no key is found.

Mapped.getOr

: fn(Mapped<K, V>, K, V): V

Get the value associated with [key], falling back to [fallback] when no such key exists.

Mapped.has

: fn(Mapped<K, V>, K): Boolean

Checks whether [key] is present in the Mapped.

Mapped.keys

: fn(Mapped<K, V>): Listed<K>

Gets a Listed of all of the keys.

Mapped.values

: fn(Mapped<K, V>): Listed<V>

Gets a Listed of all of the values.

Mapped.toMap

: fn(Mapped<K, V>): Map<K, V>

Converts the Mapped to a Map.

It does not need to make a copy if the Mapped is already a Map because neither can be mutated.

Mapped.toMapBuilder

: fn(Mapped<K, V>): MapBuilder<K, V>

Converts the Mapped to a MapBuilder.

It must make a copy, even when the Mapped is already a MapBuilder so that both can be mutated independently.

Mapped.toList

: fn(Mapped<K, V>): List<Pair<K, V>>

Identical to Mapped.toListBuilder except for the return value being a List.

Constructs a Pair<Mapped.<K>, Mapped.<V>> for each key and value in the Mapped.

Mapped.toListBuilder

: fn(Mapped<K, V>): ListBuilder<Pair<K, V>>

Constructs a Pair<Mapped.<K>, Mapped.<V>> for each key and value in the Mapped.

Mapped.toListWith

: fn<T>(Mapped<K, V>, fn(K, V): T): List<T>

Similar to Mapped.toListBuilderWith.

Calls [func] on each key and value in the Mapped.

Mapped.toListBuilderWith

: fn<T>(Mapped<K, V>, fn(K, V): T): ListBuilder<T>

Returns a ListBuilder<T>.

Calls [func] on each key and value in the Mapped.

Mapped.forEach

: fn(Mapped<K, V>, fn(K, V): Void): Void

Calls the provided binary function func on each key and value for the side effect

This method is more likely to change or be removed before Temper 1.0 than most

class Pair

Pair <K, V> extends AnyValue

typeFormals

Pair.<K>

out extends AnyValue

Pair.<V>

out extends AnyValue

properties

Pair.key

: K

Pair.value

: V

methods

Pair.constructor

: fn(Pair<K, V>, K, V): Void

interface MapKey

Limited at present to String and Int keys but expected to be user-implementable in the future.

MapKey extends AnyValue, Equatable

class Deque

A Deque or double-ended queue allows for breadth-first processing via efficient adds at the end and efficient removal from the front.

let deque = new Deque<String>();
deque.add("a");
deque.add("b");

console.log(deque.removeFirst()); //!outputs "a"

deque.add("c");
deque.add("d");

while (true) {
  console.log(deque.removeFirst()); //!outputs ["b", "c", "d"]
} orelse void;
// ✅

Deque <T> extends AnyValue

typeFormals

Deque.<T>

extends AnyValue

properties

Deque.isEmpty

: Boolean

True if this contains no elements.

methods

Deque.constructor

: fn(Deque<T>): Void

Deque.add

: fn(Deque<T>, T): Void

Add an element at the end.

Deque.removeFirst

: fn(Deque<T>): T | Bubble

Removes and returns the first element.

Function Types

The keyword fn is used to denote function values and types.

Example Means
fn (): Void Type for a function that takes no arguments and returns the void value
fn (Int): Int Type for a function that takes one integer and returns an integer
fn<T> (T): List<T> Type for a generic function with a type parameter <T>
fn (...Int): Boolean Type for a function that takes any number of integers

Source: Types.kt

interface Function

Function extends AnyValue

interface AnyValue

The type for any successful value.

All class and interface types, including builtin types like Int, are sub-types of this type.

The special type Bubble is not.

AnyValue

interface Equatable

Indicates that values can be tested for equality using == and !=. User-defined types can't currently extend Equatable, but we expect to support this in the future.

Equatable extends AnyValue

class Null

The singleton null value represents the absence of a value of another type.

In Temper, null is type checked by using the Null type in conjunction with another type.

String | Null is a valid type for a local variable or function input that might refer to either null or a string value.

Null extends AnyValue, Equatable

class Void

Void

Coroutine Types

interface GeneratorFn

A marker interface for functions that may yield control to their caller even if they do not lexically mention the # yield() builtin pseudo-control-flow instruction.

GeneratorFn extends AnyValue, Function

interface Generator

A multi-step computation that may be scheduled for a step by calling the Generator.next method.

Block lambdas that extend GeneratorFn specify factories for Generators, or if they don't Bubble, for Generators.

Terminology note: Coroutines may be represented as Generators where Generator.next starts or resumes computation and the coroutine pauses itself when done with the step. We avoid the term "coroutine" because many programmers associate it narrowly with a kind of parallelism.

Generator <YIELD> extends AnyValue

typeFormals

Generator.<YIELD>

out extends AnyValue

properties

Generator.done

: Boolean

True if subsequent calls to Generator.next would do no work and return a DoneResult.

That "if" is not an "if and only if" because in the case where a GeneratorFn body yielded just before its return, done may be false but a subsequent call to next would return a DoneResult and reading done after that would get true.

methods

Generator.next

: fn(Generator<YIELD>): GeneratorResult<YIELD> | Bubble

Starts/resumes the computation and runs until it pauses or completes.

If the computation was Generator.done prior to the start of the call or it is done at the end, returns DoneResult. This is the case for a GeneratorFn that returns.

If the computation pauses, returns the value associated with the decision to pause. For a GeneratorFn, this is the value passed to # yield() builtin. If a GeneratorFn bubbles, the call to next propagates it.

Generator.close

: fn(Generator<YIELD>): Void

Should be called once it is realized that Generator.next is not going to be called again to release resources needed by next.

After close exits, Generator.done will be true.

interface SafeGenerator

A variant of Generator that does not bubble when computing the next result.

SafeGenerator <YIELD> extends AnyValue, Generator<YIELD>

typeFormals

SafeGenerator.<YIELD>

out extends AnyValue

methods

SafeGenerator.next

: fn(SafeGenerator<YIELD>): GeneratorResult<YIELD>

Starts/resumes the computation and runs until it pauses or completes.

If the computation was Generator.done prior to the start of the call or it is done at the end, returns DoneResult. This is the case for a GeneratorFn that returns.

If the computation pauses, returns the value associated with the decision to pause. For a GeneratorFn, this is the value passed to # yield() builtin.

interface GeneratorResult

One of a sequence of results from a Generator.

GeneratorResult <YIELD> extends AnyValue

typeFormals

GeneratorResult.<YIELD>

out extends AnyValue

class ValueResult

A result produced from Generator.next when there is a value to produce and Generator.done is false.

ValueResult <YIELD> extends AnyValue, GeneratorResult<YIELD>

typeFormals

ValueResult.<YIELD>

out extends AnyValue

properties

ValueResult.value

: YIELD

methods

ValueResult.constructor

: fn(ValueResult<YIELD>, YIELD): Void

class DoneResult

A result produced from Generator.next when there is no value to produce and Generator.done is true.

DoneResult extends AnyValue, GeneratorResult<Never>

methods

DoneResult.constructor

: fn(DoneResult): Void

Special Types

Never

Never is a type that represents computations that never complete.

It is a sub-type of every type.

Computations that exit abnormally, for example, by jumping via orelse, use Bubble, not Never.

A loop that never exits, perhaps because it is supposed to process jobs until the end of the embedding program, may use type Never.

Bubble

Bubble is a type that represents failure to compute a value. Bubble is a sub-type of Top but is not a sub-type of AnyValue so is not a useful type on function input parameters.

See also orelse.

Top

Top is a super-type of every type, including AnyValue and Bubble.

Invalid

A type representing a failure to compute a type.

class Problem

A Problem is an internal abstraction that represents code that cannot be compiled.

Representing problems in a partially-processed library allows running some tests, giving developers more flexibility in how they investigate what they need to do to get their code production-ready.

Problem extends AnyValue

Type Relationships

graph TD
Top --> AnyValue
Top --> Void
Top --> Bubble
AnyValue --> Union["A | B"]
subgraph nominals [Nominals]
  direction LR
  A
  B
end
Union --> A
Union --> B
A --> Intersection["A & B"]
B --> Intersection
Intersection --> Never
Void --> Never
Bubble --> Never
Invalid

Arrows point from super-types to sub-types.

There is a Top type at the top which is the super-type of all types.

At the bottom is Never which is the bottom type, a sub-type of all types, and an appropriate type for computations that never complete like

while (true) {}
// ⏸️

The Invalid type, off to the right, is a type marker for constructs that the compiler cannot make sense of. It is outside the type hierarchy; not a subtype of any other nor vice versa.

Top branches into AnyValue, Void, and Bubble. This represents the three ways a computation can complete, either by

  • producing an actual value (it produces a sub-type of AnyValue),
  • finishing normally but without a usable value, or
  • bubbling up the call stack until replaced with a value by orelse.

Below any value we have A | B, a union type. Values of type A | B can be of type A or1 of type B. A union type is a super-type of each of its members.

Below the Nominal Type box we have A & B, an intersection type. An intersection is a sub-type of each of its elements.

class C extends A, B {}
// ⏸️

In that, C is a declared sub-type of both A and B, so it's a sub-type of A & B.

(Union and intersection types are actually more general. A | Bubble is an expressible type as is (A | Null) & D, so this diagram does not imply that all union/intersection types fit neatly in a region on one side of nominal types. Void has contraints here, however. Void | Bubble makes sense, but A | Void doesn't.)

In the middle are NominalTypes. These are types declared with a name and parameters. NominalTypes include all these:

Boolean         // A builtin type
List<T | Null>  // A parameterized type
C               // A nominal type, assuming a definition like `class C` is in scope.
fn (Int): Int   // A type for functions that take an Int and return an Int
// ⏸️

Type Tags

Type tags are what determine whether a narrowing cast1 would be safe at runtime.

Static types have more nuance than type tags.

Static Type Type Tag
C C
Boolean Boolean
List<Int> List
fn(): Void Function

As you can see, all function values have the same type tag, Function, and generic class's corresponding type tag has the type parameters erased.

Type Compatibility

Overloaded functions and methods are filtered based on type and argument list compatibility.

For example, + has variants that operate on Int and Float64.

During type checking, the Temper compiler filters out unusable variants. So if two arguments whose static type is Int are passed in, we can filter out the Float64 variants because we know that no values whose Type Tags is Float64 will ever be passed in.

An input with a static type, I is compatible with a function argument with static type, A when any of the following are true:

  1. I is a function type and Function is compatible with A
  2. A is a function type and I is compatible with Function
  3. I is a type parameter and its bound is compatible with A
  4. A is a type parameter and I is compatible with A*'s bound
  5. I and A are nominal types and A with all its type parameters replaced with wildcards is a sub-type of A with all its type parameters with wildcards.
  6. I is a union type and any member is compatible with A
  7. I is an intersection type and all members are compatible with A
  8. A is a union type and I is compatible with all members of A
  9. A is an intersection type and I is compatible with any member of A

  1. A narrowing cast is from a super-type to a sub-type. For example, from AnyValue to class C