Language Tour
Basics
Section titled “Basics”// All bindings are immutableconst name = "Alice"const age = 30const scores: Array<number> = [95, 87, 92]
// Named functionsfn greet(name: string) -> string { `Hello, ${name}!`}
// Implicit return — last expression is the return valuefn double(n: number) -> number { n * 2}
// Closures use fn() — same keyword, no nameconst add = fn(a, b) a + bconst log = fn() Console.log("clicked")// Pipe value as first argumentconst result = [1, 2, 3, 4, 5] |> Array.filter(fn(n) n > 2) |> Array.map(fn(n) n * 10) |> Array.sort
// Dot shorthand — even shorter than closuresusers |> Array.filter(.active) |> Array.map(.name) |> Array.sort
// Placeholder _ for non-first position5 |> add(3, _) // add(3, 5)
// _ outside pipes creates partial applicationconst addTen = add(10, _) // fn(x) add(10, x)
// Tap for side effects mid-pipelinedata |> transform |> Pipe.tap(Console.log) |> savePattern Matching
Section titled “Pattern Matching”// Replaces if/else, switch, and ternaryconst label = match status { 200..299 -> "success", 404 -> "not found", 500 -> "server error", _ -> "unknown",}
// Destructure union variantsmatch route { Home -> <HomePage />, Profile(id) -> <ProfilePage id={id} />, NotFound -> <NotFoundPage />,}
// Guardsmatch user.age { _ when user.age >= 18 -> "adult", _ -> "minor",}
// Multi-depth destructuringmatch error { Network(Timeout(ms)) -> `Timed out after ${ms}ms`, Network(DnsFailure(host)) -> `DNS failed: ${host}`, NotFound -> "not found", _ -> "unknown error",}
// Array patternsmatch items { [] -> "empty", [only] -> `just ${only}`, [first, ..rest] -> `${first} and ${rest |> Array.length} more`,}
// String patternsmatch url { "/users/{id}" -> fetchUser(id), "/users/{id}/posts" -> fetchPosts(id), _ -> notFound(),}
// Pipe into matchconst icon = temperature |> match { _ when _ < 0 -> "snowflake", 0..15 -> "cloud", 16..30 -> "sun", _ -> "fire",}// Recordstype User { id: string, name: string, email: string,}
// Union types (discriminated, exhaustive)type Shape { | Circle { radius: number } | Rectangle { width: number, height: number } | Triangle { base: number, height: number }}
fn area(shape: Shape) -> number { match shape { Circle(r) -> Math.PI * r * r, Rectangle(w, h) -> w * h, Triangle(b, h) -> 0.5 * b * h, }}
// Constructors — positional or namedconst u = User("1", "Alice", "alice@test.com")const u = User(name: "Alice", id: "1", email: "alice@test.com")
// Record spreadconst updated = User(..u, name: "Bob")
// Tuplesconst point: (number, number) = (10, 20)const (x, y) = point
// Branded types — prevent mixing IDstype UserId = Brand<string, "UserId">type OrderId = Brand<string, "OrderId">
// Newtypes — single-value wrapperstype OrderId { number }const id = OrderId(42)const OrderId(n) = id // destructure to get inner value
// Opaque types — only the defining module can create/readopaque type HashedPassword = string
// String literal unions (for npm interop)type Method = "GET" | "POST" | "PUT" | "DELETE"
// Record compositiontype ButtonProps { ...BaseProps, onClick: fn() -> (), label: string,}
// Tuple index accessconst pair = ("hello", 42)const first = pair.0 // "hello"const second = pair.1 // 42
// Default parameter valuesfn greet(name: string, greeting: string = "Hello") -> string { `${greeting}, ${name}!`}greet("Alice") // "Hello, Alice!"greet("Alice", "Hey") // "Hey, Alice!"Error Handling
Section titled “Error Handling”// Result<T, E> replaces exceptionsfn divide(a: number, b: number) -> Result<number, string> { match b { 0 -> Err("division by zero"), _ -> Ok(a / b), }}
// ? operator for early returnfn loadProfile(id: string) -> Result<Profile, Error> { const user = fetchUser(id)? const posts = fetchPosts(user.id)? Ok(Profile(user, posts))}
// Option<T> replaces null/undefinedmatch user.nickname { Some(nick) -> nick, None -> user.name,}
// collect — accumulate all errors instead of failing fastfn validateForm(input: FormInput) -> Result<ValidForm, Array<string>> { collect { const name = validateName(input.name)? const email = validateEmail(input.email)? const age = validateAge(input.age)? ValidForm(name, email, age) }}
// parse<T> — compiler-generated runtime validationconst user = json |> parse<User>?const items = data |> parse<Array<Product>>?// Async functionsasync fn fetchUser(id: string) -> Result<User, Error> { const response = await Http.get(`/api/users/${id}`)? const user = await response |> Http.json? Ok(user)}
// npm functions might throw — wrap with tryimport { parseYaml } from "yaml-lib"const config = try parseYaml(rawText) // Result<T, Error>
// Async closuresconst data = useSuspenseQuery({ queryKey: ["users"], queryFn: async fn() fetchUsers(),})Stdlib
Section titled “Stdlib”// Map<K, V>const config = Map.fromArray([("host", "localhost"), ("port", "8080")])const updated = config |> Map.set("port", "3000")const port = config |> Map.get("port") // Option<string>
// Set<T>const tags = Set.fromArray(["urgent", "bug"])const withNew = tags |> Set.add("frontend")const common = Set.intersect(teamA, teamB)
// Http — pipe-friendly fetch returning Resultconst users = await Http.get("https://api.example.com/users")? |> Http.json?const result = await Http.post(url, { name: "Alice" })?
// Structural equality — == does deep comparisonconst a = User(name: "Alice", age: 30)const b = User(name: "Alice", age: 30)const same = a == b // true (compares fields, not references)For Blocks
Section titled “For Blocks”// Attach functions to types (like extension methods)for Array<Todo> { export fn remaining(self) -> number { self |> Array.filter(.done == false) |> Array.length }}
// Inline formexport for string fn shout(self) -> string { self |> String.toUpper}
// Use in pipes — self is the piped valuetodos |> remaining"hello" |> shout // "HELLO"Traits
Section titled “Traits”trait Display { fn display(self) -> string}
for User: Display { fn display(self) -> string { `${self.name} (${self.email})` }}
// Auto-derive for recordstype Point { x: number, y: number,} deriving (Display)import trusted { useState } from "react"
export fn Counter() -> JSX.Element { const [count, setCount] = useState(0)
<div> <h1>{`Count: ${count}`}</h1> <button onClick={fn() setCount(count + 1)}> Increment </button> {count |> match { 0 -> <p>Click the button!</p>, _ -> <p>{`Clicked ${count} times`}</p>, }} </div>}Imports
Section titled “Imports”// Standard importimport { Todo, Filter } from "./types"
// npm imports are unsafe by defaultimport { parseYaml } from "yaml-lib"const result = try parseYaml(input) // wraps in Result
// trusted imports skip the try requirementimport trusted { useState } from "react"import trusted { clsx } from "clsx"
// Import for-block extensionsimport { for Array, for string } from "./helpers"fn add(a: number, b: number) -> number { a + b }
test "addition" { assert add(1, 2) == 3 assert add(-1, 1) == 0}Placeholders
Section titled “Placeholders”// todo — compile-time warning, throws at runtimefn processPayment(order: Order) -> Result<Receipt, Error> { todo}
// unreachable — asserts a code path is impossiblematch direction { "north" -> go(0, 1), "south" -> go(0, -1), "east" -> go(1, 0), "west" -> go(-1, 0), _ -> unreachable,}