# East Developer Guide Usage guide for the East programming language. This guide covers core expression types and their operations. For additional formatting, conversion, and generation utilities, see the **[Standard Library documentation](./STDLIB.md)**. --- ## Table of Contents - [Quick Start](#quick-start) - [Types](#types) - [East Namespace](#east-namespace) - [Functions](#functions) - [Expressions](#expressions) --- ## Quick Start East is a **statically typed, expression-based language** embedded in TypeScript. You write East programs using a fluent TypeScript API, then compile them to portable **IR (Intermediate Representation)** that can execute in different environments (javascript, julia, python, etc). East code runs in a controlled environment - you define **platform functions** that your East code can call, providing access to external capabilities like logging, database access, or any other effects you want to expose. **Workflow:** 1. **Define platform functions** - create an object with the external functions you want East code to access (e.g., logging, I/O, database queries) 2. **Define East functions** using `East.function()` with explicit types 3. **Build expressions** using methods on typed expression objects (`.add()`, `.map()`, etc.) 4. **Compile and run** using `East.compile(fn, platform)` to execute the code 5. **(Optional) Serialize to IR** using `.toIR()` for transmission/storage across environments ### Basic Example ```typescript import { East, IntegerType, ArrayType, StructType, StringType, DictType, NullType } from "@elaraai/east"; // Platform function for logging const log = East.platform("log", [StringType], NullType); const platform = [ log.implement(console.log), ]; // Define sale data type const SaleType = StructType({ product: StringType, quantity: IntegerType, price: IntegerType }); // Calculate revenue per product from sales data const calculateRevenue = East.function( [ArrayType(SaleType)], DictType(StringType, IntegerType), ($, sales) => { // Group sales by product and sum revenue (quantity × price) const revenueByProduct = sales.groupSum( // Group by product name ($, sale) => sale.product, // Sum quantity × price ($, sale) => sale.quantity.multiply(sale.price) ); // Log revenue for each product $(log(East.str`Total Revenue: ${revenueByProduct.sum()}`)); $.return(revenueByProduct); } ); // Compile and execute const compiled = East.compile(calculateRevenue, platform); const sales = [ { product: "Widget", quantity: 10n, price: 50n }, { product: "Gadget", quantity: 5n, price: 100n }, { product: "Widget", quantity: 3n, price: 50n } ]; const result = compiled(sales); // Result: Map { "Widget" => 650n, "Gadget" => 500n } // Logs: "Gadget: $500" and "Widget: $650" ``` --- ## Types East is statically typed for speed and correctness, using **structural typing** for ease of use. All types (except functions) have a **total ordering**, enabling their use as Dict keys and Set elements, and allowing deep comparison operations. ### Type System Concepts - **`EastType`** - A type descriptor (e.g., `IntegerType`, `StringType`, `ArrayType`) - **`ValueTypeOf`** - The JavaScript runtime value for a type (e.g., `bigint` for `IntegerType`, `string` for `StringType`) - **`Expr`** - A typed expression that can be composed and compiled (e.g., `IntegerExpr`, `StringExpr`) **Key insight:** Most East functions accept either `Expr` OR `ValueTypeOf`, allowing you to mix expressions and raw values. | Type | JavaScript Value | Mutability | Description | |------|-----------------|------------|-------------| | **Primitive Types** | | | | | `NullType` | `null` | Immutable | Unit type (single value) | | `BooleanType` | `boolean` | Immutable | True or false | | `IntegerType` | `bigint` | Immutable | 64-bit signed integers | | `FloatType` | `number` | Immutable | IEEE 754 double-precision (distinguishes `-0.0` from `0.0`) | | `StringType` | `string` | Immutable | UTF-8 text | | `DateTimeType` | `Date` | Immutable | UTC timestamp with millisecond precision | | `BlobType` | `Uint8Array` | Immutable | Binary data | | **Compound Types** | | | | | `ArrayType` | `T[]` | **Mutable** | Ordered collection | | `SetType` | `Set` | **Mutable** | Sorted set (keys ordered by total ordering) | | `DictType` | `Map` | **Mutable** | Sorted dict (keys ordered by total ordering) | | `StructType` | `{...}` | Immutable | Product type (field order matters) | | `VariantType` | `variant` | Immutable | Sum type (cases sorted alphabetically) | | **Function Type** | | | | | `FunctionType` | Function | Immutable | First-class function (serializable as IR, not as data) | ### Important Notes - **Total ordering**: All types (even `Float` with `NaN`, `-0.0`) have a defined total ordering - **Immutable types**: Can be used as Dict keys and Set elements - Includes: All primitives, Blob, Struct, Variant - Excludes: Array, Set, Dict, Function - **Data types**: Can be serialized (excludes Function) - **Equality**: Deep structural equality for all types - Mutable types also support reference equality via `East.is()` - **Field/case order**: - Struct field order is significant for structural typing - Variant cases are automatically sorted alphabetically - **Operations marked ❗**: Can throw runtime errors --- ## East Namespace The `East` namespace is the main entry point for building East programs using a **fluent interface**. In East, you construct programs by building **expressions** - typed values that can be composed, transformed, and eventually compiled to executable code. Think of expressions as building blocks: `East.value(42n)` creates an integer expression, and you can call methods on it like `.add(1n)` to build larger expressions. The `East` namespace provides functions to create expressions from JavaScript values, perform comparisons, and access type-specific utilities. **Operations:** | Signature | Description | Example | |-----------|-------------|---------| | **Expression Creation** | | `value(val: ValueTypeOf): Expr` | Create expression from JavaScript value | `East.value(42n)` | | `value(val: Expr \| ValueTypeOf, type: T): Expr` | Create expression with explicit type | `East.value(x, IntegerType)` | | str\`...\`: StringExpr | String interpolation template | East.str\`Hello ${name}\` | | `print(expr: Expr): StringExpr` | Convert any expression to string representation | `East.print(x)` | | **Function Definition** | | `function(inputs: I, output: O, body: ($, ...args) => Expr \| value): FunctionExpr` | Define a function (see [Function](#function)) | `East.function([IntegerType], IntegerType, ($, x) => x.add(1n))` | | `compile(fn: FunctionExpr, platform: PlatformFunction[]): (...inputs) => ValueTypeOf` | Compile East function to executable JavaScript | `East.compile(myFunction, [log.implement(console.log)])` | | `platform(name: string, inputs: I, output: O): (...args) => ExprType` | Create callable helper for platform function | `const log = East.platform("log", [StringType], NullType)` | | **Comparisons** | | `equal(a: Expr, b: Expr \| ValueTypeOf): BooleanExpr` | Deep equality comparison | `East.equal(x, 10n)` | | `notEqual(a: Expr, b: Expr \| ValueTypeOf): BooleanExpr` | Deep inequality comparison | `East.notEqual(x, 0n)` | | `less(a: Expr, b: Expr \| ValueTypeOf): BooleanExpr` | Less than comparison (total ordering) | `East.less(x, 100n)` | | `lessEqual(a: Expr, b: Expr \| ValueTypeOf): BooleanExpr` | Less than or equal comparison | `East.lessEqual(x, y)` | | `greater(a: Expr, b: Expr \| ValueTypeOf): BooleanExpr` | Greater than comparison | `East.greater(x, 0n)` | | `greaterEqual(a: Expr, b: Expr \| ValueTypeOf): BooleanExpr` | Greater than or equal comparison | `East.greaterEqual(score, 50n)` | | `is(a: Expr, b: Expr \| ValueTypeOf): BooleanExpr` | Reference equality (for mutable types) | `East.is(arr1, arr2)` | | **Utilities** | | `min(a: Expr, b: Expr \| ValueTypeOf): Expr` | Minimum of two values (uses total ordering) | `East.min(x, 100n)` | | `max(a: Expr, b: Expr \| ValueTypeOf): Expr` | Maximum of two values (uses total ordering) | `East.max(x, 0n)` | | `clamp(x: Expr, min: Expr \| ValueTypeOf, max: Expr \| ValueTypeOf): Expr` | Clamp value between min and max | `East.clamp(x, 0n, 100n)` | --- ### Functions Functions in East are first-class values that can be defined, passed around, and called. They have concrete input and output types, and their bodies are written using a fluent interface. This section follows the workflow from [Quick Start](#quick-start): 1. Define platform functions 2. Define East functions 3. Compile and execute 4. (Optional) Serialize for transmission --- #### Defining Platform Functions East code runs in a sandboxed environment and can only interact with the outside world through **platform functions** that you explicitly provide. This ensures security and makes East code portable across different environments. **Creating platform functions:** Use `East.platform()` to create callable helpers that reference platform functions: ```typescript import { East, StringType, NullType, IntegerType } from "@elaraai/east"; // Define platform function helpers const log = East.platform("log", [StringType], NullType); // define the run-time implementation const platform = [ log.implement(console.log), ]; ``` **Defining and compiling an East function:** ```typescript const greet = East.function([StringType], NullType, ($, name) => { // Call platform function from East code $(log(East.str`Hello, ${name}!`)); $.return(null); }); const compiled = East.compile(greet, platform); compiled("Alice"); // Logs: "Hello, Alice!" ``` The compiled function has proper TypeScript types that match the East function signature. **Serializing an East function:** ```typescript // Serialize to JSON const ir = greet.toIR(); const jsonString = JSON.stringify(ir.toJSON()); // ... Send jsonString over network ... ``` Dynamically compile and execute the function on the remote environment ```typescript import { EastIR } from "@elaraai/east"; // define the remote environment run-time implementation const remote_platform = { log: (msg: string) => { console.log(`Result: ${msg}`) } }; // Deserialize and compile on remote environment const receivedIR = EastIR.fromJSON(JSON.parse("... jsonString value ... ")); // comile with a platform implementation on the remote environment! const remote_compiled = receivedIR.compile(platform); compiled("Bob"); // Logs: "Result: Hello, Bob!" ``` **Operations:** The first argument in an east function body (`$`) is a `BlockBuilder`, which is an entry point to scope specific operations. | Signature | Description | Example | |-----------|-------------|---------| | **Variables** | | `const(value: ValueTypeOf): Expr` | Declare immutable variable (infers type) | `const x = $.const(42n)` | | `const(value: Expr \| ValueTypeOf, type: T): Expr` | Declare immutable variable with explicit type | `const x = $.const(y, IntegerType)` | | `let(value: ValueTypeOf): Expr` | Declare mutable variable (infers type) | `const x = $.let(0n)` | | `let(value: Expr \| ValueTypeOf, type: T): Expr` | Declare mutable variable with explicit type | `const x = $.let(y, IntegerType)` | | `assign(variable: Expr, value: Expr \| ValueTypeOf): NullExpr` | Reassign mutable variable (must be declared with `$.let`) | `$.assign(x, 10n)` | | **Execution** | | `$(expr: Expr): Expr` | Execute expression (often for side effects), returns the expression | `$(arr.pushLast(42n))` | | `return(value: Expr \| ValueTypeOf): NeverExpr` | Early return from function | `$.return(x)` | | `error(message: StringExpr \| string, location?: Location): NeverExpr` | Throw error with message | `$.error("Invalid input")` | | **Control Flow** | | `if(condition: BooleanExpr \| boolean, body: ($) => void \| Expr): IfBuilder` | If statement (chain with `.elseIf()`, `.else()`) | `$.if(East.greater(x, 0n), $ => $.return(x))` | | `while(condition: BooleanExpr \| boolean, body: ($, label) => void \| Expr): NullExpr` | While loop | `$.while(East.greater(x, 0n), ($, label) => $.assign(x, x.subtract(1n)))` | | `for(array: ArrayExpr, body: ($, value, index, label) => void): NullExpr` | For loop over array elements | `$.for(arr, ($, val, i, label) => $(total.add(val)))` | | `for(set: SetExpr, body: ($, key, label) => void): NullExpr` | For loop over set keys | `$.for(s, ($, key, label) => $(arr.pushLast(key)))` | | `for(dict: DictExpr, body: ($, value, key, label) => void): NullExpr` | For loop over dict entries | `$.for(d, ($, val, key, label) => $(total.add(val)))` | | `break(label: Label): NeverExpr` | Break from loop (use label from loop body) | `$.break(label)` | | `continue(label: Label): NeverExpr` | Continue to next iteration (use label from loop body) | `$.continue(label)` | | `match(variant: VariantExpr, cases: { [K]: ($, data) => void \| Expr }): NullExpr` | Pattern match on variant (statement form) | `$.match(opt, { Some: ($, x) => $.return(x), None: $ => $.return(0n) })` | | `try(body: ($) => void \| Expr): TryCatchBuilder` | Try block (chain with `.catch(($, message, stack) => ...)`) | `$.try($ => arr.get(i)).catch(($, msg, stack) => $.return(0n))` | --- ## Expressions This section describes the operations available on each type's expressions. ### Boolean Boolean expressions support logical operations and conditional branching using the ternary-like `ifElse` method. **Example:** ```typescript import { East, IntegerType, BooleanType } from "@elaraai/east"; const validateOrder = East.function([IntegerType, IntegerType, BooleanType], BooleanType, ($, quantity, price, isPremium) => { // Create mutable boolean - starts as true, can be updated const isValid = $.let(true); // Alternative ways to create boolean values: // const isValid = $.let(true, BooleanType); // const isValid = $.let(East.value(true)); // const isValid = $.let(East.value(true, BooleanType)); // Check if order is too expensive without approval $.if(East.greater(price, 10000n), $ => { $.assign(isValid, false); }); // Check for invalid quantity or price $.if(East.less(quantity, 1n).or($ => East.less(price, 0n)), $ => { $.assign(isValid, false); }); // Combine checks: valid AND (premium OR large order) const finalCheck = isValid.and($ => isPremium.or($ => East.greater(quantity, 100n))); $.return(finalCheck); }); const compiled = East.compile(validateOrder, []); console.log(compiled(150n, 500n, true)); // true console.log(compiled(50n, 500n, false)); // false console.log(compiled(0n, 50n, false)); // false (invalid quantity) ``` **Operations:** | Signature | Description | Example | |-----------|-------------|---------| | **Short-Circuiting Operations** | | `not(): BooleanExpr` | Logical NOT | `x.not()` | | `and(y: ($) => BooleanExpr \| boolean): BooleanExpr` | Logical AND (short-circuit) | `x.and($ => y)` | | `or(y: ($) => BooleanExpr \| boolean): BooleanExpr` | Logical OR (short-circuit) | `x.or($ => y)` | | `ifElse(thenFn: ($) => any, elseFn: ($) => any): ExprType>` | Conditional expression (ternary) | `condition.ifElse($ => trueValue, $ => falseValue)` | | **Non-Short-Circuiting Operations** | | `bitAnd(y: BooleanExpr \| boolean): BooleanExpr` | Bitwise AND (always evaluates both) | `x.bitAnd(y)` | | `bitOr(y: BooleanExpr \| boolean): BooleanExpr` | Bitwise OR (always evaluates both) | `x.bitOr(y)` | | `bitXor(y: BooleanExpr \| boolean): BooleanExpr` | Bitwise XOR (always evaluates both) | `x.bitXor(y)` | --- ### Integer Integer expressions (`IntegerExpr`) represent 64-bit signed integers with standard arithmetic, mathematical functions, and rich formatting utilities in the standard library. **Example:** ```typescript import { East, IntegerType, StringType } from "@elaraai/east"; const formatRevenue = East.function([IntegerType], StringType, ($, revenue) => { // Create integer value with $.let() and East.value() (type inference) const price = $.let(East.value(47n)); const rounded = price.add(5n).divide(10n).multiply(10n); // Round to nearest 10 // Alternative: Create integer value with $.let() and East.value() with explicit type const bonus = $.let(East.value(1000n, IntegerType)); const quarterly = revenue.add(bonus).divide(12n).multiply(3n); $.return(East.str`Revenue: ${revenue}, Price: ${rounded}, Quarterly: ${quarterly}`); }); const compiled = East.compile(formatRevenue, []); console.log(compiled(1234567n)); // "Revenue: 1234567, Price: 50, Quarterly: 308641" ``` **Operations:** | Signature | Description | Example | |-----------|-------------|---------| | `negate(): IntegerExpr` | Unary negation | `x.negate()` | | `add(y: IntegerExpr \| bigint): IntegerExpr` | Addition | `x.add(5n)` | | `subtract(y: IntegerExpr \| bigint): IntegerExpr` | Subtraction | `x.subtract(3n)` | | `multiply(y: IntegerExpr \| bigint): IntegerExpr` | Multiplication | `x.multiply(2n)` | | `divide(y: IntegerExpr \| bigint): IntegerExpr` | Integer division (floored), `0 / 0 = 0` | `x.divide(10n)` | | `remainder(y: IntegerExpr \| bigint): IntegerExpr` | Remainder (floored modulo) | `x.remainder(3n)` | | `pow(y: IntegerExpr \| bigint): IntegerExpr` | Exponentiation | `x.pow(2n)` | | `abs(): IntegerExpr` | Absolute value | `x.abs()` | | `sign(): IntegerExpr` | Sign (-1, 0, or 1) | `x.sign()` | | `log(base: IntegerExpr \| bigint): IntegerExpr` | Logarithm (floored, custom base) | `x.log(10n)` | | `toFloat(): FloatExpr` | Convert to float (may be approximate) | `x.toFloat()` | **Standard Library:** See [STDLIB.md](./STDLIB.md#integer) for additional formatting and rounding functions. --- ### Float Float expressions (`FloatExpr`) represent IEEE 754 double-precision floating-point numbers with standard arithmetic and mathematical functions. **Example:** ```typescript const calculateCircle = East.function([FloatType], FloatType, ($, radius) => { // Create float value with $.let() and East.value() (type inference) const pi = $.let(East.value(3.14159)); const area = pi.multiply(radius.pow(2.0)); // Alternative: Create float value with $.let() and East.value() with explicit type const scaleFactor = $.let(East.value(1.5, FloatType)); const scaled = area.multiply(scaleFactor); $.return(scaled); }); const compiled = East.compile(calculateCircle, []); console.log(compiled(10.0)); // 471.2385 ``` **Operations:** | Signature | Description | Example | |-----------|-------------|---------| | `negate(): FloatExpr` | Unary negation | `x.negate()` | | `add(y: FloatExpr \| number): FloatExpr` | Addition | `x.add(2.5)` | | `subtract(y: FloatExpr \| number): FloatExpr` | Subtraction | `x.subtract(1.5)` | | `multiply(y: FloatExpr \| number): FloatExpr` | Multiplication | `x.multiply(2.0)` | | `divide(y: FloatExpr \| number): FloatExpr` | Division, `0.0 / 0.0 = NaN` | `x.divide(2.0)` | | `remainder(y: FloatExpr \| number): FloatExpr` | Remainder (floored modulo) | `x.remainder(3.0)` | | `pow(y: FloatExpr \| number): FloatExpr` | Exponentiation | `x.pow(2.0)` | | `abs(): FloatExpr` | Absolute value | `x.abs()` | | `sign(): FloatExpr` | Sign (-1, 0, or 1) | `x.sign()` | | `sqrt(): FloatExpr` | Square root | `x.sqrt()` | | `exp(): FloatExpr` | Exponential (e^x) | `x.exp()` | | `log(): FloatExpr` | Natural logarithm | `x.log()` | | `sin(): FloatExpr` | Sine | `x.sin()` | | `cos(): FloatExpr` | Cosine | `x.cos()` | | `tan(): FloatExpr` | Tangent | `x.tan()` | | `toInteger(): IntegerExpr` **❗** | Convert to integer (must be exact, errors otherwise) | `x.toInteger()` | --- ### String String expressions (`StringExpr`) provide text manipulation, pattern matching, encoding, and parsing capabilities. **Example:** ```typescript const processEmail = East.function([StringType], StringType, ($, email) => { const atIndex = email.indexOf("@"); const domain = email.substring(atIndex.add(1n), email.length()); // Create string value with $.let() and East.value() (type inference) const greeting = $.let(East.value("Hello")); // Alternative: Create string value with $.let() and East.value() with explicit type const separator = $.let(East.value(" ", StringType)); const message = greeting.concat(separator).concat(domain); $.return(message.upperCase()); }); const compiled = East.compile(processEmail, []); console.log(compiled("user@example.com")); // "HELLO EXAMPLE.COM" ``` **Operations:** | Signature | Description | Example | |-----------|-------------|---------| | **Manipulation** | | `concat(other: StringExpr \| string): StringExpr` | Concatenate strings | `str.concat(" world")` | | `repeat(count: IntegerExpr \| bigint): StringExpr` | Repeat string n times | `str.repeat(3n)` | | `substring(from: IntegerExpr \| bigint, to: IntegerExpr \| bigint): StringExpr` | Extract substring | `str.substring(0n, 5n)` | | `upperCase(): StringExpr` | Convert to uppercase | `str.upperCase()` | | `lowerCase(): StringExpr` | Convert to lowercase | `str.lowerCase()` | | `trim(): StringExpr` | Remove whitespace from both ends | `str.trim()` | | `trimStart(): StringExpr` | Remove whitespace from start | `str.trimStart()` | | `trimEnd(): StringExpr` | Remove whitespace from end | `str.trimEnd()` | | `split(separator: StringExpr \| string): ArrayExpr` | Split into array | `str.split(",")` | | `replace(search: StringExpr \| string, replacement: StringExpr \| string): StringExpr` | Replace first occurrence | `str.replace("old", "new")` | | **Query** | | `length(): IntegerExpr` | Get string length (UTF-16 code units) | `str.length()` | | `startsWith(prefix: StringExpr \| string): BooleanExpr` | Test if starts with prefix | `str.startsWith("Hello")` | | `endsWith(suffix: StringExpr \| string): BooleanExpr` | Test if ends with suffix | `str.endsWith(".txt")` | | `contains(substring: StringExpr \| string): BooleanExpr` | Test if contains substring | `str.contains("world")` | | `contains(regex: RegExp): BooleanExpr` | Test if matches regex | `str.contains(/[0-9]+/)` | | `indexOf(substring: StringExpr \| string): IntegerExpr` | Find index of substring (-1 if not found) | `str.indexOf("world")` | | `indexOf(regex: RegExp): IntegerExpr` | Find index of regex match (-1 if not found) | `str.indexOf(/[0-9]+/)` | | **Encoding** | | `encodeUtf8(): BlobExpr` | Encode as UTF-8 bytes | `str.encodeUtf8()` | | `encodeUtf16(): BlobExpr` | Encode as UTF-16 bytes (little-endian with BOM) | `str.encodeUtf16()` | | **Parsing** | | `parse(type: T): ExprType` **❗** | Parse string to given type (fallible) | `str.parse(IntegerType)` | | `parseJson(type: T): ExprType` **❗** | Parse JSON to given type (fallible) | `str.parseJson(IntegerType)` | **Standard Library:** See [STDLIB.md](./STDLIB.md#string) for error formatting utilities. --- ### DateTime DateTime expressions (`DateTimeExpr`) represent UTC timestamps with millisecond precision, supporting component access, arithmetic, and rounding operations. **Example:** ```typescript const addBusinessDays = East.function([DateTimeType, IntegerType], DateTimeType, ($, startDate, days) => { // Create datetime value with $.let() and East.value() (type inference) const baseDate = $.let(East.value(new Date("2025-01-01T00:00:00Z"))); // Alternative: Create datetime value with $.let() and East.value() with explicit type const epoch = $.let(East.value(new Date("1970-01-01T00:00:00Z"), DateTimeType)); const result = startDate.addDays(days); const rounded = East.DateTime.roundDownDay(result, 1n); $.return(rounded); }); const compiled = East.compile(addBusinessDays, []); const start = new Date("2025-10-10T14:30:00Z"); const end = compiled(start, 7n); console.log(end); // 2025-10-17 00:00:00 UTC ``` **Operations:** | Signature | Description | Example | |-----------|-------------|---------| | **Component Access** | | `getYear(): IntegerExpr` | Get year | `date.getYear()` | | `getMonth(): IntegerExpr` | Get month (1-12) | `date.getMonth()` | | `getDayOfMonth(): IntegerExpr` | Get day of month (1-31) | `date.getDayOfMonth()` | | `getDayOfWeek(): IntegerExpr` | Get day of week (0-6, Sunday=0) | `date.getDayOfWeek()` | | `getHour(): IntegerExpr` | Get hour (0-23) | `date.getHour()` | | `getMinute(): IntegerExpr` | Get minute (0-59) | `date.getMinute()` | | `getSecond(): IntegerExpr` | Get second (0-59) | `date.getSecond()` | | `getMillisecond(): IntegerExpr` | Get millisecond (0-999) | `date.getMillisecond()` | | **Arithmetic** | | `addMilliseconds(ms: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Add milliseconds (int or float) | `date.addMilliseconds(1000n)` | | `subtractMilliseconds(ms: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Subtract milliseconds | `date.subtractMilliseconds(500n)` | | `addSeconds(s: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Add seconds | `date.addSeconds(60n)` | | `subtractSeconds(s: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Subtract seconds | `date.subtractSeconds(30n)` | | `addMinutes(m: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Add minutes | `date.addMinutes(10n)` | | `subtractMinutes(m: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Subtract minutes | `date.subtractMinutes(5n)` | | `addHours(h: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Add hours | `date.addHours(2n)` | | `subtractHours(h: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Subtract hours | `date.subtractHours(1n)` | | `addDays(d: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Add days | `date.addDays(7n)` | | `subtractDays(d: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Subtract days | `date.subtractDays(1n)` | | `addWeeks(w: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Add weeks | `date.addWeeks(2n)` | | `subtractWeeks(w: IntegerExpr \| FloatExpr \| bigint \| number): DateTimeExpr` | Subtract weeks | `date.subtractWeeks(1n)` | | **Duration** | | `durationMilliseconds(other: DateTimeExpr \| Date): IntegerExpr` | Duration in milliseconds (positive if other > this) | `date.durationMilliseconds(otherDate)` | | `durationSeconds(other: DateTimeExpr \| Date): FloatExpr` | Duration in seconds | `date.durationSeconds(otherDate)` | | `durationMinutes(other: DateTimeExpr \| Date): FloatExpr` | Duration in minutes | `date.durationMinutes(otherDate)` | | `durationHours(other: DateTimeExpr \| Date): FloatExpr` | Duration in hours | `date.durationHours(otherDate)` | | `durationDays(other: DateTimeExpr \| Date): FloatExpr` | Duration in days | `date.durationDays(otherDate)` | | `durationWeeks(other: DateTimeExpr \| Date): FloatExpr` | Duration in weeks | `date.durationWeeks(otherDate)` | | **Conversion** | | `toEpochMilliseconds(): IntegerExpr` | Milliseconds since Unix epoch | `date.toEpochMilliseconds()` | **Standard Library:** See [STDLIB.md](./STDLIB.md#datetime) for construction from components and additional rounding functions. --- ### Blob Blob expressions (`BlobExpr`) represent immutable binary data with byte access and encoding/decoding operations. **Example:** ```typescript const encodeData = East.function([IntegerType], BlobType, ($, value) => { const encoded = East.Blob.encodeBeast(value, 'v2'); // Create blob value with $.let() and East.value() (type inference) const header = $.let(East.value(new Uint8Array([0x42, 0x45]))); // Alternative: Create blob value with $.let() and East.value() with explicit type const footer = $.let(East.value(new Uint8Array([0xFF]), BlobType)); $.return(encoded); }); const compiled = East.compile(encodeData, []); const blob = compiled(42n); console.log(blob); // Uint8Array containing BEAST-encoded 42n ``` **Operations:** | Signature | Description | Example | |-----------|-------------|---------| | **Base Operations** | | `size(): IntegerExpr` | Get size in bytes | `blob.size()` | | `getUint8(offset: IntegerExpr \| bigint): IntegerExpr` **❗** | Get byte at offset (0-255, errors if out of bounds) | `blob.getUint8(0n)` | | `decodeUtf8(): StringExpr` **❗** | Decode as UTF-8 (fallible) | `blob.decodeUtf8()` | | `decodeUtf16(): StringExpr` **❗** | Decode as UTF-16 (fallible) | `blob.decodeUtf16()` | | `decodeBeast(type: T, version: 'v1' \| 'v2' = 'v1'): ExprType` **❗** | Decode binary BEAST format (v1 or v2, fallible) | `blob.decodeBeast(IntegerType, 'v2')` | **Standard Library:** See [STDLIB.md](./STDLIB.md#blob) for BEAST encoding utilities. --- ### Array Array expressions (`ArrayExpr`) represent mutable, ordered collections with rich functional operations, mutations, and conversions. **Example:** ```typescript const processPrices = East.function([ArrayType(IntegerType)], IntegerType, ($, prices) => { const doubled = prices.map(($, x, i) => x.multiply(2n)); const filtered = doubled.filter(($, x, i) => East.greater(x, 100n)); const sum = filtered.sum(); // Create array value with $.let() and East.value() (type inference) const bonuses = $.let(East.value([10n, 20n, 30n])); // Alternative: Create array value with $.let() and East.value() with explicit type const fees = $.let(East.value([5n, 10n], ArrayType(IntegerType))); $(prices.pushLast(999n)); // Mutate original array $.return(sum); }); const compiled = East.compile(processPrices, []); console.log(compiled([50n, 60n, 70n])); // 260n (60*2 + 70*2) ``` **Operations:** | Signature | Description | Example | |-----------|-------------|---------| | **Read Operations** | | `size(): IntegerExpr` | Get array length | `array.size()` | | `has(index: IntegerExpr \| bigint): BooleanExpr` | Check if index is valid (0 ≤ index < size) | `array.has(5n)` | | `get(index: IntegerExpr \| bigint): ExprType` **❗** | Get element (errors if out of bounds) | `array.get(0n)` | | `get(index: IntegerExpr \| bigint, defaultFn: FunctionType<[IntegerType], V>): ExprType` | Get element or compute default | `array.get(10n, East.function([IntegerType], IntegerType, ($, i) => 0n))` | | `tryGet(index: IntegerExpr \| bigint): OptionExpr` | Safe get returning Option | `array.tryGet(0n)` | | **Mutation Operations** | | `update(index: IntegerExpr \| bigint, value: ExprType \| ValueTypeOf): NullExpr` **❗** | Replace element (errors if out of bounds) | `array.update(0n, 42n)` | | `merge(index: IntegerExpr \| bigint, value: Expr, updateFn: FunctionType<[V, T2, IntegerType], V>): ExprType` | Merge value with existing element using function | `array.merge(0n, 5n, ($, old, new, i) => old.add(new))` | | `pushLast(value: ExprType \| ValueTypeOf): NullExpr` | Append to end | `array.pushLast(42n)` | | `popLast(): ExprType` **❗** | Remove from end (errors if empty) | `array.popLast()` | | `pushFirst(value: ExprType \| ValueTypeOf): NullExpr` | Prepend to start | `array.pushFirst(42n)` | | `popFirst(): ExprType` **❗** | Remove from start (errors if empty) | `array.popFirst()` | | `append(other: ArrayExpr): NullExpr` | Append all elements from other array (mutating) | `array.append(otherArray)` | | `prepend(other: ArrayExpr): NullExpr` | Prepend all elements from other array (mutating) | `array.prepend(otherArray)` | | `mergeAll(other: ArrayExpr, mergeFn: FunctionType<[V, T2, IntegerType], V>): NullExpr` | Merge all elements from another array using function | `array.mergeAll(other, ($, cur, new, i) => cur.add(new))` | | `clear(): NullExpr` | Remove all elements | `array.clear()` | | `sortInPlace(byFn?: FunctionType<[V], DataType>): NullExpr` | Sort in-place | `array.sortInPlace()` | | `reverseInPlace(): NullExpr` | Reverse in-place | `array.reverseInPlace()` | | **Functional Operations (Immutable)** | | `copy(): ArrayExpr` | Shallow copy | `array.copy()` | | `slice(start: IntegerExpr \| bigint, end: IntegerExpr \| bigint): ArrayExpr` | Extract subarray | `array.slice(0n, 10n)` | | `concat(other: ArrayExpr): ArrayExpr` | Concatenate into new array | `array.concat(otherArray)` | | `getKeys(keys: ArrayExpr, onMissing?: FunctionType<[IntegerType], V>): ArrayExpr` | Get values at given indices | `array.getKeys(indices, East.function([IntegerType], IntegerType, ($, i) => 0n))` | | `sort(byFn?: FunctionType<[V], DataType>): ArrayExpr` | Return sorted copy | `array.sort()` | | `reverse(): ArrayExpr` | Return reversed copy | `array.reverse()` | | `isSorted(byFn?: FunctionType<[V], DataType>): BooleanExpr` | Check if sorted | `array.isSorted()` | | `findSortedFirst(value: T2, byFn?: FunctionType<[V], TypeOf>): IntegerExpr` | Binary search for first element ≥ value | `array.findSortedFirst(42n)` | | `findSortedLast(value: T2, byFn?: FunctionType<[V], TypeOf>): IntegerExpr` | Binary search for last element ≤ value | `array.findSortedLast(42n)` | | `findSortedRange(value: T2, byFn?: FunctionType<[V], TypeOf>): StructExpr<{start, end}>` | Binary search for range of elements equal to value | `array.findSortedRange(42n)` | | `map(fn: FunctionType<[V, IntegerType], U>): ArrayExpr` | Transform each element | `array.map(($, x, i) => x.multiply(2n))` | | `filter(predicate: FunctionType<[V, IntegerType], BooleanType>): ArrayExpr` | Keep matching elements | `array.filter(($, x, i) => East.greater(x, 0n))` | | `filterMap(fn: FunctionType<[V, IntegerType], OptionType>): ArrayExpr` | Filter and map using Option | `array.filterMap(($, x, i) => East.greater(x, 0n) ? East.some(x.multiply(2n)) : East.none())` | | `firstMap(fn: FunctionType<[V, IntegerType], OptionType>): OptionExpr` | Map until first successful result (returns Option) | `array.firstMap(($, x, i) => East.greater(x, 0n) ? East.some(x) : East.none())` | | `findFirst(value: V): OptionExpr` | Find index of first matching element | `array.findFirst(42n)` | | `findFirst(value: T2, by: FunctionType<[V, IntegerType], T2>): OptionExpr` | Find first match with projection | `array.findFirst("active", ($, u, i) => u.status)` | | `findAll(value: V): ArrayExpr` | Find all indices of matching elements | `array.findAll(42n)` | | `findAll(value: T2, by: FunctionType<[V, IntegerType], T2>): ArrayExpr` | Find all matches with projection | `array.findAll("active", ($, u, i) => u.status)` | | `forEach(fn: FunctionType<[V, IntegerType], any>): NullExpr` | Execute function for each element | `array.forEach(($, x, i) => $(total.add(x)))` | | **Reduction Operations** | | `reduce(combineFn: FunctionType<[T, V, IntegerType], T>, init: T): ExprType` | Fold/reduce with initial value | `array.reduce(($, acc, x, i) => acc.add(x), 0n)` | | `reduce(combineFn: FunctionType<[V, V, IntegerType], V>): ExprType` | Fold/reduce without initial (uses first element) | `array.reduce(($, acc, x, i) => acc.add(x))` | | `mapReduce(mapFn: FunctionType<[V, IntegerType], U>, combineFn: FunctionType<[T, U, IntegerType], T>, init: T): ExprType` | Map then reduce with initial value | `array.mapReduce(($, x, i) => x.multiply(2n), ($, acc, x, i) => acc.add(x), 0n)` | | `mapReduce(mapFn: FunctionType<[V, IntegerType], U>, combineFn: FunctionType<[U, U, IntegerType], U>): ExprType` | Map then reduce without initial | `array.mapReduce(($, x, i) => x.multiply(2n), ($, acc, x, i) => acc.add(x))` | | `every(predicate?: FunctionType<[V, IntegerType], BooleanType>): BooleanExpr` | True if all match predicate | `array.every()` | | `some(predicate?: FunctionType<[V, IntegerType], BooleanType>): BooleanExpr` | True if any match predicate | `array.some()` | | `sum(): IntegerExpr \| FloatExpr` | Sum of numeric array | `array.sum()` | | `sum(fn: FunctionType<[V, IntegerType], IntegerType \| FloatType>): IntegerExpr \| FloatExpr` | Sum with projection | `array.sum(($, x, i) => x.multiply(2n))` | | `mean(): FloatExpr` | Mean (NaN if empty) | `array.mean()` | | `mean(fn: FunctionType<[V, IntegerType], IntegerType \| FloatType>): FloatExpr` | Mean with projection | `array.mean(($, x, i) => x.toFloat())` | | `findMaximum(by?: FunctionType<[V, IntegerType], any>): OptionExpr` | Find index of maximum element | `array.findMaximum()` | | `findMinimum(by?: FunctionType<[V, IntegerType], any>): OptionExpr` | Find index of minimum element | `array.findMinimum()` | | `maximum(by?: FunctionType<[V, IntegerType], any>): ExprType` | Get maximum element value (errors if empty) | `array.maximum()` | | `minimum(by?: FunctionType<[V, IntegerType], any>): ExprType` | Get minimum element value (errors if empty) | `array.minimum()` | | **Conversion Operations** | | `stringJoin(separator: StringExpr \| string): StringExpr` | Join string array (only for `ArrayType`) | `array.stringJoin(", ")` | | `toSet(keyFn?: FunctionType<[V, IntegerType], K>): SetExpr` | Convert to set (ignoring duplicates) | `array.toSet()` | | `toDict(keyFn?: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], U>, onConflictFn?: FunctionType<[U, U, K], U>): DictExpr` | Convert to dict | `array.toDict(($, x, i) => i)` | | `flatMap(fn?: FunctionType<[V, IntegerType], ArrayType>): ArrayExpr` | Flatten array of arrays | `array.flatMap()` | | `flattenToSet(fn?: FunctionType<[V, IntegerType], SetType>): SetExpr` | Flatten to set | `array.flattenToSet()` | | `flattenToDict(fn?: FunctionType<[V, IntegerType], DictType>, onConflictFn?: FunctionType<[U, U, K], U>): DictExpr` | Flatten to dict | `array.flattenToDict()` | | **Grouping Operations** | | `groupReduce(keyFn: FunctionType<[V, IntegerType], K>, valueFn: FunctionType<[V, IntegerType], U>, initFn: FunctionType<[K], T>, reduceFn: FunctionType<[T, U, K], T>): DictExpr` | Group by key and reduce groups | `array.groupReduce(($, x, i) => x.remainder(2n), ($, x, i) => x, ($, key) => 0n, ($, acc, val, key) => acc.add(val))` | | `groupSize(keyFn?: FunctionType<[V, IntegerType], K>): DictExpr` | Count elements in each group | `array.groupSize(($, x, i) => x.remainder(2n))` | | `groupEvery(keyFn: FunctionType<[V, IntegerType], K>, predFn: FunctionType<[V, IntegerType], BooleanType>): DictExpr` | Check if all elements in each group match predicate | `array.groupEvery(($, x, i) => x.remainder(2n), ($, x, i) => East.greater(x, 0n))` | | `groupSome(keyFn: FunctionType<[V, IntegerType], K>, predFn: FunctionType<[V, IntegerType], BooleanType>): DictExpr` | Check if any element in each group matches predicate | `array.groupSome(($, x, i) => x.remainder(2n), ($, x, i) => East.greater(x, 10n))` | | `groupFindFirst(keyFn: FunctionType<[V, IntegerType], K>, value: T2, projFn?: FunctionType<[V, IntegerType], T2>): DictExpr>` | Find first matching index in each group | `array.groupFindFirst(($, x, i) => x.remainder(2n), 42n)` | | `groupFindAll(keyFn: FunctionType<[V, IntegerType], K>, value: T2, projFn?: FunctionType<[V, IntegerType], T2>): DictExpr>` | Find all matching indices in each group | `array.groupFindAll(($, x, i) => x.remainder(2n), 42n)` | | `groupFindMinimum(keyFn: FunctionType<[V, IntegerType], K>, byFn?: FunctionType<[V, IntegerType], any>): DictExpr` | Find index of minimum in each group | `array.groupFindMinimum(($, x, i) => x.remainder(2n))` | | `groupFindMaximum(keyFn: FunctionType<[V, IntegerType], K>, byFn?: FunctionType<[V, IntegerType], any>): DictExpr` | Find index of maximum in each group | `array.groupFindMaximum(($, x, i) => x.remainder(2n))` | | `groupSum(keyFn: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], IntegerType \| FloatType>): DictExpr` | Sum values in each group | `array.groupSum(($, x, i) => x.remainder(2n))` | | `groupMean(keyFn: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], IntegerType \| FloatType>): DictExpr` | Mean of values in each group | `array.groupMean(($, x, i) => x.remainder(2n))` | | `groupMinimum(keyFn: FunctionType<[V, IntegerType], K>, byFn?: FunctionType<[V, IntegerType], any>): DictExpr` | Get minimum value in each group | `array.groupMinimum(($, x, i) => x.remainder(2n))` | | `groupMaximum(keyFn: FunctionType<[V, IntegerType], K>, byFn?: FunctionType<[V, IntegerType], any>): DictExpr` | Get maximum value in each group | `array.groupMaximum(($, x, i) => x.remainder(2n))` | | `groupToArrays(keyFn: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], U>): DictExpr>` | Collect elements into arrays by group | `array.groupToArrays(($, x, i) => x.remainder(2n))` | | `groupToSets(keyFn: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], U>): DictExpr>` | Collect elements into sets by group | `array.groupToSets(($, x, i) => x.remainder(2n))` | | `groupToDicts(keyFn: FunctionType<[V, IntegerType], K>, keyFn2: FunctionType<[V, IntegerType], K2>, valueFn?: FunctionType<[V, IntegerType], U>, combineFn?: FunctionType<[U, U, K2], U>): DictExpr>` | Collect elements into nested dicts | `array.groupToDicts(($, x, i) => x.remainder(2n), ($, x, i) => i, ($, x, i) => x)` | **Standard Library:** See [STDLIB.md](./STDLIB.md#array) for array generation functions (range, linspace, generate). --- ### Set Set expressions (`SetExpr`) represent mutable, sorted collections of unique keys with set operations and functional transformations. **Example:** ```typescript const processSet = East.function([SetType(IntegerType), SetType(IntegerType)], IntegerType, ($, setA, setB) => { const unionSet = setA.union(setB); const intersection = setA.intersection(setB); // Create set value with $.let() and East.value() (type inference) const extras = $.let(East.value(new Set([100n, 200n]))); // Alternative: Create set value with $.let() and East.value() with explicit type const defaults = $.let(East.value(new Set([1n, 2n]), SetType(IntegerType))); $(setA.insert(999n)); // Mutate original set const total = unionSet.sum(); $.return(total); }); const compiled = East.compile(processSet, []); const a = new Set([1n, 2n, 3n]); const b = new Set([3n, 4n, 5n]); console.log(compiled(a, b)); // 15n (1+2+3+4+5) ``` **Operations:** | Signature | Description | Example | |-----------|-------------|---------| | **Read Operations** | | `size(): IntegerExpr` | Get set size | `set.size()` | | `has(key: ExprType \| ValueTypeOf): BooleanExpr` | Check if key exists | `set.has(42n)` | | **Mutation Operations** | | `insert(key: ExprType \| ValueTypeOf): NullExpr` **❗** | Insert key (errors if exists) | `set.insert(42n)` | | `tryInsert(key: ExprType \| ValueTypeOf): BooleanExpr` | Safe insert (returns success) | `set.tryInsert(42n)` | | `delete(key: ExprType \| ValueTypeOf): NullExpr` **❗** | Delete key (errors if missing) | `set.delete(42n)` | | `tryDelete(key: ExprType \| ValueTypeOf): BooleanExpr` | Safe delete (returns success) | `set.tryDelete(42n)` | | `clear(): NullExpr` | Remove all elements | `set.clear()` | | `unionInPlace(other: SetExpr): NullExpr` | Union in-place (mutating) | `set.unionInPlace(otherSet)` | | **Set Operations** | | `copy(): SetExpr` | Shallow copy | `set.copy()` | | `union(other: SetExpr): SetExpr` | Return union | `set.union(otherSet)` | | `intersection(other: SetExpr): SetExpr` | Return intersection | `set.intersection(otherSet)` | | `difference(other: SetExpr): SetExpr` | Return difference (in this, not in other) | `set.difference(otherSet)` | | `symmetricDifference(other: SetExpr): SetExpr` | Return symmetric difference | `set.symmetricDifference(otherSet)` | | `isSubsetOf(other: SetExpr): BooleanExpr` | Check if subset | `set.isSubsetOf(otherSet)` | | `isSupersetOf(other: SetExpr): BooleanExpr` | Check if superset | `set.isSupersetOf(otherSet)` | | `isDisjointFrom(other: SetExpr): BooleanExpr` | Check if no common elements | `set.isDisjointFrom(otherSet)` | | **Functional Operations (Immutable)** | | `filter(predicate: FunctionType<[K], BooleanType>): SetExpr` | Keep matching elements | `set.filter(($, key) => East.greater(key, 0n))` | | `filterMap(fn: FunctionType<[K], OptionType>): ArrayExpr` | Filter and map using Option | `set.filterMap(($, key) => East.greater(key, 0n) ? East.some(key) : East.none())` | | `firstMap(fn: FunctionType<[K], OptionType>): OptionExpr` | Map until first successful result | `set.firstMap(($, key) => East.greater(key, 10n) ? East.some(key) : East.none())` | | `forEach(fn: FunctionType<[K], any>): NullExpr` | Execute function for each element | `set.forEach(($, key) => $(arr.pushLast(key)))` | | `map(fn: FunctionType<[K], V>): DictExpr` | Map to dict (keys unchanged, values from fn) | `set.map(($, key) => key.multiply(2n))` | | `reduce(fn: FunctionType<[T, K], T>, init: T): ExprType` | Fold/reduce over set | `set.reduce(($, acc, key) => acc.add(key), 0n)` | | `every(fn?: FunctionType<[K], BooleanType>): BooleanExpr` | True if all match | `set.every()` | | `some(fn?: FunctionType<[K], BooleanType>): BooleanExpr` | True if any match | `set.some()` | | `sum(): IntegerExpr \| FloatExpr` | Sum of numeric set | `set.sum()` | | `sum(fn: FunctionType<[K], IntegerType \| FloatType>): IntegerExpr \| FloatExpr` | Sum with projection | `set.sum(($, key) => key.multiply(2n))` | | `mean(): FloatExpr` | Mean (NaN if empty) | `set.mean()` | | `mean(fn: FunctionType<[K], IntegerType \| FloatType>): FloatExpr` | Mean with projection | `set.mean(($, key) => key.toFloat())` | | **Conversion Operations** | | `toArray(fn?: FunctionType<[K], V>): ArrayExpr` | Convert to array | `set.toArray()` | | `toSet(keyFn?: FunctionType<[K], U>): SetExpr` | Convert to new set (ignoring duplicates) | `set.toSet(($, key) => key.multiply(2n))` | | `toDict(keyFn?: FunctionType<[K], K2>, valueFn?: FunctionType<[K], V>, onConflictFn?: FunctionType<[V, V, K2], V>): DictExpr` | Convert to dict | `set.toDict()` | | `flattenToArray(fn: FunctionType<[K], ArrayType>): ArrayExpr` | Flatten to array | `set.flattenToArray(($, key) => East.Array.range(0n, key))` | | `flattenToSet(fn: FunctionType<[K], SetType>): SetExpr` | Flatten to set | `set.flattenToSet(($, key) => otherSetDict.get(key))` | | `flattenToDict(fn: FunctionType<[K], DictType>, onConflictFn?: FunctionType<[V, V, K2], V>): DictExpr` | Flatten to dict | `set.flattenToDict(($, key) => nestedDicts.get(key), ($, v1, v2, k) => v1.add(v2))` | | **Grouping Operations** | | `groupReduce(keyFn: FunctionType<[K], K2>, valueFn: FunctionType<[K], V>, initFn: FunctionType<[K2], T>, reduceFn: FunctionType<[T, V, K2], T>): DictExpr` | Group by key and reduce groups | `set.groupReduce(($, key) => key.remainder(2n), ($, key) => key, ($, grp) => 0n, ($, acc, val, grp) => acc.add(val))` | | `groupSize(keyFn?: FunctionType<[K], K2>): DictExpr` | Count elements in each group | `set.groupSize(($, key) => key.remainder(2n))` | | `groupEvery(keyFn: FunctionType<[K], K2>, predFn: FunctionType<[K], BooleanType>): DictExpr` | Check if all elements in each group match predicate | `set.groupEvery(($, key) => key.remainder(2n), ($, key) => East.greater(key, 0n))` | | `groupSome(keyFn: FunctionType<[K], K2>, predFn: FunctionType<[K], BooleanType>): DictExpr` | Check if any element in each group matches predicate | `set.groupSome(($, key) => key.remainder(2n), ($, key) => East.greater(key, 10n))` | | `groupSum(keyFn: FunctionType<[K], K2>, valueFn?: FunctionType<[K], IntegerType \| FloatType>): DictExpr` | Sum values in each group | `set.groupSum(($, key) => key.remainder(2n))` | | `groupMean(keyFn: FunctionType<[K], K2>, valueFn?: FunctionType<[K], IntegerType \| FloatType>): DictExpr` | Mean of values in each group | `set.groupMean(($, key) => key.remainder(2n))` | | `groupToArrays(keyFn: FunctionType<[K], K2>, valueFn?: FunctionType<[K], V>): DictExpr>` | Collect elements into arrays by group | `set.groupToArrays(($, key) => key.remainder(2n))` | | `groupToSets(keyFn: FunctionType<[K], K2>, valueFn?: FunctionType<[K], U>): DictExpr>` | Collect elements into sets by group | `set.groupToSets(($, key) => key.remainder(2n))` | | `groupToDicts(keyFn: FunctionType<[K], K2>, keyFn2: FunctionType<[K], K3>, valueFn?: FunctionType<[K], V>, combineFn?: FunctionType<[V, V, K3], V>): DictExpr>` | Collect elements into nested dicts | `set.groupToDicts(($, key) => key.remainder(2n), ($, key) => key, ($, key) => key)` | **Standard Library:** See [STDLIB.md](./STDLIB.md#set) for set generation functions. --- ### Dict Dict expressions (`DictExpr`) represent mutable, sorted key-value mappings with functional operations and flexible merging strategies. **Example:** ```typescript import { East, DictType, StringType, IntegerType } from "@elaraai/east"; const inventoryLookup = East.function([DictType(StringType, IntegerType), StringType], IntegerType, ($, inventory, item) => { const count = inventory.get(item, East.function([StringType], IntegerType, ($, key) => 0n)); // Create dict value with $.let() and East.value() (type inference) const defaults = $.let(East.value(new Map([["apple", 0n], ["banana", 0n]]))); // Alternative: Create dict value with $.let() and East.value() with explicit type const prices = $.let(East.value(new Map([["apple", 5n]]), DictType(StringType, IntegerType))); $(inventory.merge("widget", 5n, ($, old, newVal, key) => old.add(newVal))); const total = inventory.sum(); $.return(count); }); const compiled = East.compile(inventoryLookup, []); const inventory = new Map([["apple", 10n], ["banana", 5n]]); console.log(compiled(inventory, "apple")); // 10n ``` **Operations:** | Signature | Description | Example | |-----------|-------------|---------| | **Read Operations** | | `size(): IntegerExpr` | Get dict size | `dict.size()` | | `has(key: ExprType \| ValueTypeOf): BooleanExpr` | Check if key exists | `dict.has("foo")` | | `get(key: ExprType \| ValueTypeOf): ExprType` **❗** | Get value (errors if missing) | `dict.get("foo")` | | `get(key: ExprType \| ValueTypeOf, defaultFn: FunctionType<[K], V>): ExprType` | Get value or compute default | `dict.get("foo", East.function([StringType], IntegerType, ($, key) => 0n))` | | `tryGet(key: ExprType \| ValueTypeOf): OptionExpr` | Safe get returning Option | `dict.tryGet("foo")` | | `keys(): SetExpr` | Get all keys as set | `dict.keys()` | | `getKeys(keys: SetExpr, onMissing?: FunctionType<[K], V>): DictExpr` | Get values for given keys | `dict.getKeys(keySet, East.function([StringType], IntegerType, ($, key) => 0n))` | | **Mutation Operations** | | `insert(key: ExprType \| ValueTypeOf, value: ExprType \| ValueTypeOf): NullExpr` **❗** | Insert (errors if exists) | `dict.insert("foo", 42n)` | | `insertOrUpdate(key: ExprType \| ValueTypeOf, value: ExprType \| ValueTypeOf): NullExpr` | Insert or update (idempotent) | `dict.insertOrUpdate("foo", 42n)` | | `update(key: ExprType \| ValueTypeOf, value: ExprType \| ValueTypeOf): NullExpr` **❗** | Update existing (errors if missing) | `dict.update("foo", 100n)` | | `merge(key: ExprType \| ValueTypeOf, value: T2, updateFn: FunctionType<[V, T2, K], V>, initialFn?: FunctionType<[K], V>): NullExpr` | Merge value with existing using function | `dict.merge("count", 1n, ($, old, new, key) => old.add(new), ($, key) => 0n)` | | `getOrInsert(key: ExprType \| ValueTypeOf, defaultFn: FunctionType<[K], V>): ExprType` | Get or insert default if missing | `dict.getOrInsert("foo", East.function([StringType], IntegerType, ($, key) => 0n))` | | `delete(key: ExprType \| ValueTypeOf): NullExpr` **❗** | Delete (errors if missing) | `dict.delete("foo")` | | `tryDelete(key: ExprType \| ValueTypeOf): BooleanExpr` | Safe delete (returns success) | `dict.tryDelete("foo")` | | `pop(key: ExprType \| ValueTypeOf): ExprType` **❗** | Remove and return value (errors if missing) | `dict.pop("foo")` | | `swap(key: ExprType \| ValueTypeOf, value: ExprType \| ValueTypeOf): ExprType` **❗** | Replace and return old value (errors if missing) | `dict.swap("foo", 100n)` | | `clear(): NullExpr` | Remove all entries | `dict.clear()` | | `unionInPlace(other: DictExpr, mergeFn?: FunctionType<[V, V, K], V>): NullExpr` **❗** | Union in-place (errors on conflict without mergeFn) | `dict.unionInPlace(otherDict, ($, v1, v2, key) => v2)` | | `mergeAll(other: DictExpr, mergeFn: FunctionType<[V, V2, K], V>, initialFn?: FunctionType<[K], V>): NullExpr` | Merge all entries from another dict | `dict.mergeAll(other, ($, cur, new, key) => cur.add(new))` | | **Functional Operations** | | `copy(): DictExpr` | Shallow copy | `dict.copy()` | | `map(fn: FunctionType<[V, K], U>): DictExpr` | Transform values (keys unchanged) | `dict.map(($, val, key) => val.multiply(2n))` | | `filter(predicate: FunctionType<[V, K], BooleanType>): DictExpr` | Keep matching entries | `dict.filter(($, val, key) => East.greater(val, 0n))` | | `filterMap(fn: FunctionType<[V, K], OptionType>): DictExpr` | Filter and map using Option | `dict.filterMap(($, val, key) => East.greater(val, 0n) ? East.some(val.multiply(2n)) : East.none())` | | `firstMap(fn: FunctionType<[V, K], OptionType>): OptionExpr` | Map until first successful result | `dict.firstMap(($, val, key) => East.greater(val, 10n) ? East.some(val) : East.none())` | | `forEach(fn: FunctionType<[V, K], any>): NullExpr` | Execute function for each entry | `dict.forEach(($, val, key) => $(arr.pushLast(val)))` | | `reduce(fn: FunctionType<[T, V, K], T>, init: T): ExprType` | Fold/reduce over dict | `dict.reduce(($, acc, val, key) => acc.add(val), 0n)` | | `every(fn?: FunctionType<[V, K], BooleanType>): BooleanExpr` | True if all match | `dict.every()` | | `some(fn?: FunctionType<[V, K], BooleanType>): BooleanExpr` | True if any match | `dict.some()` | | `sum(): IntegerExpr \| FloatExpr` | Sum of numeric values | `dict.sum()` | | `sum(fn: FunctionType<[V, K], IntegerType \| FloatType>): IntegerExpr \| FloatExpr` | Sum with projection | `dict.sum(($, val, key) => val.multiply(2n))` | | `mean(): FloatExpr` | Mean (NaN if empty) | `dict.mean()` | | `mean(fn: FunctionType<[V, K], IntegerType \| FloatType>): FloatExpr` | Mean with projection | `dict.mean(($, val, key) => val.toFloat())` | | **Conversion Operations** | | `toArray(fn?: FunctionType<[V, K], U>): ArrayExpr` | Convert to array | `dict.toArray()` | | `toSet(keyFn?: FunctionType<[V, K], U>): SetExpr` | Convert to set (ignoring duplicates) | `dict.toSet(($, val, key) => key)` | | `toDict(keyFn?: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], V2>, onConflictFn?: FunctionType<[V2, V2, K2], V2>): DictExpr` | Convert to new dict | `dict.toDict(($, val, key) => key)` | | `flattenToArray(fn?: FunctionType<[V, K], ArrayType>): ArrayExpr` | Flatten to array | `dict.flattenToArray()` | | `flattenToSet(fn?: FunctionType<[V, K], SetType>): SetExpr` | Flatten to set | `dict.flattenToSet()` | | `flattenToDict(fn?: FunctionType<[V, K], DictType), onConflictFn?: FunctionType<[V2, V2, K2], V2>): DictExpr` | Flatten to dict | `dict.flattenToDict()` | | **Grouping Operations** | | `groupReduce(keyFn: FunctionType<[V, K], K2>, valueFn: FunctionType<[V, K], U>, initFn: FunctionType<[K2], T>, reduceFn: FunctionType<[T, U, K2], T>): DictExpr` | Group by key and reduce groups | `dict.groupReduce(($, val, key) => key.remainder(2n), ($, val, key) => val, ($, grp) => 0n, ($, acc, v, grp) => acc.add(v))` | | `groupSize(keyFn?: FunctionType<[V, K], K2>): DictExpr` | Count elements in each group | `dict.groupSize(($, val, key) => key.remainder(2n))` | | `groupEvery(keyFn: FunctionType<[V, K], K2>, predFn: FunctionType<[V, K], BooleanType>): DictExpr` | Check if all elements in each group match predicate | `dict.groupEvery(($, val, key) => key.remainder(2n), ($, val, key) => East.greater(val, 0n))` | | `groupSome(keyFn: FunctionType<[V, K], K2>, predFn: FunctionType<[V, K], BooleanType>): DictExpr` | Check if any element in each group matches predicate | `dict.groupSome(($, val, key) => key.remainder(2n), ($, val, key) => East.greater(val, 10n))` | | `groupSum(keyFn: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], IntegerType \| FloatType>): DictExpr` | Sum values in each group | `dict.groupSum(($, val, key) => key.remainder(2n))` | | `groupMean(keyFn: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], IntegerType \| FloatType>): DictExpr` | Mean of values in each group | `dict.groupMean(($, val, key) => key.remainder(2n))` | | `groupToArrays(keyFn: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], U>): DictExpr>` | Collect elements into arrays by group | `dict.groupToArrays(($, val, key) => key.remainder(2n))` | | `groupToSets(keyFn: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], U>): DictExpr>` | Collect elements into sets by group | `dict.groupToSets(($, val, key) => key.remainder(2n))` | | `groupToDicts(keyFn: FunctionType<[V, K], K2>, keyFn2: FunctionType<[V, K], K3>, valueFn?: FunctionType<[V, K], U>, combineFn?: FunctionType<[U, U, K3], U>): DictExpr>` | Collect elements into nested dicts | `dict.groupToDicts(($, val, key) => key.remainder(2n), ($, val, key) => key, ($, val, key) => val)` | **Standard Library:** See [STDLIB.md](./STDLIB.md#dict) for dict generation functions. --- ### Struct Struct fields are accessed directly as properties. **Example:** ```typescript const PersonType = StructType({ name: StringType, age: IntegerType }); const updateAge = East.function([PersonType, IntegerType], PersonType, ($, person, yearsToAdd) => { const newAge = person.age.add(yearsToAdd); // Create struct value with $.let() and East.value() (type inference) const updated = $.let(East.value({ ...person, age: newAge })); // Alternative: Create struct value with $.let() and East.value() with explicit type const defaultPerson = $.let(East.value({ name: "Unknown", age: 0n }, PersonType)); $.return(updated); }); const compiled = East.compile(updateAge, []); const result = compiled({ name: "Alice", age: 30n }, 5n); console.log(result); // { name: "Alice", age: 35n } ``` **Operations:** | Signature | Description | Example | |-----------|-------------|---------| | **Read Operations** | | `field: ExprType` | Access struct field | `person.name` | **Notes:** - Structs are immutable (use spread to create modified copies) --- ### Variant Variants represent tagged unions (sum types). **Example:** ```typescript import { East, variant, VariantType, IntegerType, NullType } from "@elaraai/east"; const OptionType = VariantType({ Some: IntegerType, None: NullType }); const processOption = East.function([OptionType], IntegerType, ($, value) => { // Create variant value with $.let() and East.value() (type inference) const defaultValue = $.let(East.value(variant("None", null))); // Alternative: Create variant value with $.let() and East.value() with explicit type const someValue = $.let(East.value(variant("Some", 10n), OptionType)); // Pattern match using statement form const result = $.let(0n); $.match(value, { Some: ($, x) => $.assign(result, x.add(1n)), None: $ => $.assign(result, 0n), }); $.return(result); }); const compiled = East.compile(processOption, []); console.log(compiled(variant("Some", 41n))); // 42n console.log(compiled(variant("None", null))); // 0n ``` **Operations:** | Signature | Description | Example | |-----------|-------------|---------| | **Base Operations** | | `match(cases: { [K]: ($, data) => Expr }): ExprType` | Pattern match on all cases | `opt.match({ Some: ($, x) => x, None: $ => 0n })` | | `unwrap(tag?: string): ExprType` **❗** | Extract value (errors if wrong tag) | `opt.unwrap("Some")` | | `unwrap(tag: string, defaultFn: ($) => Expr): ExprType` | Extract value or compute default | `opt.unwrap("Some", $ => 0n)` | | `getTag(): StringExpr` | Get tag as string | `opt.getTag()` | | `hasTag(tag: string): BooleanExpr` | Check if has specific tag | `opt.hasTag("Some")` |