Strings, pipes & defaults

Implemented

Template strings with ${...}, the pipe operator ->, and ?? for options. Each one desugars before hashing.

Interpolation

A template string is written with backticks. ${...} holds any expression; the checker picks the conversion from the expression’s type (string as is, int, float, and bool as "true" or "false"). It desugars to the ++ chain you would have written by hand, so the two hash identically.

let greet = (name: string, n: int): string => `Hello, ${name}! You are number ${n}.`

let flag = (b: bool): string => `it is ${b}`
Verified: greet('Ada', 3) is 'Hello, Ada! You are number 3.'.

Escapes are \`, \$, \\, \n, \r, \t and \"; a $ not followed by { is plain text and a template may span lines. Records, variants, lists and tuples are not converted for you (convert them yourself), and a parameter with no annotation has no type to pick a conversion from, so annotate it. Int.toText(n) is also callable directly.

The pipe

a->f(b) is f(a, b): the left value goes in as the first argument. A single _ among the arguments says where it goes instead. It is a pure rewrite, so the pipe and the direct call share one hash.

let double = (n: int): int => n * 2
let sub = (a: int, b: int): int => a - b

let a = 4->double->double
let b = 10->sub(3, _)
let c = [1, 2, 3]->List.map(double)
Verified: a is 16 and b is sub(3, 10), which is -7.

Not built: piping directly into a lambda literal.

Defaults with ??

d["k"] ?? 0 gives the value, or the default when the key (or list element) is missing. It is a switch on the option, the default is only evaluated when needed, and it binds between a comparison and +, right-associatively. See Lists, dictionaries & options.