Appendix C

Derived Forms

A number of Nex’s surface forms are conveniences: they add no expressive power, and are explained by translation into a smaller bare language. Defining them this way keeps the semantic rules of Chapters 4 and 5 few, since a derived form inherits its meaning from its translation. This appendix gives those translations.

Each translation is shown as a surface form, an arrow, and the bare form it is translated to: wherever the phrase on the left of the arrow appears, it is replaced by the phrase on the right. Translation happens before elaboration, so the static and dynamic semantics see only bare forms. The fresh identifiers a translation introduces, written _i, _c and the like, are chosen to differ from every identifier in the program.

C.1Collection Displays

The array, map, and set displays abbreviate a creation followed by a sequence of insertions.

[e₁, …, eₙ]→do let _a := create Array; _a.add(e₁); … ; _a.add(eₙ); _a end
{k₁: v₁, …, kₙ: vₙ}→do let _m := create Map; _m.put(k₁, v₁); … ; _m end
{}→create Map
#{e₁, …, eₙ}→do let _s := create Set; _s.add(e₁); … ; _s end
#{}→create Set

The element type of the created collection is inferred from the display, or taken from the annotation of the surrounding let or parameter when the display is empty. The empty set must be written #{} and not {}: the latter is the empty map.

(A do…end used here is the block-as-expression whose value is its final expression. The displays are the one place the bare language needs it, and it may be read simply as “build the collection and yield it.”)

C.2The Counted Loop

The repeat loop runs its body a number of times given by its bound. It translates into a general loop over a fresh counter.

repeat e do b end→
from let _i := 0 until _i >= e do b ; _i := _i + 1 end

The bound e appears in the until condition and so is re-evaluated before each iteration; an assignment within the body to a variable occurring in e therefore alters the number of repetitions. A program that requires a count fixed in advance should bind the bound first (let n := e) and repeat over n.

C.3The Cursor Loop

The across loop traverses any Iterable value (Appendix B), or any value of a class with a cursor method that returns a cursor. It translates into a general loop driven by the Cursor methods.

across e as x do b end→
do let _c := e.cursor; _c.start;
  from until _c.at_end do let x := _c.item ; b ; _c.next end end

The cursor variable x is bound afresh on each iteration to the current item, and is in scope only within the body. Its type is that of e.cursor.item: T when e is an Iterable[T]. When e is itself a cursor, it is the type of e.item, so a cursor class that declares a narrower item keeps it. An Array yields its elements in order, a String its characters, and a Map its entries as Map_Entry values with a key and a value (Section 2.8 and Appendix B).

C.4Cascaded Conditionals

The elseif chain abbreviates nested conditionals.

if e₁ then b₁ elseif e₂ then b₂ else b₃ end→
if e₁ then b₁ else (if e₂ then b₂ else b₃ end) end

C.5Shared Parameter Types

Several parameters may share a single type annotation; this abbreviates the fully annotated list.

(a, b: T)→(a: T, b: T)

C.6Union Declarations

A union declaration (Section 3.2.2) abbreviates a sealed hierarchy of data classes. The parent becomes a sealed deferred class, and each variant becomes a class inheriting it, whose fields are the variant’s payload and whose sole constructor takes and assigns them.

union P⟨[G…]⟩ V₁ … Vₙ end→
sealed deferred class P⟨[G…]⟩ end
  and, for each variant Vᵢ(f₁: T₁, …, fₖ: Tₖ),
  class Vᵢ inherit P⟨[G…]⟩ feature f₁: T₁ … fₖ: Tₖ
    create make(f₁: T₁, …, fₖ: Tₖ) do f₁ := f₁ ; … ; fₖ := fₖ end end

A payload-free variant Vᵢ becomes a class with an empty feature section and a nullary make. Because the translation names the parent in each variant’s inherit clause, the generic arguments carry through, and the resulting classes are exactly those a hand-written sealed hierarchy would produce. Nothing further — no method, invariant, or contract — is generated.

Prefixing the declaration with enum enriches the same hierarchy into an enumeration. This is permitted only when every variant is payload-free and P is non-generic. On top of the translation above, the parent gains an ordering and one interned constant per member, and each variant’s make records its declaration index.

enum union P V₁ … Vₙ end→
sealed deferred class P inherit Comparable
  feature ordinal: Integer
    set_ordinal(o: Integer) do ordinal := o end
    compare(o: Any): Integer do
      if convert o to p: P then result := ordinal − p.ordinal
      else raise "P.compare requires a P" end
    end
  feature V₁: P = create V₁.make() ; … ; Vₙ: P = create Vₙ.make()
    values: Array[P] = [V₁, …, Vₙ] end
  and, for each variant Vᵢ at 0-based declaration index i,
  class Vᵢ inherit P create make() do this.set_ordinal(i) end end

Thus P.Vᵢ is the one canonical Vᵢ value: a class constant, evaluated once (Section 3.3), so P.Vᵢ == P.Vⱼ tests identity. The members order by the sequence in which they are written, and P.values lists them all. compare takes an Any, as Comparable’s does, since an override may not narrow a parameter (Section 4.9); given a value that is not a member of P, it raises.

Because the enrichment claims those four names, a variant may not be named ordinal, set_ordinal, compare, or values.

Each variant’s constructor reaches ordinal through set_ordinal rather than assigning it directly, since it is a field P declares, not one Vᵢ does. The field assignment rule of Section 4.5 confines a field write to its own declaring class regardless of receiver, so Vᵢ could not assign it any other way — bare or otherwise.

A plain union never gains any of this; the enrichment is requested, never inferred.

C.7Match Patterns and Guards

A match clause richer than the bare id as id dispatch of the core (Section 2.8) is translated into it. A clause V(fp₁, …, fpₙ) as x if g then b becomes the bare clause V as _m — binding x to _m as well when as x is present — whose body binds the field patterns and then tests the field constraints together with the guard. If any test fails, control passes to the clauses that follow, exactly as a false guard does.

Each field pattern fpᵢ on the field f contributes a binding, a test, or both:

f (bind)→let f := _m.f
f as y (rename)→let y := _m.f
_ (skip)→— nothing
f: T (type test)→test convert _m.f to _n: T, then let f := _n
f: T(…) (nested)→test convert _m.f to _n: T, then T’s field patterns on _n

The tests are conjoined with the guard g. A clause whose field patterns are only binds, renames, and skips contributes no test, so once its class matches it commits and never falls through — which is why such a clause still counts toward exhaustiveness (Section 4.5), whereas a clause carrying a type test, a nested pattern, or a guard does not. The one exception is a type test f: T that cannot fail because the declared type of f already conforms to T; its convert still runs, but the clause counts as if it were a plain bind. A top-level _ pattern translates to the else block.

C.8Expression Functions

An anonymous function whose body would only compute its result may be written with => and the expression. The expression extends as far to the right as an expression can.

fn (ps): T => e→fn (ps): T do result := e end

Unlike the other forms here, the translation is completed during elaboration, by the rule for anonymous functions in Section 4.4: when the result type T is neither written nor supplied by the context, it is the type of e; and when the result type is Void, or e has no value (a call to a procedure), the body is e itself, as a statement, rather than result := e.

let double := fn (n: Integer) => n * 2        -- Function(n: Integer): Integer
names.sort(fn (a, b) => a.length - b.length)  -- a, b: String, from Array[String].sort
let say := fn (m: String) => print(m)         -- no result

C.9Extensions of Classes

An extension whose target is a class (Section 3.2.3) abbreviates editing that class’s declaration: its feature sections are appended to the class’s own.

class C⟨[G…]⟩ … S₁ … Sₖ ⟨invariant⟩ end
extend C⟨[G…]⟩ X₁ … Xₘ end→
class C⟨[G…]⟩ … S₁ … Sₖ X₁ … Xₘ ⟨invariant⟩ end

Several extensions of one class are appended in the order in which the static world receives them: those of interned units first, as the units are elaborated, then the current unit’s, in source order. The order affects no meaning, since an extension may not supply a feature the class or another extension supplies.

The translation is not quite textual. Each routine appended this way keeps the knowledge that it came from an extension, for two purposes. Its body is elaborated with a client’s view of the class (Section 4.10), not the class’s own; and a diagnostic about it names the extension’s unit and line.

C.10What Is Not Derived

It is worth saying which apparent conveniences are not derived forms, but primitive constructs with rules of their own.

The conditional expression when…then…else is primitive (Section 4.4), not an abbreviation for an if statement, because it yields a value.

The short-circuit operators and, or and implies are primitive (Section 5.3), not abbreviations for conditionals, because their evaluation order is part of the language’s definition. a implies b has the value of not a or b but is not an abbreviation for it either: its right operand is checked with the narrowings of a in effect, as an and’s is (Section 4.5), and an or’s is not. The safe access ?. is primitive too (Sections 4.4 and 5.3).

The contract clauses, though they could in principle be expanded into explicit checks and raises, are treated as primitive so that the Definition can speak of preconditions, postconditions, and invariants as first-class notions (Section 5.6) rather than as a particular pattern of generated code.

An extension of a built-in type or a host class is primitive too (Sections 4.10 and 5.4). Such a type has no declaration for the extension to be folded into, and the features it adds are reached by rules of their own rather than as methods of a class.