A compact, linear tour of Nex — from first programs to classes, contracts, and modules.
Nex programs are built from class declarations, function declarations, and calls.
print("Hello, Nex") -- "Hello, Nex"
Comments start with --:
-- This line is ignored by the compiler/interpreter
print("Hello again") -- "Hello again"
print shows strings with surrounding quotes and numbers as-is.
Variables are introduced with let and assigned with :=.
let name: String := "Ada"
let age: Integer := 12
let height: Real := 1.52
let ok: Boolean := true
In the REPL, type annotations are optional by default:
let x := 10
let y := x + 5
Built-in scalar types include:
IntegerInteger64RealDecimalCharBooleanStringDetachable (nullable) types use a leading ?:
let maybe_name: ?String := nil
Arithmetic:
let a := 10 + 2 * 3 -- 16
let b := (10 + 2) * 3 -- 36
let c := 10 % 3 -- 1
let d := 2 ^ 8 -- 256
Comparison and boolean logic:
let e := a = b -- false
let f := a /= b -- true
let g := (a > 5) and (b < 40) -- true
let h := not g -- false
Operator precedence follows conventional order: unary, multiplicative, additive, comparison, equality, and, or.
if age >= 18 then
print("adult")
elseif age >= 13 then
print("teen")
else
print("child")
end
With age at 12 (from above), this prints "child".
when is an expression, so it can appear on the right-hand side:
let category := when age >= 18 "adult" else "minor" end -- "minor"
case age of
0, 1, 2 then print("small")
3, 4, 5 then print("medium")
else print("large")
end
With age at 12, this prints "large".
Each then branch takes a statement. If you need multiple statements, use a scoped do ... end block.
from ... until ... do ... endfrom
let i: Integer := 1
until
i > 5
do
print(i)
i := i + 1
end
Prints 1 through 5, one per line.
repeatrepeat 3 do
print("tick")
end
Prints "tick" three times.
acrossacross [10, 20, 30] as x do
print(x)
end
Prints 10, 20, 30. across also works with strings and maps.
Top-level functions use function:
function greet(name: String)
do
print("Hello, " + name)
end
function double(n: Integer): Integer
do
result := n * 2
end
Call forms:
greet("Bob") -- "Hello, Bob"
print(double(5)) -- 10
Anonymous functions use fn:
let inc := fn (n: Integer): Integer do
result := n + 1
end
print(inc(10)) -- 11
let xs: Array [Integer] := [1, 2, 3]
print(xs.get(0)) -- 1
let m: Map [String, String] := {"name": "Nex", "kind": "language"}
print(m.get("name")) -- "Nex"
Array and map literals are expressions, so they can be nested.
let ids: Set[Integer] := #{1, 2, 3}
print(ids.contains(2)) -- true
print(ids.union(#{4})) -- #{1, 2, 3, 4}
An empty set uses #{}:
let empty_ids: Set[Integer] := #{}
spawn starts a lightweight concurrent task:
let t: Task[Integer] := spawn do
result := 40 + 2
end
print(t.await) -- 42
If a task does not produce a value, use plain Task:
let t: Task := spawn do
print("working") -- "working"
end
t.await
Tasks can also be timed or cancelled:
print(t.await(100))
print(t.cancel)
print(t.is_cancelled)
Task groups can be joined in two ways:
print(await_any([t1, t2]))
print(await_all([t1, t2]))
Channels let tasks exchange values safely:
let ch: Channel[Integer] := create Channel[Integer]
spawn do
ch.send(42)
end
print(ch.receive) -- 42
ch.close
By default, channels are unbuffered: send and receive rendezvous, so each side waits for the other.
For buffered communication:
let ch: Channel[Integer] := create Channel[Integer].with_capacity(2)
ch.send(1)
ch.send(2)
print(ch.size) -- 2
Non-blocking probes:
print(ch.try_send(3))
print(ch.try_receive)
Timed channel operations:
print(ch.send(3, 50))
print(ch.receive(50))
Use select when you want to react to whichever channel operation is ready first:
select
when inbox.receive as msg then
print(msg)
when worker.await as value then
print(value)
when control.receive as signal then
print(signal)
timeout 100 then
print("timed out")
else
print("idle")
end
A class groups fields and methods.
class Counter
create
make(start: Integer) do
this.value := start
end
feature
value: Integer
inc() do
this.value := this.value + 1
end
current(): Integer do
result := value
end
end
Construction uses create:
let c: Counter := create Counter.make(10)
c.inc
print(c.current) -- 11
Parameterless calls may omit parentheses (c.inc, c.current).
class Box [T]
create
make(initial: T) do
this.value := initial
end
feature
value: T
get(): T do
result := value
end
end
With constraints:
class Dictionary [K -> Hashable, V]
feature
key: K
value: V
end
class Animal
feature
name: String
speak do
print(name)
end
create
named(name: String) do
this.name := name
end
end
class Dog
inherit Animal
feature
speak do
print(name + " says woof")
end
end
let a: Animal := create Animal.named("Ko")
a.speak -- "Ko"
let d: Animal := create Dog.named("Ki")
d.speak -- "Ki says woof"
Contracts are first-class in Nex.
class Wallet
feature
money: Real
spend(amount: Real)
require
non_negative_amount: amount >= 0.0
enough: amount <= money
do
this.money := money - amount
ensure
decreased: money = old money - amount
end
create
with_balance(amount: Real) do
money := amount
end
invariant
never_negative: money >= 0.0
end
let w: Wallet := create Wallet.with_balance(-10)
Error: Class invariant violation: never_negative
With a valid balance the methods run, and a broken precondition is reported the same way:
let w: Wallet := create Wallet.with_balance(10.2)
w.spend(9)
w.money -- 1.1999999999999993
w.spend(2) -- Error: Precondition violation: enough
let attempts := 0
do
attempts := attempts + 1
if attempts < 3 then
raise "not ready"
end
print("ok")
rescue
print("retrying")
retry
end
raise, rescue, and retry provide structured recovery paths.
import java.util.Scanner
import Math from './math.js'
intern math/Calculator
intern math/Calculator as Calc
Use intern for Nex-to-Nex modularity and import for target-platform interop.
class BankAccount
create
make(initial: Real) do
this.balance := initial
end
feature
balance: Real
deposit(amount: Real)
require
positive: amount > 0.0
do
this.balance := balance + amount
ensure
grew: balance >= old balance
end
withdraw(amount: Real)
require
positive: amount > 0.0
enough: amount <= balance
do
this.balance := balance - amount
ensure
shrank: balance = old balance - amount
end
show do
print("balance = " + balance)
end
invariant
never_negative: balance >= 0.0
end
let account := create BankAccount.make(100.0)
account.deposit(25.0)
account.withdraw(40.0)
account.show -- "balance = 85.0"
intern (Section 13).Ready to go deeper? The book Programming with Nex covers everything here at a teaching pace — with the reasoning behind each feature, larger worked examples, and graded exercises.
max(a, b: Integer): Integer using if.10 down to 1.Pair [A, B] class with first and second.transfer(amount) method between two accounts.intern.Optional tooling — skip on a first read. Nex ships an interactive debugger you can drive from the REPL once you are comfortable writing programs. This is a quick reference; the full command set is in docs/md/DEBUGGER.md.
Enable the debugger in the REPL:
:debug on
Set breakpoints and run:
:break Wallet.spend
:break Wallet.spend if amount > 100
:break field:money
:tbreak Wallet.spend:42
At the dbg> prompt:
:where
:locals
:print money
:next
:continue
Watch values change:
:watch money
:watch money if money > 100
:watches
Tune breakpoint hit behavior:
:ignore 1 2 -- breakpoint[1] ignores first 2 hits
:every 1 3 -- breakpoint[1] pauses every 3rd hit
Control breakpoints without deleting:
:disable 1
:enable 1
Pause on failures:
:breakon exception on
:breakon contract on
:breakon contract filter invariant
Save and restore debugger state:
:breaksave .nex-debug.edn
:breakload .nex-debug.edn