Choice

The famous slogan of sum types is: Make illegal states unrepresentable!

Choice types — the dual of sum types, also known as codata — deserve an equally potent slogan:

Make illegal operations unperformable!

Choice types are somewhat related to interfaces, like in Go, or Java, but I encourage you to approach them with a fresh mind. The differences are important enough to consider choice types their own thing.

A choice type is defined by a finite number of branches, each with a different name and a result.

Values of a choice type are objects that can (and must) be destructed using one of the available branches, to obtain its result.

type ChooseStringOrNumber = choice {
  .string => String,
  .number => Int,
}

A choice type is spelled with the keyword choice, followed by curly braces enclosing a comma-separated list of branches.

Each branch has a lower-case name prefixed by a period, followed by =>, and a single obligatory result type.

choice {
  .branch1 => Result1,
  .branch2 => Result2,
  .branch3 => Result3,
}

If the result is a function, we can use syntax sugar, and move the argument to the left side of the arrow, inside round parentheses:

type CancellableFunction<a, b> = choice {
  .cancel => !,
  //.apply => [a] b,
  .apply(a) => b,
}

Like functions, choice types are linear. A value of a choice type may not be dropped, or copied. It must be destructed exactly once, using one of its branches.

Choice types are frequently used together with iterative types to define objects that can be acted upon repeatedly. For example, the built-in Console type obtained as a handle to print to the standard output is an iterative choice:

type Console = iterative choice {
  .close => !,
  .print(String) => self,
}

Then it can be used to print multiple lines in order:

def Main = Console.Open
  .print("First line.")
  .print("Second line.")
  .print("Third line.")
  .close

Construction

Values of choice types are constructed using standalone case expressions.

def Example: ChooseStringOrNumber = case {
  .string => "Hello!",
  .number => 42,
}

Each branch inside the curly braces follows the same syntax as the branches in the corresponding type, except with types replaced by their values.

def IntToString: CancellableFunction<Int, String> = case {
  .cancel => !,
  //.apply => [s] Int.ToString(s),
  .apply(s) => Int.ToString(s),
}

Unlike patterns in .case branches of either types, branches in case expressions of choice types don’t have a payload to bind: they produce a result. However, we can still bind function arguments on the left side of the arrow.

Destruction

Choices are destructed by selecting a branch, transforming it into the corresponding result. We do it by applying .branch after a value of a choice type.

def Number = Example.number  // = 42

Above, we defined the type CancellableFunction<a, b>, and a value of that type: IntToString. Bare functions are linear, so we must call them, but the cancellable function gives us a choice of either calling it, or not.

We can use this to define a map function for optional values:

type Option<a> = either {
  .none!,
  .some a,
}

dec MapOption :
  [type a, b]
  [Option<a>]
  [CancellableFunction<a, b>]
  Option<b>

def MapOption = [type a, b] [option, func] option.case {
  .none! => let ! = func.cancel in .none!,
//                  \_________/
  .some x => let y = func.apply(x) in .some y,
//                   \________/
}

def Result = MapOption(type Int, String)(.some 42, IntToString)  // = .some "42"

This example also shows that in Par, you don’t have to be shy about writing your types on multiple lines. The syntax is designed for that.