Functions
zena
// Top-level declarations use `function` (never a closure):
function greet(name: String, prefix: String = 'Hello'): String {
  return `${prefix}, ${name}!`;
}

export function main() {
  let names = ['Alice', 'Bob'];
  var count = 0;

  // Arrow functions are closures and can be assigned to variables
  let addCount = (text: String) => `(${count += 1}) ${text}!`;

  console.log(addCount(greet('World')));

  // Inline closures are contextually typed (`n` needs no annotation):
  let messages = names.map((n) => addCount(greet(n, 'Hi')));

  for (let msg in messages) {
    console.log(msg);
  }
}
Tail Calls
zena
// `tail return` compiles the call to WebAssembly's `return_call`: the
// frame is discarded before the callee runs, so a chain of tail calls
// runs in constant stack space. The same code with a plain `return`
// exhausts the stack long before a million frames.
function depth(n: i32, acc: i32): i32 {
  if (n == 0) {
    return acc;
  }
  tail return depth(n - 1, acc + 1);
}

// Mutual recursion counts too, and so do method, closure and
// interface calls.
function isEven(n: i32): boolean {
  if (n == 0) { return true; }
  tail return isOdd(n - 1);
}

function isOdd(n: i32): boolean {
  if (n == 0) { return false; }
  tail return isEven(n - 1);
}

export let main = () => {
  console.log(`depth reached: ${depth(1000000, 0)}`);
  console.log(`1000000 is even: ${isEven(1000000)}`);

  // `tail` is contextual, so it is still an ordinary name.
  let tail = [1, 2, 3];
  console.log(`tail: ${tail.length}`);
};
Expression Oriented
zena
let parse = (input: String): i32 => {
  if (input == 'bad') {
    throw new Error('not a number');
  }
  return input.length;
};

export let main = () => {
  // `if`, `try`, `match`, and `throw` are all expressions.
  let ok = true;
  let status = if (ok) 'Completed' else throw new Error('failed');
  console.log(status);

  let value = try {
    parse('bad')
  } catch (e) {
    -1
  };
  console.log(`${value}`);
};
Loops
zena
export let main = () => {
  let items = [10, 20, 30];

  // `for-in` walks any Iterable.
  for (let item in items) {
    console.log(`${item}`);
  }

  // Iterator.next() returns inline (found, value) — `while let` unwraps it.
  let iterator = items.[Iterable.iterator]();
  while (let (true, item) = iterator.next()) {
    console.log(`next: ${item}`);
  }
};
Pipelines
zena
let shout = (s: String): String => `${s.asciiUpperCase()}!`;
let repeat = (s: String, times: i32): String => {
  var out = '';
  for (var i = 0; i < times; i += 1) {
    out += s;
  }
  return out;
};

export let main = () => {
  // `|>` pipes the left value into `$` on the right.
  let banner = 'zena'
    |> shout($)
    |> repeat($, 2);
  console.log(banner);
};
Types
zena
// Types can be primitives
let x: i32 = 123;

// Or concrete classes. FixedArray is a class
let a: FixedArray<i32> = [1, 2, 3];

// Or interface types. Map is an interface type. Mixins define interfaces too.
let m: Map<String, i32> = {'Alice' => 95, 'Bob' => 87};

// Type aliases can define new types

// Like record types:
type Point = {x: f64, y: f64};

// Tuple types:
type Pair = (String, i32);

// Literal types:
type Success = 'success';

// Union types:
type Status = 'success' | 'failure';

// Function types:
type IntToString = (x: i32) => String;

// Distinct types are new nominal names for an existing type
distinct type UserId = i32;

// Opaque types hide implementation details
opaque type RecordId = String;

// Unions can not mix primitives and references
type U = String | i32; // error
type V = String | Box<i32>; // OK
Records & Tuples
zena
// Records are anonymous structures of named fields:
let origin = {x: 10.0, y: 20.0};

// Tuples are anonymous, ordered sets of values:
let result = (true, 'Hello');

// Record types can have optional fields
type Opts = {timeout?: i32, retry?: boolean};

function getTimeout(opts: Opts) {
  // Optional fields must be read with a default
  return opts.timeout ?? 5000;
}

export function main() {
  // Records are read with property access
  let x = origin.x;

  // Tuples are read with index access
  let isValid = result[0];

  // Optional fields are optional
  let timeout = getTimeout({});

  console.log(`${x}, ${isValid}, ${timeout}`);
}
Arrays
zena
export function main() {
  // Array literals create a FixedArray, which is an unwrapped Wasm GC array
  let numbers: FixedArray<i32> = [10, 20, 30];

  console.log(`numbers[1]: ${numbers[1]}`);

  logInts(numbers, 'Array');

  // Growable arrays use the GrowableArray class (literal coming soon!)
  let myNumbers = new GrowableArray<i32>();
  myNumbers.push(42);

  logInts(myNumbers, 'GrowableArray');
}

// The base array interface is Array:
function logInts(numbers: Array<i32>, label: String) {
  console.log(label + ':');

  // Iteration can be done with for/in:
  for (let n in numbers) {
    console.log(`- ${n}`);
  }
}
Maps
zena
export function main() {
  // Zena has map literals, which create a HashMap
  let scores: Map<String, i32> = {'Alice' => 95, 'Bob' => 87};

  // Maps support the [] operator, which either returns a value or _throws_, so
  // check if the value exists with .has()
  if (scores.has('Alice')) {
    console.log(`Alice scored ${scores['Alice']}`);
  }

  // To avoid throwing use .get(), which returns an inline (boolean, value)
  if (let (true, score) = scores.get('Bob')) {
    console.log(`Bob scored ${score}`);
  }

  // Add values to a Map with []=
  scores['Chris'] = 90;

  logScores(scores);
}

function logScores(scores: Map<String, i32>) {
  // Maps are iterable, yielding MapEntry with `.key` and `.value` fields
  for (let {key as name, value as score} in scores) {
    console.log(`${name}: ${score}`);
  }
}
Classes
zena
// Fields are immutable by default; `var` opts into mutation.
class Cat {
  id: String;                  // public, immutable
  #greeting = 'Meow';          // private
  var name = 'Bob';            // public, mutable
  var(#mood) mood: String;     // public getter, private setter

  new(this.id, this.name, mood: String) : #mood = mood {
    console.log(`Created cat: ${this.name}`);
  }

  sayHi(): String {
    return `${this.#greeting}, I'm ${this.name}`;
  }
}

export let main = () => {
  let cat = new Cat('c-1', 'Whiskers', 'grumpy');
  console.log(cat.sayHi());
  console.log(cat.mood);
};
Mixins & Interfaces
zena
interface Animal {
  speak(): void;
}

mixin Friendly {
  greet(name: String): void {
    console.log(`Hello, ${name}!`);
  }
}

// Classes implement interfaces explicitly and pick up behaviour with `with`.
class Dog with Friendly implements Animal {
  speak(): void {
    console.log('Woof');
  }
}

export let main = () => {
  let dog = new Dog();
  dog.speak();
  dog.greet('Zena');
};
Sealed Classes
zena
// Sealed hierarchies are a closed set, so `match` is checked for exhaustiveness.
sealed class Expr {
  case Lit(value: i32)
  case Add(left: Expr, right: Expr)
  case Neg(operand: Expr)
}

let eval = (e: Expr): i32 => match (e) {
  case Lit {value}: value
  case Add {left, right}: eval(left) + eval(right)
  case Neg {operand}: -eval(operand)
};

export let main = () => {
  let expr = new Add(new Lit(2), new Neg(new Lit(5)));
  console.log(`${eval(expr)}`);
};
Pattern Matching
zena
sealed class Shape {
  case Circle(radius: f64)
  case Rect(width: f64, height: f64)
}

// Guards run after the pattern matches; `_` is the wildcard.
let describe = (shape: Shape): String => match (shape) {
  case Circle {radius} if radius > 10.0: 'a large circle'
  case Circle: 'a circle'
  case Rect {width, height} if width == height: 'a square'
  case _: 'a rectangle'
};

export let main = () => {
  console.log(describe(new Circle(20.0)));
  console.log(describe(new Circle(5.0)));
  console.log(describe(new Rect(3.0, 3.0)));
  console.log(describe(new Rect(4.0, 5.0)));
};
Enums
zena
// Enums are nominal wrapper types backed by integers or strings.
enum Color {
  Red,
  Green,
  Blue
}

export let main = () => {
  let color: Color = Color.Red;
  console.log(`${color == Color.Red}`);
};
Extension Classes
zena
// Extension classes add methods to a type you don't own — including primitives.
extension class IntExtensions on i32 {
  isEven(): boolean {
    return this % 2 == 0;
  }
}

// Methods resolve on the static extension type, so there is no dispatch cost.
let describe = (n: IntExtensions): String => if (n.isEven()) 'even' else 'odd';

export let main = () => {
  console.log(describe(4 as IntExtensions));
  console.log(describe(7 as IntExtensions));
};
Async Functions
zena
import { sleep, milliseconds } from 'zena:time';

// Async functions return a Future<T> and can await other futures.
async function fetchUser(id: i32): Future<String> {
  await sleep(milliseconds(1000));
  return `User #${id}`;
}

export async function main(): Future<void> {
  console.log('starting...');
  // await waits for an async function call to complete.
  let userOne = await fetchUser(1);
  console.log(userOne);

  // async functions return Futures when not awaited
  let userTwoFuture: Future<String> = fetchUser(2);

  // Combinators like Future.all() can wait for multiple Futures in parallel
  let [userTwo, userThree] = await Future.all([userTwoFuture, fetchUser(3)]);

  console.log(`${userTwo}, ${userThree}`);
}
Regular Expressions
zena
import {regex} from 'zena:regex';

export let main = () => {
  // The `regex` template tag takes raw text — no double-escaped backslashes.
  let pattern = regex`^[a-z]+$`;
  console.log(`${pattern.test('hello')}`);
  console.log(`${pattern.test('Hello')}`);
};
Imports & Exports
zena
// Standard ES import syntax.
import {min, max} from 'zena:math';

// The Python-style form is also supported.
from 'zena:math' import {abs};

// There are no globals: everything, including `console`, is imported.
export let pi = 3.14159;

export let main = () => {
  console.log(`${min(2.0, 7.0)} ${max(2.0, 7.0)} ${abs(-3.0)} ${pi}`);
};
Modules
zena
import { add, greet } from './math.zena';

export let main = () => {
  console.log(greet('Zena Developer'));
  console.log(`1 + 2 = ${add(1, 2)}`);
};