Samples
Patterns that teach the language
Focused VexaScript snippets covering operator overloads, delegates, sync functions, ranges, extensions, and more.
Operator overloading
Operator and method overloading and new-less constructions, for concise writing.
class Vec2(val x: number, val y: number) {
operator+(other: Vec2) => Vec2(x + other.x, y + other.y)
operator-(other: Vec2) => Vec2(x - other.x, y - other.y)
}
Vec2(1, 2) + Vec2(3, 4)
JSX with Preact
Destructuring with types to avoid duplication while being concise.
import { h } from "preact"
fun Welcome({ name: string }) {
return <section>Hello {name}</section>
}
Implicit property access
When no ambiguity happens, this is optional.
class Counter(var value: int) {
fun increment(): int => ++value
}
Class delegates
Satisfy an interface by forwarding its members to another value using by.
interface Shape {
area: number
fill(color: string): string
}
class Rectangle(val width: number, val height: number) : Shape {
area => width * height
fill(color: string) => `${color}:${width}x${height}`
}
class ShapeLogger(val shape: Shape, val label: string) : Shape by { shape } {
describe() => `${label}: area=${area}`
}
Tuple delegate
A [value, setter] tuple delegate wires reads and writes through custom accessors — like React's useState.
fun useState(value: number) {
return [() => value, (newValue: number) => { value = newValue }]
}
var count by useState(0)
count = count + 1
count += 1
count++
Object & function delegates
A { value } object or a zero-argument function also work as delegates — all assignments route through the accessor.
fun box<T>(initial: T) {
return { value: initial }
}
var source = 1
var observed by () => source
var total by box(0)
total = observed + 2
source = 5
total += observed
total++
Sync functions
sync functions auto-await any Promise-typed expression. Write sequential code without explicit await.
sync fun fetchPrice(item: string): number {
return fetch(`/prices/${item}`).json()
}
sync fun checkout(): number {
const base = fetchPrice("book")
const tax = fetchPrice("tax")
return base + tax
}
The go operator
Inside a sync function, prefix an expression with go to keep the raw Promise instead of auto-awaiting it.
sync fun main(): void {
const pending: Promise<number> = go fetchPrice("audit")
const price = fetchPrice("book")
console.log(price)
console.log(await pending)
}
Defer
defer schedules cleanup for the end of the block — it runs even when the block returns early or throws.
fun readValue(): int {
console.log("open")
defer console.log("close-2")
defer console.log("close-1")
console.log("read")
return 7
}
Range expressions
... is end-inclusive; ..< is end-exclusive. Both work directly in for-of loops and as values.
for (n of 0 ..< 5) {
console.log(n)
}
for (n of 1 ... 5) {
console.log(n)
}
Smart casts
Inside if branches, is and in narrow the type of a stable identifier automatically.
class Cat { meow() {} }
class Dog { bark() {} }
fun greet(animal: Cat | Dog) {
if (animal is Cat) {
animal.meow()
} else {
animal.bark()
}
}
fun clamp(value: int | string) {
if (value in 0 ... 100) {
const safe: int = value
}
}
Tail lambdas
A lambda after the closing parenthesis — or as the only argument — follows Kotlin/Swift style and reduces visual noise.
val doubled = [1, 2, 3].map { it * 2 }
val even = [1, 2, 3, 4].filter { it % 2 == 0 }
val result = [1, 2, 3].map {
const tripled = it * 3
tripled + 1
}
Extension properties
Add read-only properties to existing types. Import them where needed; access without an import is an error.
class Duration(val milliseconds: number)
val number.milliseconds => Duration(this)
val number.seconds: Duration => Duration(this * 1000)
val number.minutes: Duration => Duration(this * 60_000)
val d1 = 500.milliseconds
val d2 = 2.seconds
val d3 = 1.minutes
Generic extension methods
Extension methods can be generic and work with built-in collection types like Array<T>.
fun <T> Array<T>.second(): T => this[1]
val <T> Array<T>.doubledLength => length * 2
val xs = [10, 20, 30]
console.log(xs.second()) // 20
console.log(xs.doubledLength) // 6
Named arguments
Pass arguments by parameter name in any order. The compiler reorders them to match the callee's parameter list.
fun connect(host: string, port: number, tls: boolean = false) {}
connect(port: 8080, host: "localhost")
connect("localhost", port: 8080, tls: true)
class Point(val x: number, val y: number)
val p = Point(y: 2, x: 1)
Function overloads
Multiple functions can share the same name when their parameter types differ. The compiler picks the right one at each call site.
function describe(value: int): string { return "int:" + value }
function describe(value: string): string { return "str:" + value }
console.log(describe(42))
console.log(describe("hello"))