Type definition

A type definition in Tenet assigns a type expression to a type name. A type definition begins with the type keyword, followed by a name, optionally with a parameter list, and a type expression.

User-defined types are treated as templates for concrete types, wherein a template with no parameters is effectively an alias.

A user-defined type is defined by the type top-level statement. For example:

type My_Int := Int
type Pair[First, Second] := (first: First, second: Second)

Cyclical or recursive types are illegal because the unbound recursion or iteration necessary to manipulate them is prohibited.

type ConsList[T] := #nil | cons ~ (head: T, tail: ConsList[T])  ;; Error: cyclic reference detected.

Type application

Type application converts a generic type into a concrete type by specifying all parameters. There is no facility for partial type application. While builtin types like List and Map are allowed to have positional parameters, user-defined types are not.

type List_of_Ints := List[Int]
type String_Int_Pair := Pair[First: Str, Second: Int]

Generic functions

Functions may also be generic, though a generic function is not exportable. Type arguments are presented as ordinary arguments.

At present, type arguments are only applied to the function signature.

func generic_func(T, num: Int, val: T) -> #nope | yep ~ T {
    if num < 5 {
        return yep ~ val
    } else {
        return #nope

func doubles_list(T, list: [T]) -> [T] {
    return list ++ list

;; Specify the type parameters to construct an exportable function.
let concrete_func := generic_func(T: Str, ...)

In general, a generic function may be used like a regular function and type inference will determine the appropriate parameters.

However, if inference can’t determine the correct parameter, the parameterization must be specified:

let result := generic_func(3, "okay")        ;; Infers from argument.
let non_exportable := doubles_list([])       ;; Infers, but can't be exported.
let empty1 := doubles_list(T: Int, [])
let doubles_int := doubles_list(T: Int, ...) ;; Now it's an exportable function.
let empty2 := doubles_int([])                ;; Valid
let empty3 : [Int] := doubles_list([])       ;; Also valid