Skip to content

Left‐Hand Sides

Andrew Johnson edited this page Mar 22, 2025 · 20 revisions

Most programming languages focus primarily on right-hand sides. Bias is often given towards towards the program logic that does something. By contrast, left-hand sides are normally very bland. Most languages only permit variable names and maybe a type.

In LSTS, left-hand sides are more diverse.

Left-Hand Sides Are Just as Important as Right-Hand Sides

Right-hand sides do things. Left-hand sides condition things.

In logic, conditions are just as important as expressions. Programming is about determining not just how to do something, but also about determining why to do something. Left-hand sides help us define conditional logic more naturally, so in LSTS the LHS are given just as much feature depth as the RHS.

A Variety of Features

The basic building block of a lhs is the binding. A binding is an optional bind mode and a variable identifier name to bind to. The default bind mode is set. If the binding name is _ underscore, then the value will remain unbound.

let a = 5;
set a = 3;
a = 2;

Tuples are a simple extension for a common data structure.

(let x, let y) = (1, 2);

Lists, vectors, and other sequential types are also common data structure.

[let x.. let y.. let remainder] = [1, 2, 3, 4, 5];

Objects are important too. Objects can have case tags, such as LeftCase. If no case tag is necessary, then an _ underscore can be provided instead.

Tuple { let x, let y } = (1, 2);
# this is equivalent to the earlier tuple example

Objects can be destructured by their field name also.

Tuple{ let x=first } => (1, 2);

Values can also appear on left-hand sides. A value that appears on the left-hand side will make the binding conditional. Conditional left-hand sides can fail.

match 1 {
   1 => print("One");
   2 => print("Two");
}

Value destructuring is potentially complicated. Some types can be partially matched and partially bound. One example of this is matching a prefix or suffix of a string.

match "abc" {
   "a".. remainder => print("'a' + \{remainder}");
}

Variable data can be treated as a value with the ~ tilde operator.

match x {
   ~y => print("X equals Y");
}

Pointers are sometimes automatically dereferenced, but the original pointer value can be saved if a raw specifier is added to the binding.

(let raw p, let y) = (&x, 2);

Value destructuring and bindings can sometimes be intermixed. Object field destructuring is a common example of this.

match (1, 2) {
   Tuple{ let x=first:1, second:2 } => print("Case 1");
}

Left-Hand Sides Are Values Too

One of the more interesting recent features in LSTS is dynamic left-hand sides. An LHS Value is a function with the type signature lval -> Maybe<rval>. This means that any LHS can be applied to an lval and might return an rval.

However, there is also another function that needs to exist for LHS values which is subsumption. For an LHS to be a valid value there must also be a LHS <: LHS defined. This is used to order each LHS such that the most specific case is attempted first.

Together these features mean that match statements can be constructed dynamically on-the-fly and used in the same program.

let dynamic-cases = dynamic-match(type(type-of-lval), type(type-of-rval));

# only default case right now
match value dynamic-cases; 

dynamic-cases.insert(case "aa" => print("A"));
dynamic-cases.insert(case "a"..rest => print("A \{rest}"));

# now two cases are checked
match value dynamic-cases; 
Clone this wiki locally