Appendix D

Reading Guide

You do not need to have read Programming with Nex to follow the listings in this book. Here is the vocabulary they actually use, in one place, for a reader who already knows another statically typed language.

Comments, Values, and Classes

Comments begin with -- and run to end of line. let declares a local with a type and an initial value; := is both declaration-assignment and plain reassignment. Equality is =, inequality is /=.

let total: Integer := 3 + 4
total := total + 1

A class groups data (feature) and constructors (create); inside a method, this is the current object and result is the name you assign to for a return value:

class Counter
create
  make(start: Integer) do value := start end
feature
  value: Integer
  increment() do value := value + 1 end
  current(): Integer do result := value end
end

let c: Counter := create Counter.make(0)
c.increment()

A no-argument method may be called without parentheses — store.tasks rather than store.tasks() — which is why several listings in this book read a field-like value with no trailing ().

Generics, Arrays, and Maps

Type parameters go in square brackets: class Lru_Cache [K, V] (Chapter 3). The two collections every project in this book uses are Array[T] and Map[K, V]:

let xs: Array[Integer] := [1, 2, 3]
xs.add(4)
let first: Integer := xs.get(0)

let prices: Map[String, Integer] := {}
prices.set("apple", 150)
if prices.contains_key("apple") then ... end

Any is the top type every value inherits, used where a project deliberately steps outside static typing — parsing untyped JSON in Chapters 2, 7, and 9 is the only place this book does that.

Detachable Types: ?T

A leading ? on a type marks it detachable — it may hold nil. Task_Store.find in Chapter 2 returns ?Task: either a real task, or nothing, stated in the type rather than left to a caller's memory of whether a sentinel value might come back instead.

let t: ?Task := store.find(id)
if t /= nil then
  t.mark_done()
end

Sum Types: union and match

A sum type — a value that is exactly one of a closed set of shapes — is written union, one line per shape:

union Task_Status
  Open
  Done
end

match takes a sum type apart, and because the set of shapes is closed, the compiler requires every case to be handled — a missing one is a compile error, not a runtime surprise:

match status of
  when Done then result := true
  when Open then result := false
end

A when pattern can also destructure fields carried by a shape — when Bin_Op(op, left, right) then ... in Chapter 4's expression AST binds three locals directly from the matched variant.

Refinement Types

A refinement type narrows an existing type with a predicate, in one line, with no wrapper class — at runtime, the value is its base type:

declare type Priority = Integer where p: p >= 1 and p <= 5

The predicate is checked wherever a plain value is narrowed into the refined type — a parameter, a typed let — and never again; code that already holds a Priority can trust the range without re-checking it. Chapter 2 develops this fully.

Contracts

Contracts are ordinary syntax, not a library, and every chapter in this book uses at least one of the three forms below.

  • require — a precondition, checked before a method runs: what the caller must already guarantee.
  • ensure — a postcondition, checked on return: what the method itself guarantees. old e refers to e's value on entry.
  • invariant — a property that holds of every instance of a class, before and after every method call (Chapter 3 builds a whole library around one).
add(text: String, priority: Priority): Integer
require
  non_empty: text /= ""
do
  ...
ensure
  grew: tasks.length = old tasks.length + 1
end

Each clause has a name (non_empty, grew); a violated contract reports which named promise broke, which is the entire reason this book keeps calling contracts more informative than a generic exception.

Functions and Loops

A free-standing function is written with function. The loop form used throughout this book is from ... until ... do ... end — the from block initializes, the loop runs until its condition becomes true:

let i: Integer := 0
from
  i := 0
until
  i = xs.length
do
  ...
  i := i + 1
end

An anonymous function is written fn(params): ReturnType do ... endChapter 7's HTTP route handler and Chapter 5's sort comparator are both written this way, passed directly where a callback is expected.

Concurrency: Task, spawn, Channel

spawn do ... end starts a block of code running concurrently and returns a Task[T] handle to it — nothing about the syntax differs from an ordinary block, which is deliberate:

let t: Task[Integer] := spawn do
  result := expensive_computation()
end
let value: Integer := t.await()

await_all takes an array of tasks and waits for every one, returning their results in order — Chapter 5's fan-out/fan-in over one file-read task per file is the clearest example in this book. Channel[T] is a typed, thread-safe queue for message passing between tasks, rather than shared mutable state guarded by a lock — Chapter 6's chat hub is built entirely around one.

intern and Java Interop

intern <Name> loads another Nex module by name, resolved against a file whose snake_case name matches; Appendix C covers the naming rule this implies in full. import brings in a Java class by its fully-qualified name, and Java code is called from inside a with "java" do ... end block — only Chapter 8 and Chapter 9's dashboards use either. A Nex class implements a Java interface the same way it inherits a Nex one, with inherit:

class Increment_Listener
inherit
  ActionListener
  ...
  actionPerformed(e: ActionEvent) do
    state.increment()
  end
end

That is the whole vocabulary this book's listings depend on. Where a construct does something subtle, the chapter it appears in explains it in place.