Types in Tenet
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.
Int,StrandBoolare built-in types(field: Type, ...)for recordstag ~ Type | ...for unions\[T]is shorthand forList\[T]- There isn’t a shorthand for
Set\[T] \[K: V]is shorthand forMap\[K, V]
Value Types
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 practical implication of Tenet’s values is that any value can be placed in any variable, or stored within a record or collection. They can always be consistently serialized and deserialized.
Atomic Types
The current types are fairly basic; we’ll add Float and put some bounds on Int.
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
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 =
ok ~ Str
| #err
type Optional = just ~ Int | #nothing
Unions are order-independent within Tenet semantics.
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 \[].
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 \[:].
type Color = #red | #blue
let numbers : \[Int] = \[1, 2, 3]
let colors : Set\[Color] = set\[#red, #blue]
let scores : \[Str: Int] = \["alice": 95, "bob": 87]
What’s missing
Some future features (objects, capabilities, stators, blocks) are missing, but we’re resolved to discuss them in a roadmap.
There’s no null
Tenet has no null or undefined special values. It’s not that we don’t like null, most languages have worked out how to tame it by making it plainly apparent.
The problem with null is it doesn’t go far enough: there’s only one null and we don’t know what it means. In Tenet, the user can plainly state that data is
#missing, #corrupted, #blank, #lost, #blithering or whatever meaningful label applies.
Function types
Again, function types and functions as values are great. They raise some thorny questions:
- can you capture a mutable name?
- what type does a function value have?
- what does equality mean?
- can you serialize them?
- can you break out of
forEachfrom within a lambda?
We hold that a function fundamentally isn’t a value. Tenet will offer blocks that can be passed to functions and act as though they are inlined, and functors that are callable objects.
Special Types
These aren’t missing, but they’re not immediately user accessible.
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.