Types in Tenet
Tenet doesn’t try to apply a single paradigm to all “things” that can be modeled by the language. For instance, in object-oriented languages, it’s common to claim that “everything is an object.” Instead, Tenet has a few broad categories of “things” that serve various purposes.
In Tenet, a value is defined by some properties:
- has a clear structure and meaning
- has a well-defined notion of what it means to be equal
- can be fully encoded and decoded
The most important kind of “value” that these rules proscribe is a function value. The problem with function values is that the equality of two function values is a non-trivial attribute and, per Rice’s theorem, determining this in general is equivalent to solving the halting problem.
And the practical implementation is that any value can be placed in any variable, or stored within a record or collection.
Tenet has no null or undefined special values. Rather than shoe-horning various kinds of
missing values into a generic null, it’s preferable to plainly state that the data is
#missing, #corrupted, #blank, #lost or whatever meaningful label applies.
Another category is a block that:
- can’t be stored in a regular variable
- is defined and used at a function call site
- has no notion of equality
- requires extra work to encode or decode
These look like function values in other languages, but can’t be assigned to variables. That is,
they aren’t first-class function values. The advantage is that blocks can guarantee that control
flow in the block must always work: they are always used while the function is running, so it’s
always meaningful to break out of a loop.
And we’d also dispute how “first-class” function values actually are when there’s no good definition of equality for them.
Three final categories are:
- stator
- The guts of an object: a stator defines some local state, it can respond to methods and update its state, but it's not allocated on the heap. A stator is also a hierarchical finite state machine.
- capability
- A mechanism to control side-effects, capabilities can be passed explicitly or implicitly to mark which functions can do things like perform IO or spawn threads. These are very similar to the effects algebra, but capabilities work a bit more smoothly with blocks.
- object
- Objects tie stators and capabilities and values together. As the description of stator suggests, objects are simply stators allocated on the heap. These are managed by an object capability, e.g. in a GC implementation, it may be the global GC. The object capability can also grant other capabilities to objects, allowing passing capabilities around while keeping track of their lifetime. While objects are allocated on the heap, they also have a value semantic as a unique ID of the object. The value semantic can't be overridden: equality of objects is always based on identity.
Value Types
Atomic Types
Int— unbounded signed integers.Str— a finite sequences of Unicode code points. No normalization is performed. A string declared mutable may be used like aStringBuilderin other languages.Bool— the usualtrueandfalsevalues
Container Types
Lists have a type [T], or List[T] in full. They are a homogeneous finite sequence of
values of type T. A list value literal is written as [a, b, c, d], and the empty list is [].
Sets have a type Set[T]. They are a homogeneous finite set of values of type T. A set
value literal is written as set\[a, b, c, d], and the empty set is set\[].
Note: the backslash is used to indicate the word
setis not a regular variable name.
Maps have a type [K: V], or Map[K: V] in full. They are a finite mapping from keys of
type K to values of type V. A map literal is written as [a: b, c: d], and the empty map
is [:].
let numbers : [Int] = [1, 2, 3]
let colors : Set[Color] = set\[#red, #blue]
let scores : [Str: Int] = ["alice": 95, "bob": 87]
Algebraic Types
Records
A record is a fixed collection of named fields with potentially different types. Record types are closed — two records are compatible only if they have exactly the same field names and compatible field types.
type Point = (x: Int, y: Int)
let p = (x: 3, y: 4)
Tagged Unions
A union is a discriminated sum type. Every member is a tagged value tag ~ variant and an
expression #tag is shorthand for tag ~ (), meaning the variant is the unit record value.
type Result(T) =
ok ~ T
| #err
type Optional(T) = just ~ T | #nothing
Unions are order-independent within Tenet semantics.
Non-value Types
Block Argument Types
We can declare a block argument type in a type statement:
type Callback = { when: Time, msg: Str => Result(Str) }
These can’t be used in any value type expression. In particular, you can’t have a list of callbacks.
More commonly, though, block argument types can be directly shown as part of the function signature.
fun foreach-users(users: [User]) func: { user: User, number: Int => () } {
let mut num = 0;
for user in users {
func(user: user, number: (num += 1));
}
}
do {
foreach-users([user-1, user-2, user-3]) { user, number =>
print-line("User number \(number) has name \(user.name).")
}
}
Block types describe the argument names and types and the return type. Blocks aren’t normal values; they can’t be assigned to variables, but can only be constructed when passed to a function invocation.
Type Expressions
Type expressions appear in type definitions, function signatures, and annotations. In general, Tenet tries to make types homoiconic, that is, types usually look like the values they represent.
[T]is shorthand forList[T]- There isn’t a shorthand for
Set[T] [K: V]is shorthand forMap[K, V](field: Type, ...)for recordstag ~ Type | ...for unions
Special Types
Bottom (⊥)
The empty type, used internally during type inference.
- The empty list
[]has typeList[⊥]and is a member of everyList[T]. - The empty set
set\[]has typeSet[⊥]and is a subset of everySet[T]. - The empty map
[:]has type[⊥: ⊥]and is a member of every[K: V].
Top (⊤)
This is used internally. In inference, we often need to find a common supertype of multiple types, and if this isn’t available, Top is a marker to indicate this.
Future / Deferred
The following are not part of the current version:
- Restricted integer/string types with bounds
- Views on maps
- User-defined composite collections
- Relational / indexed collections
- Tree / graph / path types
- Advanced mutation and inner lensing