Built-in Types Overview¶
We've now built and tested libraries in Temper. We've also already seen a lot of the built-in types and features. But to round out ourselves, let's look more at what comes built into Temper. If you want all the details, though, the full references on built-in functions and constants and also types are elsewhere. Here, we'll just be doing some overview.
We also diverge here from our example geometric shapes library, though it served us well for context to get started.
For the types below, some of them can be found as class or interface definitions in the file Implicits.temper in the Temper source code. The interpreter uses much of the code here, but classes here are also separately redefined for each backend.
Special types¶
Temper has a number of special types, some of which you'll see more than others. See also additional discussion and a diagram of some of these types:
Top
- The supertype of all other types, equivalent toAnyValue | Bubble
.Bubble
- Unusable as an ordinary value. Handled viaorelse
. It's used in conjunction with other types as function return types, such asInt | Bubble
.Void
- Indicates that a function return value must be ignored, allowing for better compatibility with type expectations of some backends.AnyValue
- The supertype of all usable values. As for any type that isn't a sealed interface, you can't downcast fromAnyValue
. This allows type safety while still using appropriate backend values.Null
- Supports data interop with external systems. Can be used along with other types, such asString | Null
. The only value forNull
isnull
. For functions that might fail, usuallyBubble
is a better choice thanNull
. And for functions whose result should be ignored, useVoid
.Never
- The subtype of all other types, representing computations that never complete, such as infinite loops. Computations that exit abnormally useBubble
, notNever
.Invalid
- A type for when a type can't be computed.
As suggested above, sometimes we can distinguish types at runtime, but this
often depends on the specific backend. That's why downcasting is so constrained
in Temper. But in addition to sealed interface subtypes, you can also downcast
to check against null
:
$ let maybeMultiply(x: Float64 | Null, y: Float64 | Null): Float64 | Null {
// The `.as<T>()` macro has return type `T | Bubble`, so use `orelse`.
(x.as<Float64>() * y.as<Float64>()) orelse null
}
interactive#0: void
$ maybeMultiply(1.5, 2.0)
interactive#1: 3.0
$ maybeMultiply(null, 2.0)
interactive#2: null
$ maybeMultiply(1.5, null)
interactive#3: null
$ maybeMultiply(null, null)
interactive#4: null
Function types¶
Function types are represented specially with the keyword fn
. A higher order
function reference supports positional arguments only. Some examples include:
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 |
Numeric types¶
As we saw earlier, Temper has the following numeric types built in:
Float64
- 64-bit IEEE 754 floating point number. These are widely supported across modern languages and platforms. Math operations might not be guaranteed to provide 100% consistent results across all backends, however.Int
- Integer type whose size is specific to each backend. We expect to provide checked arithmetic by default in the future so you getBubble
whenever correct integer math would fail, such as by hitting bit limits (see issue#4).
These two types don't interop directly. For example:
$ 1 + 2.5
1: 1 + 2.5
┗━━━━━┛
[interactive#0:1+0-7]@G: No applicable variants in (fn (Int, Int): Int & fn (Int): Int & fn (Float64, Float64): Float64 & fn (Float64): Float64) for inputs (Int, Float64)
interactive#0: fail
$ 1.toFloat64() + 2.5
interactive#1: 3.5
$ 1 + (2.5).toInt()
interactive#2: 3
$ 1 + Infinity.toInt()
interactive#3: fail
$ 1 + NaN.toInt()
interactive#4: fail
The methods toFloat64
and toInt
might fail with Bubble
depending on the
size of the values (or NaN) and the backend's Int
size. Variants
toFloat64Unsafe
and toIntUnsafe
always return a value, but they have
backend-dependent behavior outside safe values. The unsafe methods should be
used only for small values and/or with fuzz testing across backends.
We also plan to provide additional common mathematical operations, including rounding (see issue#27).
Boolean¶
For logical operations, Temper also has a distinct type:
Boolean
- Eithertrue
orfalse
.
Common Boolean operations exist, such as &&
, ||
, !
, and so on. You can't
use numbers as Booleans:
$ if (1) { console.log("hi"); }
1: if (1) { console.log("hi"
⇧
[interactive#1:1+4-5]@G: Expected value of type Boolean not Int
1: if (1) { console.log("hi"
⇧
[interactive#1:1+4-5]@R: Expected value of type Boolean not Int
interactive#1: fail
$ if (true) { console.log("hi"); }
hi
interactive#2: void
The error repeats in the bad usage example because it happens at multiple
stages. The first report is from static type checking. When compiler errors
(including static type errors) happen in Temper, you can still carry through to
backends or the interpreter. This behavior assists testing portions of code that
aren't in error. But then the behavior is backend-dependent. In this case, the
interpreter sees the Int
value and fails with the same error message as the
type checker.
You shouldn't publish libraries with Temper compiler errors.
String¶
Temper has the following types for representing text:
String
- Immutable text data.StringIndex
- An index into a string that allows efficient left-to-right traversal.
Here are some examples:
$ let what = "libraries";
interactive#0: void
$ let message = "I ❤ ${what}";
interactive#1: void
$ message.split(" ")
interactive#2: ["I","❤","libraries"]
$ message.isEmpty
interactive#3: false
$ message.length
1: message.length
┗━━━━┛
[interactive#4:1+8-14]@G: Expected function type, but got Invalid
1: message.length
┗━━━━━━━━━━━━┛
[interactive#4:1+0-14]@G: No member length in String__27
interactive#4: fail
$ message.countBetween(String.begin, message.end)
interactive#6: 13
$ message[message.next(message.next(String.begin))]
interactive#7: 10084
Different backends have different internal string representations, so simple
random access into string data can be inefficient.
StringIndex
is an opaque type that connects to an integer type on each
backend, but the exact integer differs depending on the "native string encoding."
- In languages like Rust and C++, the integer is a byte offset. Supplemental
code-points use four bytes, so
myString.next(i)
might return a number up to 4 greater thani
. - In languages like C#, Java, and JavaScript, the integer is a UTF-16 code unit
offet, and a supplemental code-point uses 2 UTF-16 surrogates, so
myString.next(i)
might return a number up to 2 greater thani
. - Python3 presents its strings as like an array of code-point, so
myString.next(i)
is 1 greater thani
, unlessi
is already at the end of the string.
Temper also has raw string syntax to treat backslashes as text data via the raw
string tag, as well as multiline string syntax:
$ raw"\d+\.\d+"
interactive#0: "\\d+\\.\\d+"
$ """
- An outline
- With indentation
- Final point
"""
interactive#1: "- An outline\n - With indentation\n- Final point"
Note how the minimum indentation level denotes the beginning of each line of text. You can also use more than three quote to control the number of quotes needed to finish the string:
$ """"
"""
""""
interactive#2: "\"\"\""
But you can't do single-line multi-quoted strings. For example, """abc"""
is
an error. And there are reasons for that. The current and future feature set of
Temper string templates goes much deeper than this (see issue#18), but let's
move on for now.
Temper has character syntax using a string tagged with char
.
Character values are simple integer code-point values.
$ char'👪'
interactive#3: 128106
List types¶
A list is a sequential, random access, sized collection, and built-in types include three list types:
Listed
- An interface for read-only access to data. Extended by bothList
andListBuilder
. In the future, third party code might also be able to extend it (see issue#28), but that doesn't work correctly today.List
- A class for immutable listed data.ListBuilder
- A class for mutable building of listed data.
The classes List
and ListBuilder
, just as for other built-in types, are
represented by core types in each backend to the extent possible, such as by
IReadOnlyList
or IList
in C#, List
in Java, Array
in JS, or tuple
or
list
in Python.
Here are some examples:
$ let nums = [10, 20, 30]; // inferred type: List<Int>
interactive#0: void
$ nums.length
interactive#1: 3
$ nums[1]
interactive#2: 20
$ nums.add(40);
1: nums.add(40);
┗━┛
[interactive#3:1+5-8]@G: Expected function type, but got Invalid
1: nums.add(40);
┗━━━━━━━━━━┛
[interactive#3:1+0-12]@G: No member add in List__10 | Listed__44
interactive#3: fail
$ let nums2 = do {
let builder = new ListBuilder<Int>();
builder.addAll(nums);
builder.add(40);
builder.toList()
}
interactive#4: void
$ nums2
interactive#5: [10,20,30,40]
$ nums2.map { (n): Int;; n / 10 }
interactive#6: [1,2,3,4]
For many common cases, functions should accept Listed
views and return
immutable List
data:
let calculateSomething<T>(items: Listed<T>): List<T> {
// ... meaningful calculations here ...
}
As mentioned earlier, Temper allowes for mutable data but prefers immutable.
Map types¶
Temper's map types are like its list types, but they associate keys more arbitrarily with values:
Mapped
- An interface for read-only access to data, extended by bothMap
andMapBuilder
.Map
- A class for immutable listed data.MapBuilder
- A class for mutable building of listed data.
As for list types, Map
and MapBuilder
use standard backend types where
available, such as IReadOnlyDictionary
or IDictionary
in C#, Map
in Java
or JS, or dict
in Python.
$ let messages = new Map([
new Pair(200, "OK"),
new Pair(404, "Not Found"),
new Pair(500, "Internal Server Error"),
]);
interactive#0: void
$ messages[200]
interactive#1: "OK"
$ messages.toList().map { (entry): Int;; entry.key }
interactive#2: [200,404,500]
$ let neighbors = do {
let builder = new MapBuilder<String, List<String>>();
builder["Honduras"] = ["El Salvador", "Guatemala", "Nicaragua"];
builder["Nicaragua"] = ["Costa Rica", "Honduras"];
builder.toMap()
};
interactive#3: void
$ neighbors["Nicaragua"]
interactive#4: ["Costa Rica","Honduras"]
For now, you can use only Int
or String
keys. We hope to expand support to
user-defined key types in the future (see issue#29).
Other types¶
Temper has additional built-in types, but above discussion highlights some of the
most important ones. Also, some types currently in Implicits.temper,
such as Deque<T>
and DenseBitVector
might move to support libraries in the
future (see issue#30).
There's also currently a standard library called "std"
with modules
"std/regex"
and "std/testing"
. These also are likely to see some
reorgnization before 1.0 (see issue#26). We already saw a bit of testing
earlier. And we'll take a look at regular expressions soon, but first let's
understand how Temper handles memory management.
Links¶
- NEXT: Memory Management
- PREVIOUS: Modules & Libraries
- Reference: Built-ins, Types