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:

What’s uncommon

Floor division instead of truncating. A common use case for modulo is to implement a hashtable, and users are surprised that the sign of the modulo is the sign of the numerator.

Keyword relational operators. We need the angle brackets; we swear it’s for a good cause.

Keyword logical operators. Keywords fit nicely with if and while.

Concatenation isn’t plus. It helps catch errors.

Compound assignment operators

All the arithmetic operators have compound assignment, but ++= is particularly useful, and and= and or= are short-circuiting.

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.

do {
    let p = (x: 3, y: 4);
    print-line("x is " ++ int-to-str(p.x));
    print-line("y is " ++ int-to-str(p.y));
}

Union Variant Unpacking !

The unpacking 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.

do {
    let result: ok ~ Str | #miss = ok ~ "apple";
    let fruit = result ! ok;
    print-line("Fruit is " ++ fruit);
}

This is directly analogous to record access:

Expression-level Pattern Matching ? and ?!

The when operators ? and ?! perform pattern matching inside a larger expression without introducing a full when block. They have very similar syntax to when, except that, being a statement, when can do nothing and move on to the next statement.

The ? operator performs pattern matching and is always exhaustive — all cases must be covered, and all branches must produce a value with a common supertype.

do { 
    let desc = error ~ "Problem" ? {
        ok ~ let v    -> "success: " ++ v;
        #miss         -> "not found";
        error ~ let e -> "failed with " ++ e
    };
    print-line(desc); 
}

The escaping when operator ?! is similar but does not have to be exhaustive: any unmatched cases are allowed to escape.

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

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:

fun process(data: Str, mode: #strict | #loose) {
    print-line(
      "Got data " ++ data ++ "; mode: " ++
      (mode ? { #strict -> "strict"; #loose -> "loose" })
    );
}

do {
  process(data: "input", mode: #strict);
  process("input", mode: #strict);
  process("input", #strict);
}