Version 0.3.0#

Generators allow for complex iteration and asynchrony and yield is the new pause#

Coroutines, functions that may yield control back to their caller, are useful for:

In both cases, a scheduler is responsible for resuming execution of a function that yielded control.

Temper is using the terms generator and yield which have familiar meanings to users of programming languages like C#, JavaScript, and Python. Instead of using terminology like coroutine which many programmers associate with a specific approach to parallelism.

Now, yield is a control-flow operator, like return that yields control back to the caller. Like return, it may be followed by an expression which specifies a value to provide to the caller. Unlike return, yield does not tear down the function execution. Instead, it suspends execution until a scheduler resumes it.

interface Generator<YIELDED_TYPE> represents a suspended computation. It's .next method allows starting/resuming the computation.

yield may only be used in a block lambda that extends GeneratorFn.

The function called with the block lambda receives a factory for Generators. It can call that factory with the arguments listed in the block lambda's signature, to get an instance of interface Generator which, when generator.next is called, resumes after the last yield instruction.

Regex literals#

Temper now supports regex literals using either of the following syntax forms:

This is implemented using the temper-regex-parser library, which is itself written in Temper, then compiled to Java and publish to Maven Central. There is no plan to fully self-host the Temper compiler in Temper, but this still is an example of partial self-hosting and demonstrates the promise of Temper for writing and sharing libraries.

Regex literals require import of "std/regex" (possibly renamed in the future) and produce regexes in compiled form. To simplify future usage, we've renamed CompiledRegex to Regex and what was Regex to RegexNode.

Reassignments of const declarations are now reported as errors#

let declarations are only allowed to be assigned once. var declarations are allowed to be reassigned.

That former restriction was not fully enforced but now it is.

let f(n: Int): String {
  let i = n;   // Temper does not require `const` keyword.
  ++i;         // Reassignment
  return i.toString();
}
console.log(f(41)); //!logs "42"

Now, running that produces an error message:

3: ++i;         // Reassig
   ┗━┛
[interactive#2:3+2-5]@T: i__4 is reassigned after interactive#2:2+10-11 but is not declared `var` at interactive#2:2+2-11
2: let i = n;   // Temper does n
           ⇧
2: let i = n;   // Temper does n
   ┗━━━━━━━┛

Mac ARM binary bundles#

While not a core part of the language or compiler, we have now begun releasing Apple silicon ARM binary bundles for Mac.

🆕Date.dayOfWeek#

std/temporal's Date type now has a .dayOfWeek computed property that produces an ISO 8601 weekday number.

🆕Int.max and Int.min#

Previously, Float64 had max and min methods. Now, Int does to.

1.min(10) == 1 &&
1.max(-1) == 1

Listed.reduce#

Temper Listed types now support reduce methods as in the following examples:

let vals = [2, 3, 4];
let sum = vals.reduce { (sum, n);; sum + n }; // -> 9
let notSum = vals.slice(0, 0).reduce { (sum, n);; sum + n } orelse -1; // -> -1
let poorReverseJoin = vals.reduceFrom("") { (text: String, n): String;;
    "${n.toString()}${text}"
}; // -> "432"

🆕StringSlice.excludeAfter#

The new StringSlice method, excludeAfter allows taking the prefix of a slice that ends at another.

StringSlice is meant to allow efficient left-to-right processing of strings, when we can't assume the native string representation supports O(1) random-access for a particular code-unit size.

excludeAfter is used below to choose a prefix of the remaining, unprocessed content up to the point where we need to do some special processing.

do {
  let carriageReturn = 0xA; // ASCII \n

  // Prefixes each line of its input with `> `.
  let markdownQuote(str: String, out: ListBuilder<String>): Void {
    var remainder = str.codePoints; // The unprocessed portion
    var emitted = remainder;        // The portion processed onto out
    while (!remainder.isEmpty) {
      let next = remainder.advance(1);
      if (remainder.read() == carriageReturn) {
        out.add("> ");
        out.add(emitted.excludeAfter(next).toString());
        emitted = next;
      }
      remainder = next;
    }
    if (!emitted.isEmpty) {
      out.add("> ");
      out.add(emitted.toString());
    }
  }

  // Create an output list to accumulate chunks.
  let stringBuilder = new ListBuilder<String>();
  markdownQuote("foo\nbar\nbaz", stringBuilder);
  //!outputs "> foo\n> bar\n> baz"
  console.log(stringBuilder.join("") { (chunk: String): String;; chunk });
}