// 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 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}`);
};
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}`);
};
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}`);
}
};
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 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 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}`);
}
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}`);
}
}
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}`);
}
}
// 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);
};
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 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)}`);
};
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 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 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));
};
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}`);
}
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')}`);
};
// 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}`);
};
import { add, greet } from './math.zena';
export let main = () => {
console.log(greet('Zena Developer'));
console.log(`1 + 2 = ${add(1, 2)}`);
};