Expressions in Tenet

Tenet expressions are mostly familiar but with a few distinctive features, particularly around deconstruction and escaping.

Basic Operators

Tenet supports the usual arithmetic, comparison, and logical operators:

Compound operaors

Deconstruction Operators

Tenet provides two main parametric deconstruction operators that extract parts of composite values.

Record Field Access .

The dot operator . extracts a field from a record. The field name is a compile-time parameter.

let p = (x: 3, y: 4)
p.x == 3
p.y == 4

Union Variant Unpacking !

The bang operator ! unpacks a tagged value from a union. The tag is a compile-time parameter.

If the value has the expected tag, the variant is returned. If the tag doesn’t match, the entire value escapes.

let result = some-func()       // returns ok~42 or #miss
let value  = result ! ok       // succeeds → 42; fails → escapes

This is directly analogous to record access:

Expression-level Pattern Matching ? and ?!

The ? operator is the expression-level counterpart to the when statement. It performs pattern matching and is always exhaustive — all cases must be covered, and all branches must produce a value with a common supertype.

let desc = some-union ? {
    ok ~ v      -> "success: " ++ v
    #miss       -> "not found"
    #error ~ e  -> "failed with " ++ e
}

The variant ?! (question-bang) is similar but does not have to be exhaustive if the unmatched cases are allowed to escape according to the surrounding context.

let value = risky-result ?! {
    ok ~ v -> v
    #miss  -> 0
    // other tags are allowed to escape
}

? and ?! are useful when you want pattern matching inside a larger expression without introducing a full when block.

Function calls

Functions are invoked with named arguments, but the early arguments can be passed positionally in the same order as in the function definition:

// All identical
process(data: input, mode: #strict);
process(input, mode: #strict);
process(input, #strict);

Block arguments are constructed at the call site:

let processed = with-handler(input) { line =>
    reply " | " ++ line ++ " | "
}

Blocks can also be included inside the parens like a regular argument:

let processed = with-handler(input, { line =>
    reply " | " ++ line ++ " | "
})

// Full arguments
let processed = with-handler(input: input) handle: { line =>
    " | " ++ line ++ " | "
}

// Passing blocks inside the parens
let processed = with-handler(input, handle: { line =>
    " | " ++ line ++ " | "
}) 

Future / Deferred