Files
2025-11-29 18:25:07 +08:00

66 KiB
Raw Permalink Blame History

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.


Table of Contents


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

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<IntegerType>)
  • ValueTypeOf<T> - The JavaScript runtime value for a type (e.g., bigint for IntegerType, string for StringType)
  • Expr<T> - A typed expression that can be composed and compiled (e.g., IntegerExpr, StringExpr)

Key insight: Most East functions accept either Expr<T> OR ValueTypeOf<T>, 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> T[] Mutable Ordered collection
SetType<K> Set<K> Mutable Sorted set (keys ordered by total ordering)
DictType<K, V> Map<K, V> Mutable Sorted dict (keys ordered by total ordering)
StructType<Fields> {...} Immutable Product type (field order matters)
VariantType<Cases> variant Immutable Sum type (cases sorted alphabetically)
Function Type
FunctionType<I, O> 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<V>(val: ValueTypeOf<V>): Expr<V> Create expression from JavaScript value East.value(42n)
value<T extends EastType>(val: Expr<T> | ValueTypeOf<T>, type: T): Expr<T> Create expression with explicit type East.value(x, IntegerType)
str`...`: StringExpr String interpolation template East.str`Hello ${name}`
print<T extends EastType>(expr: Expr<T>): StringExpr Convert any expression to string representation East.print(x)
Function Definition
function<I extends EastType[], O extends EastType>(inputs: I, output: O, body: ($, ...args) => Expr | value): FunctionExpr Define a function (see Function) East.function([IntegerType], IntegerType, ($, x) => x.add(1n))
compile<I extends EastType[], O extends EastType>(fn: FunctionExpr<I, O>, platform: PlatformFunction[]): (...inputs) => ValueTypeOf<O> Compile East function to executable JavaScript East.compile(myFunction, [log.implement(console.log)])
platform<I extends EastType[], O extends EastType>(name: string, inputs: I, output: O): (...args) => ExprType<O> Create callable helper for platform function const log = East.platform("log", [StringType], NullType)
Comparisons
equal<T extends EastType>(a: Expr<T>, b: Expr<T> | ValueTypeOf<T>): BooleanExpr Deep equality comparison East.equal(x, 10n)
notEqual<T extends EastType>(a: Expr<T>, b: Expr<T> | ValueTypeOf<T>): BooleanExpr Deep inequality comparison East.notEqual(x, 0n)
less<T extends EastType>(a: Expr<T>, b: Expr<T> | ValueTypeOf<T>): BooleanExpr Less than comparison (total ordering) East.less(x, 100n)
lessEqual<T extends EastType>(a: Expr<T>, b: Expr<T> | ValueTypeOf<T>): BooleanExpr Less than or equal comparison East.lessEqual(x, y)
greater<T extends EastType>(a: Expr<T>, b: Expr<T> | ValueTypeOf<T>): BooleanExpr Greater than comparison East.greater(x, 0n)
greaterEqual<T extends EastType>(a: Expr<T>, b: Expr<T> | ValueTypeOf<T>): BooleanExpr Greater than or equal comparison East.greaterEqual(score, 50n)
is<T extends DataType>(a: Expr<T>, b: Expr<T> | ValueTypeOf<T>): BooleanExpr Reference equality (for mutable types) East.is(arr1, arr2)
Utilities
min<T extends EastType>(a: Expr<T>, b: Expr<T> | ValueTypeOf<T>): Expr<T> Minimum of two values (uses total ordering) East.min(x, 100n)
max<T extends EastType>(a: Expr<T>, b: Expr<T> | ValueTypeOf<T>): Expr<T> Maximum of two values (uses total ordering) East.max(x, 0n)
clamp<T extends EastType>(x: Expr<T>, min: Expr<T> | ValueTypeOf<T>, max: Expr<T> | ValueTypeOf<T>): Expr<T> 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:

  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:

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:

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:

// 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

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<V>(value: ValueTypeOf<V>): Expr<V> Declare immutable variable (infers type) const x = $.const(42n)
const<T extends EastType>(value: Expr<T> | ValueTypeOf<T>, type: T): Expr<T> Declare immutable variable with explicit type const x = $.const(y, IntegerType)
let<V>(value: ValueTypeOf<V>): Expr<V> Declare mutable variable (infers type) const x = $.let(0n)
let<T extends EastType>(value: Expr<T> | ValueTypeOf<T>, type: T): Expr<T> Declare mutable variable with explicit type const x = $.let(y, IntegerType)
assign<T extends EastType>(variable: Expr<T>, value: Expr<T> | ValueTypeOf<T>): NullExpr Reassign mutable variable (must be declared with $.let) $.assign(x, 10n)
Execution
$<T extends EastType>(expr: Expr<T>): Expr<T> Execute expression (often for side effects), returns the expression $(arr.pushLast(42n))
return<Ret>(value: Expr<Ret> | ValueTypeOf<Ret>): 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<T extends EastType>(array: ArrayExpr<T>, body: ($, value, index, label) => void): NullExpr For loop over array elements $.for(arr, ($, val, i, label) => $(total.add(val)))
for<K extends DataType>(set: SetExpr<K>, body: ($, key, label) => void): NullExpr For loop over set keys $.for(s, ($, key, label) => $(arr.pushLast(key)))
for<K extends DataType, V extends EastType>(dict: DictExpr<K, V>, 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<Cases>(variant: VariantExpr<Cases>, 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:

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<TypeUnion<...>> 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:

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 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:

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:

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<StringType> 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<T extends DataType>(type: T): ExprType<T> Parse string to given type (fallible) str.parse(IntegerType)
parseJson<T extends DataType>(type: T): ExprType<T> Parse JSON to given type (fallible) str.parseJson(IntegerType)

Standard Library: See STDLIB.md for error formatting utilities.


DateTime

DateTime expressions (DateTimeExpr) represent UTC timestamps with millisecond precision, supporting component access, arithmetic, and rounding operations.

Example:

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 for construction from components and additional rounding functions.


Blob

Blob expressions (BlobExpr) represent immutable binary data with byte access and encoding/decoding operations.

Example:

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<T extends EastType>(type: T, version: 'v1' | 'v2' = 'v1'): ExprType<T> Decode binary BEAST format (v1 or v2, fallible) blob.decodeBeast(IntegerType, 'v2')

Standard Library: See STDLIB.md for BEAST encoding utilities.


Array

Array expressions (ArrayExpr<T>) represent mutable, ordered collections with rich functional operations, mutations, and conversions.

Example:

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<V extends EastType>(index: IntegerExpr | bigint): ExprType<V> Get element (errors if out of bounds) array.get(0n)
get<V extends EastType>(index: IntegerExpr | bigint, defaultFn: FunctionType<[IntegerType], V>): ExprType<V> Get element or compute default array.get(10n, East.function([IntegerType], IntegerType, ($, i) => 0n))
tryGet<V extends EastType>(index: IntegerExpr | bigint): OptionExpr<V> Safe get returning Option array.tryGet(0n)
Mutation Operations
update<V extends EastType>(index: IntegerExpr | bigint, value: ExprType<V> | ValueTypeOf<V>): NullExpr Replace element (errors if out of bounds) array.update(0n, 42n)
merge<V extends EastType, T2 extends EastType>(index: IntegerExpr | bigint, value: Expr<T2>, updateFn: FunctionType<[V, T2, IntegerType], V>): ExprType<V> Merge value with existing element using function array.merge(0n, 5n, ($, old, new, i) => old.add(new))
pushLast<V extends EastType>(value: ExprType<V> | ValueTypeOf<V>): NullExpr Append to end array.pushLast(42n)
popLast<V extends EastType>(): ExprType<V> Remove from end (errors if empty) array.popLast()
pushFirst<V extends EastType>(value: ExprType<V> | ValueTypeOf<V>): NullExpr Prepend to start array.pushFirst(42n)
popFirst<V extends EastType>(): ExprType<V> Remove from start (errors if empty) array.popFirst()
append<V extends EastType>(other: ArrayExpr<V>): NullExpr Append all elements from other array (mutating) array.append(otherArray)
prepend<V extends EastType>(other: ArrayExpr<V>): NullExpr Prepend all elements from other array (mutating) array.prepend(otherArray)
mergeAll<V extends EastType, T2 extends EastType>(other: ArrayExpr<T2>, 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<V extends EastType>(byFn?: FunctionType<[V], DataType>): NullExpr Sort in-place array.sortInPlace()
reverseInPlace(): NullExpr Reverse in-place array.reverseInPlace()
Functional Operations (Immutable)
copy<V extends EastType>(): ArrayExpr<V> Shallow copy array.copy()
slice<V extends EastType>(start: IntegerExpr | bigint, end: IntegerExpr | bigint): ArrayExpr<V> Extract subarray array.slice(0n, 10n)
concat<V extends EastType>(other: ArrayExpr<V>): ArrayExpr<V> Concatenate into new array array.concat(otherArray)
getKeys<V extends EastType>(keys: ArrayExpr<IntegerType>, onMissing?: FunctionType<[IntegerType], V>): ArrayExpr<V> Get values at given indices array.getKeys(indices, East.function([IntegerType], IntegerType, ($, i) => 0n))
sort<V extends EastType>(byFn?: FunctionType<[V], DataType>): ArrayExpr<V> Return sorted copy array.sort()
reverse<V extends EastType>(): ArrayExpr<V> Return reversed copy array.reverse()
isSorted<V extends EastType>(byFn?: FunctionType<[V], DataType>): BooleanExpr Check if sorted array.isSorted()
findSortedFirst<V extends EastType, T2 extends EastType>(value: T2, byFn?: FunctionType<[V], TypeOf<T2>>): IntegerExpr Binary search for first element ≥ value array.findSortedFirst(42n)
findSortedLast<V extends EastType, T2 extends EastType>(value: T2, byFn?: FunctionType<[V], TypeOf<T2>>): IntegerExpr Binary search for last element ≤ value array.findSortedLast(42n)
findSortedRange<V extends EastType, T2 extends EastType>(value: T2, byFn?: FunctionType<[V], TypeOf<T2>>): StructExpr<{start, end}> Binary search for range of elements equal to value array.findSortedRange(42n)
map<V extends EastType, U extends EastType>(fn: FunctionType<[V, IntegerType], U>): ArrayExpr<U> Transform each element array.map(($, x, i) => x.multiply(2n))
filter<V extends EastType>(predicate: FunctionType<[V, IntegerType], BooleanType>): ArrayExpr<V> Keep matching elements array.filter(($, x, i) => East.greater(x, 0n))
filterMap<V extends EastType, U extends EastType>(fn: FunctionType<[V, IntegerType], OptionType<U>>): ArrayExpr<U> Filter and map using Option array.filterMap(($, x, i) => East.greater(x, 0n) ? East.some(x.multiply(2n)) : East.none())
firstMap<V extends EastType, U extends EastType>(fn: FunctionType<[V, IntegerType], OptionType<U>>): OptionExpr<U> Map until first successful result (returns Option) array.firstMap(($, x, i) => East.greater(x, 0n) ? East.some(x) : East.none())
findFirst<V extends EastType>(value: V): OptionExpr<IntegerType> Find index of first matching element array.findFirst(42n)
findFirst<V extends EastType, T2 extends EastType>(value: T2, by: FunctionType<[V, IntegerType], T2>): OptionExpr<IntegerType> Find first match with projection array.findFirst("active", ($, u, i) => u.status)
findAll<V extends EastType>(value: V): ArrayExpr<IntegerType> Find all indices of matching elements array.findAll(42n)
findAll<V extends EastType, T2 extends EastType>(value: T2, by: FunctionType<[V, IntegerType], T2>): ArrayExpr<IntegerType> Find all matches with projection array.findAll("active", ($, u, i) => u.status)
forEach<V extends EastType>(fn: FunctionType<[V, IntegerType], any>): NullExpr Execute function for each element array.forEach(($, x, i) => $(total.add(x)))
Reduction Operations
reduce<V extends EastType, T extends EastType>(combineFn: FunctionType<[T, V, IntegerType], T>, init: T): ExprType<T> Fold/reduce with initial value array.reduce(($, acc, x, i) => acc.add(x), 0n)
reduce<V extends EastType>(combineFn: FunctionType<[V, V, IntegerType], V>): ExprType<V> Fold/reduce without initial (uses first element) array.reduce(($, acc, x, i) => acc.add(x))
mapReduce<V extends EastType, U extends EastType, T extends EastType>(mapFn: FunctionType<[V, IntegerType], U>, combineFn: FunctionType<[T, U, IntegerType], T>, init: T): ExprType<T> Map then reduce with initial value array.mapReduce(($, x, i) => x.multiply(2n), ($, acc, x, i) => acc.add(x), 0n)
mapReduce<V extends EastType, U extends EastType>(mapFn: FunctionType<[V, IntegerType], U>, combineFn: FunctionType<[U, U, IntegerType], U>): ExprType<U> Map then reduce without initial array.mapReduce(($, x, i) => x.multiply(2n), ($, acc, x, i) => acc.add(x))
every<V extends EastType>(predicate?: FunctionType<[V, IntegerType], BooleanType>): BooleanExpr True if all match predicate array.every()
some<V extends EastType>(predicate?: FunctionType<[V, IntegerType], BooleanType>): BooleanExpr True if any match predicate array.some()
sum<V extends IntegerType | FloatType>(): IntegerExpr | FloatExpr Sum of numeric array array.sum()
sum<V extends EastType>(fn: FunctionType<[V, IntegerType], IntegerType | FloatType>): IntegerExpr | FloatExpr Sum with projection array.sum(($, x, i) => x.multiply(2n))
mean<V extends IntegerType | FloatType>(): FloatExpr Mean (NaN if empty) array.mean()
mean<V extends EastType>(fn: FunctionType<[V, IntegerType], IntegerType | FloatType>): FloatExpr Mean with projection array.mean(($, x, i) => x.toFloat())
findMaximum<V extends EastType>(by?: FunctionType<[V, IntegerType], any>): OptionExpr<IntegerType> Find index of maximum element array.findMaximum()
findMinimum<V extends EastType>(by?: FunctionType<[V, IntegerType], any>): OptionExpr<IntegerType> Find index of minimum element array.findMinimum()
maximum<V extends EastType>(by?: FunctionType<[V, IntegerType], any>): ExprType<V> Get maximum element value (errors if empty) array.maximum()
minimum<V extends EastType>(by?: FunctionType<[V, IntegerType], any>): ExprType<V> Get minimum element value (errors if empty) array.minimum()
Conversion Operations
stringJoin<V extends StringType>(separator: StringExpr | string): StringExpr Join string array (only for ArrayType<StringType>) array.stringJoin(", ")
toSet<V extends EastType, K extends DataType>(keyFn?: FunctionType<[V, IntegerType], K>): SetExpr<K> Convert to set (ignoring duplicates) array.toSet()
toDict<V extends EastType, K extends DataType, U extends EastType>(keyFn?: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], U>, onConflictFn?: FunctionType<[U, U, K], U>): DictExpr<K, U> Convert to dict array.toDict(($, x, i) => i)
flatMap<V extends EastType, U extends EastType>(fn?: FunctionType<[V, IntegerType], ArrayType<U>>): ArrayExpr<U> Flatten array of arrays array.flatMap()
flattenToSet<V extends EastType, K extends DataType>(fn?: FunctionType<[V, IntegerType], SetType<K>>): SetExpr<K> Flatten to set array.flattenToSet()
flattenToDict<V extends EastType, K extends DataType, U extends EastType>(fn?: FunctionType<[V, IntegerType], DictType<K, U>>, onConflictFn?: FunctionType<[U, U, K], U>): DictExpr<K, U> Flatten to dict array.flattenToDict()
Grouping Operations
groupReduce<V extends EastType, K extends DataType, U extends EastType, T extends EastType>(keyFn: FunctionType<[V, IntegerType], K>, valueFn: FunctionType<[V, IntegerType], U>, initFn: FunctionType<[K], T>, reduceFn: FunctionType<[T, U, K], T>): DictExpr<K, T> 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<V extends EastType, K extends DataType>(keyFn?: FunctionType<[V, IntegerType], K>): DictExpr<K, IntegerType> Count elements in each group array.groupSize(($, x, i) => x.remainder(2n))
groupEvery<V extends EastType, K extends DataType>(keyFn: FunctionType<[V, IntegerType], K>, predFn: FunctionType<[V, IntegerType], BooleanType>): DictExpr<K, BooleanType> Check if all elements in each group match predicate array.groupEvery(($, x, i) => x.remainder(2n), ($, x, i) => East.greater(x, 0n))
groupSome<V extends EastType, K extends DataType>(keyFn: FunctionType<[V, IntegerType], K>, predFn: FunctionType<[V, IntegerType], BooleanType>): DictExpr<K, BooleanType> Check if any element in each group matches predicate array.groupSome(($, x, i) => x.remainder(2n), ($, x, i) => East.greater(x, 10n))
groupFindFirst<V extends EastType, K extends DataType, T2 extends EastType>(keyFn: FunctionType<[V, IntegerType], K>, value: T2, projFn?: FunctionType<[V, IntegerType], T2>): DictExpr<K, OptionType<IntegerType>> Find first matching index in each group array.groupFindFirst(($, x, i) => x.remainder(2n), 42n)
groupFindAll<V extends EastType, K extends DataType, T2 extends EastType>(keyFn: FunctionType<[V, IntegerType], K>, value: T2, projFn?: FunctionType<[V, IntegerType], T2>): DictExpr<K, ArrayType<IntegerType>> Find all matching indices in each group array.groupFindAll(($, x, i) => x.remainder(2n), 42n)
groupFindMinimum<V extends EastType, K extends DataType>(keyFn: FunctionType<[V, IntegerType], K>, byFn?: FunctionType<[V, IntegerType], any>): DictExpr<K, IntegerType> Find index of minimum in each group array.groupFindMinimum(($, x, i) => x.remainder(2n))
groupFindMaximum<V extends EastType, K extends DataType>(keyFn: FunctionType<[V, IntegerType], K>, byFn?: FunctionType<[V, IntegerType], any>): DictExpr<K, IntegerType> Find index of maximum in each group array.groupFindMaximum(($, x, i) => x.remainder(2n))
groupSum<V extends EastType, K extends DataType>(keyFn: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], IntegerType | FloatType>): DictExpr<K, IntegerType | FloatType> Sum values in each group array.groupSum(($, x, i) => x.remainder(2n))
groupMean<V extends EastType, K extends DataType>(keyFn: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], IntegerType | FloatType>): DictExpr<K, FloatType> Mean of values in each group array.groupMean(($, x, i) => x.remainder(2n))
groupMinimum<V extends EastType, K extends DataType>(keyFn: FunctionType<[V, IntegerType], K>, byFn?: FunctionType<[V, IntegerType], any>): DictExpr<K, V> Get minimum value in each group array.groupMinimum(($, x, i) => x.remainder(2n))
groupMaximum<V extends EastType, K extends DataType>(keyFn: FunctionType<[V, IntegerType], K>, byFn?: FunctionType<[V, IntegerType], any>): DictExpr<K, V> Get maximum value in each group array.groupMaximum(($, x, i) => x.remainder(2n))
groupToArrays<V extends EastType, K extends DataType, U extends EastType>(keyFn: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], U>): DictExpr<K, ArrayType<U>> Collect elements into arrays by group array.groupToArrays(($, x, i) => x.remainder(2n))
groupToSets<V extends EastType, K extends DataType, U extends DataType>(keyFn: FunctionType<[V, IntegerType], K>, valueFn?: FunctionType<[V, IntegerType], U>): DictExpr<K, SetType<U>> Collect elements into sets by group array.groupToSets(($, x, i) => x.remainder(2n))
groupToDicts<V extends EastType, K extends DataType, K2 extends DataType, U extends EastType>(keyFn: FunctionType<[V, IntegerType], K>, keyFn2: FunctionType<[V, IntegerType], K2>, valueFn?: FunctionType<[V, IntegerType], U>, combineFn?: FunctionType<[U, U, K2], U>): DictExpr<K, DictType<K2, U>> Collect elements into nested dicts array.groupToDicts(($, x, i) => x.remainder(2n), ($, x, i) => i, ($, x, i) => x)

Standard Library: See STDLIB.md for array generation functions (range, linspace, generate).


Set

Set expressions (SetExpr<K>) represent mutable, sorted collections of unique keys with set operations and functional transformations.

Example:

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<K extends DataType>(key: ExprType<K> | ValueTypeOf<K>): BooleanExpr Check if key exists set.has(42n)
Mutation Operations
insert<K extends DataType>(key: ExprType<K> | ValueTypeOf<K>): NullExpr Insert key (errors if exists) set.insert(42n)
tryInsert<K extends DataType>(key: ExprType<K> | ValueTypeOf<K>): BooleanExpr Safe insert (returns success) set.tryInsert(42n)
delete<K extends DataType>(key: ExprType<K> | ValueTypeOf<K>): NullExpr Delete key (errors if missing) set.delete(42n)
tryDelete<K extends DataType>(key: ExprType<K> | ValueTypeOf<K>): BooleanExpr Safe delete (returns success) set.tryDelete(42n)
clear(): NullExpr Remove all elements set.clear()
unionInPlace<K extends DataType>(other: SetExpr<K>): NullExpr Union in-place (mutating) set.unionInPlace(otherSet)
Set Operations
copy<K extends DataType>(): SetExpr<K> Shallow copy set.copy()
union<K extends DataType>(other: SetExpr<K>): SetExpr<K> Return union set.union(otherSet)
intersection<K extends DataType>(other: SetExpr<K>): SetExpr<K> Return intersection set.intersection(otherSet)
difference<K extends DataType>(other: SetExpr<K>): SetExpr<K> Return difference (in this, not in other) set.difference(otherSet)
symmetricDifference<K extends DataType>(other: SetExpr<K>): SetExpr<K> Return symmetric difference set.symmetricDifference(otherSet)
isSubsetOf<K extends DataType>(other: SetExpr<K>): BooleanExpr Check if subset set.isSubsetOf(otherSet)
isSupersetOf<K extends DataType>(other: SetExpr<K>): BooleanExpr Check if superset set.isSupersetOf(otherSet)
isDisjointFrom<K extends DataType>(other: SetExpr<K>): BooleanExpr Check if no common elements set.isDisjointFrom(otherSet)
Functional Operations (Immutable)
filter<K extends DataType>(predicate: FunctionType<[K], BooleanType>): SetExpr<K> Keep matching elements set.filter(($, key) => East.greater(key, 0n))
filterMap<K extends DataType, V extends EastType>(fn: FunctionType<[K], OptionType<V>>): ArrayExpr<V> Filter and map using Option set.filterMap(($, key) => East.greater(key, 0n) ? East.some(key) : East.none())
firstMap<K extends DataType, V extends EastType>(fn: FunctionType<[K], OptionType<V>>): OptionExpr<V> Map until first successful result set.firstMap(($, key) => East.greater(key, 10n) ? East.some(key) : East.none())
forEach<K extends DataType>(fn: FunctionType<[K], any>): NullExpr Execute function for each element set.forEach(($, key) => $(arr.pushLast(key)))
map<K extends DataType, V extends EastType>(fn: FunctionType<[K], V>): DictExpr<K, V> Map to dict (keys unchanged, values from fn) set.map(($, key) => key.multiply(2n))
reduce<K extends DataType, T extends EastType>(fn: FunctionType<[T, K], T>, init: T): ExprType<T> Fold/reduce over set set.reduce(($, acc, key) => acc.add(key), 0n)
every<K extends DataType>(fn?: FunctionType<[K], BooleanType>): BooleanExpr True if all match set.every()
some<K extends DataType>(fn?: FunctionType<[K], BooleanType>): BooleanExpr True if any match set.some()
sum<K extends IntegerType | FloatType>(): IntegerExpr | FloatExpr Sum of numeric set set.sum()
sum<K extends DataType>(fn: FunctionType<[K], IntegerType | FloatType>): IntegerExpr | FloatExpr Sum with projection set.sum(($, key) => key.multiply(2n))
mean<K extends IntegerType | FloatType>(): FloatExpr Mean (NaN if empty) set.mean()
mean<K extends DataType>(fn: FunctionType<[K], IntegerType | FloatType>): FloatExpr Mean with projection set.mean(($, key) => key.toFloat())
Conversion Operations
toArray<K extends DataType, V extends EastType>(fn?: FunctionType<[K], V>): ArrayExpr<V> Convert to array set.toArray()
toSet<K extends DataType, U extends DataType>(keyFn?: FunctionType<[K], U>): SetExpr<U> Convert to new set (ignoring duplicates) set.toSet(($, key) => key.multiply(2n))
toDict<K extends DataType, K2 extends DataType, V extends EastType>(keyFn?: FunctionType<[K], K2>, valueFn?: FunctionType<[K], V>, onConflictFn?: FunctionType<[V, V, K2], V>): DictExpr<K2, V> Convert to dict set.toDict()
flattenToArray<K extends DataType, V extends EastType>(fn: FunctionType<[K], ArrayType<V>>): ArrayExpr<V> Flatten to array set.flattenToArray(($, key) => East.Array.range(0n, key))
flattenToSet<K extends DataType, U extends DataType>(fn: FunctionType<[K], SetType<U>>): SetExpr<U> Flatten to set set.flattenToSet(($, key) => otherSetDict.get(key))
flattenToDict<K extends DataType, K2 extends DataType, V extends EastType>(fn: FunctionType<[K], DictType<K2, V>>, onConflictFn?: FunctionType<[V, V, K2], V>): DictExpr<K2, V> Flatten to dict set.flattenToDict(($, key) => nestedDicts.get(key), ($, v1, v2, k) => v1.add(v2))
Grouping Operations
groupReduce<K extends DataType, K2 extends DataType, V extends EastType, T extends EastType>(keyFn: FunctionType<[K], K2>, valueFn: FunctionType<[K], V>, initFn: FunctionType<[K2], T>, reduceFn: FunctionType<[T, V, K2], T>): DictExpr<K2, T> Group by key and reduce groups set.groupReduce(($, key) => key.remainder(2n), ($, key) => key, ($, grp) => 0n, ($, acc, val, grp) => acc.add(val))
groupSize<K extends DataType, K2 extends DataType>(keyFn?: FunctionType<[K], K2>): DictExpr<K2, IntegerType> Count elements in each group set.groupSize(($, key) => key.remainder(2n))
groupEvery<K extends DataType, K2 extends DataType>(keyFn: FunctionType<[K], K2>, predFn: FunctionType<[K], BooleanType>): DictExpr<K2, BooleanType> Check if all elements in each group match predicate set.groupEvery(($, key) => key.remainder(2n), ($, key) => East.greater(key, 0n))
groupSome<K extends DataType, K2 extends DataType>(keyFn: FunctionType<[K], K2>, predFn: FunctionType<[K], BooleanType>): DictExpr<K2, BooleanType> Check if any element in each group matches predicate set.groupSome(($, key) => key.remainder(2n), ($, key) => East.greater(key, 10n))
groupSum<K extends DataType, K2 extends DataType>(keyFn: FunctionType<[K], K2>, valueFn?: FunctionType<[K], IntegerType | FloatType>): DictExpr<K2, IntegerType | FloatType> Sum values in each group set.groupSum(($, key) => key.remainder(2n))
groupMean<K extends DataType, K2 extends DataType>(keyFn: FunctionType<[K], K2>, valueFn?: FunctionType<[K], IntegerType | FloatType>): DictExpr<K2, FloatType> Mean of values in each group set.groupMean(($, key) => key.remainder(2n))
groupToArrays<K extends DataType, K2 extends DataType, V extends EastType>(keyFn: FunctionType<[K], K2>, valueFn?: FunctionType<[K], V>): DictExpr<K2, ArrayType<V>> Collect elements into arrays by group set.groupToArrays(($, key) => key.remainder(2n))
groupToSets<K extends DataType, K2 extends DataType, U extends DataType>(keyFn: FunctionType<[K], K2>, valueFn?: FunctionType<[K], U>): DictExpr<K2, SetType<U>> Collect elements into sets by group set.groupToSets(($, key) => key.remainder(2n))
groupToDicts<K extends DataType, K2 extends DataType, K3 extends DataType, V extends EastType>(keyFn: FunctionType<[K], K2>, keyFn2: FunctionType<[K], K3>, valueFn?: FunctionType<[K], V>, combineFn?: FunctionType<[V, V, K3], V>): DictExpr<K2, DictType<K3, V>> Collect elements into nested dicts set.groupToDicts(($, key) => key.remainder(2n), ($, key) => key, ($, key) => key)

Standard Library: See STDLIB.md for set generation functions.


Dict

Dict expressions (DictExpr<K, V>) represent mutable, sorted key-value mappings with functional operations and flexible merging strategies.

Example:

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<K extends DataType>(key: ExprType<K> | ValueTypeOf<K>): BooleanExpr Check if key exists dict.has("foo")
get<K extends DataType, V extends EastType>(key: ExprType<K> | ValueTypeOf<K>): ExprType<V> Get value (errors if missing) dict.get("foo")
get<K extends DataType, V extends EastType>(key: ExprType<K> | ValueTypeOf<K>, defaultFn: FunctionType<[K], V>): ExprType<V> Get value or compute default dict.get("foo", East.function([StringType], IntegerType, ($, key) => 0n))
tryGet<K extends DataType, V extends EastType>(key: ExprType<K> | ValueTypeOf<K>): OptionExpr<V> Safe get returning Option dict.tryGet("foo")
keys<K extends DataType>(): SetExpr<K> Get all keys as set dict.keys()
getKeys<K extends DataType, V extends EastType>(keys: SetExpr<K>, onMissing?: FunctionType<[K], V>): DictExpr<K, V> Get values for given keys dict.getKeys(keySet, East.function([StringType], IntegerType, ($, key) => 0n))
Mutation Operations
insert<K extends DataType, V extends EastType>(key: ExprType<K> | ValueTypeOf<K>, value: ExprType<V> | ValueTypeOf<V>): NullExpr Insert (errors if exists) dict.insert("foo", 42n)
insertOrUpdate<K extends DataType, V extends EastType>(key: ExprType<K> | ValueTypeOf<K>, value: ExprType<V> | ValueTypeOf<V>): NullExpr Insert or update (idempotent) dict.insertOrUpdate("foo", 42n)
update<K extends DataType, V extends EastType>(key: ExprType<K> | ValueTypeOf<K>, value: ExprType<V> | ValueTypeOf<V>): NullExpr Update existing (errors if missing) dict.update("foo", 100n)
merge<K extends DataType, V extends EastType, T2 extends EastType>(key: ExprType<K> | ValueTypeOf<K>, 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<K extends DataType, V extends EastType>(key: ExprType<K> | ValueTypeOf<K>, defaultFn: FunctionType<[K], V>): ExprType<V> Get or insert default if missing dict.getOrInsert("foo", East.function([StringType], IntegerType, ($, key) => 0n))
delete<K extends DataType>(key: ExprType<K> | ValueTypeOf<K>): NullExpr Delete (errors if missing) dict.delete("foo")
tryDelete<K extends DataType>(key: ExprType<K> | ValueTypeOf<K>): BooleanExpr Safe delete (returns success) dict.tryDelete("foo")
pop<K extends DataType, V extends EastType>(key: ExprType<K> | ValueTypeOf<K>): ExprType<V> Remove and return value (errors if missing) dict.pop("foo")
swap<K extends DataType, V extends EastType>(key: ExprType<K> | ValueTypeOf<K>, value: ExprType<V> | ValueTypeOf<V>): ExprType<V> Replace and return old value (errors if missing) dict.swap("foo", 100n)
clear(): NullExpr Remove all entries dict.clear()
unionInPlace<K extends DataType, V extends EastType>(other: DictExpr<K, V>, mergeFn?: FunctionType<[V, V, K], V>): NullExpr Union in-place (errors on conflict without mergeFn) dict.unionInPlace(otherDict, ($, v1, v2, key) => v2)
mergeAll<K extends DataType, V extends EastType, V2 extends EastType>(other: DictExpr<K, V2>, 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<K extends DataType, V extends EastType>(): DictExpr<K, V> Shallow copy dict.copy()
map<K extends DataType, V extends EastType, U extends EastType>(fn: FunctionType<[V, K], U>): DictExpr<K, U> Transform values (keys unchanged) dict.map(($, val, key) => val.multiply(2n))
filter<K extends DataType, V extends EastType>(predicate: FunctionType<[V, K], BooleanType>): DictExpr<K, V> Keep matching entries dict.filter(($, val, key) => East.greater(val, 0n))
filterMap<K extends DataType, V extends EastType, U extends EastType>(fn: FunctionType<[V, K], OptionType<U>>): DictExpr<K, U> Filter and map using Option dict.filterMap(($, val, key) => East.greater(val, 0n) ? East.some(val.multiply(2n)) : East.none())
firstMap<K extends DataType, V extends EastType, U extends EastType>(fn: FunctionType<[V, K], OptionType<U>>): OptionExpr<U> Map until first successful result dict.firstMap(($, val, key) => East.greater(val, 10n) ? East.some(val) : East.none())
forEach<K extends DataType, V extends EastType>(fn: FunctionType<[V, K], any>): NullExpr Execute function for each entry dict.forEach(($, val, key) => $(arr.pushLast(val)))
reduce<K extends DataType, V extends EastType, T extends EastType>(fn: FunctionType<[T, V, K], T>, init: T): ExprType<T> Fold/reduce over dict dict.reduce(($, acc, val, key) => acc.add(val), 0n)
every<K extends DataType, V extends EastType>(fn?: FunctionType<[V, K], BooleanType>): BooleanExpr True if all match dict.every()
some<K extends DataType, V extends EastType>(fn?: FunctionType<[V, K], BooleanType>): BooleanExpr True if any match dict.some()
sum<V extends IntegerType | FloatType>(): IntegerExpr | FloatExpr Sum of numeric values dict.sum()
sum<K extends DataType, V extends EastType>(fn: FunctionType<[V, K], IntegerType | FloatType>): IntegerExpr | FloatExpr Sum with projection dict.sum(($, val, key) => val.multiply(2n))
mean<V extends IntegerType | FloatType>(): FloatExpr Mean (NaN if empty) dict.mean()
mean<K extends DataType, V extends EastType>(fn: FunctionType<[V, K], IntegerType | FloatType>): FloatExpr Mean with projection dict.mean(($, val, key) => val.toFloat())
Conversion Operations
toArray<K extends DataType, V extends EastType, U extends EastType>(fn?: FunctionType<[V, K], U>): ArrayExpr<U> Convert to array dict.toArray()
toSet<K extends DataType, V extends EastType, U extends DataType>(keyFn?: FunctionType<[V, K], U>): SetExpr<U> Convert to set (ignoring duplicates) dict.toSet(($, val, key) => key)
toDict<K extends DataType, V extends EastType, K2 extends DataType, V2 extends EastType>(keyFn?: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], V2>, onConflictFn?: FunctionType<[V2, V2, K2], V2>): DictExpr<K2, V2> Convert to new dict dict.toDict(($, val, key) => key)
flattenToArray<K extends DataType, V extends EastType, U extends EastType>(fn?: FunctionType<[V, K], ArrayType<U>>): ArrayExpr<U> Flatten to array dict.flattenToArray()
flattenToSet<K extends DataType, V extends EastType, K2 extends DataType>(fn?: FunctionType<[V, K], SetType<K2>>): SetExpr<K2> Flatten to set dict.flattenToSet()
flattenToDict<K extends DataType, V extends EastType, K2 extends DataType, V2 extends EastType>(fn?: FunctionType<[V, K], DictType<K2, V2>), onConflictFn?: FunctionType<[V2, V2, K2], V2>): DictExpr<K2, V2> Flatten to dict dict.flattenToDict()
Grouping Operations
groupReduce<K extends DataType, V extends EastType, K2 extends DataType, U extends EastType, T extends EastType>(keyFn: FunctionType<[V, K], K2>, valueFn: FunctionType<[V, K], U>, initFn: FunctionType<[K2], T>, reduceFn: FunctionType<[T, U, K2], T>): DictExpr<K2, T> 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<K extends DataType, V extends EastType, K2 extends DataType>(keyFn?: FunctionType<[V, K], K2>): DictExpr<K2, IntegerType> Count elements in each group dict.groupSize(($, val, key) => key.remainder(2n))
groupEvery<K extends DataType, V extends EastType, K2 extends DataType>(keyFn: FunctionType<[V, K], K2>, predFn: FunctionType<[V, K], BooleanType>): DictExpr<K2, BooleanType> Check if all elements in each group match predicate dict.groupEvery(($, val, key) => key.remainder(2n), ($, val, key) => East.greater(val, 0n))
groupSome<K extends DataType, V extends EastType, K2 extends DataType>(keyFn: FunctionType<[V, K], K2>, predFn: FunctionType<[V, K], BooleanType>): DictExpr<K2, BooleanType> Check if any element in each group matches predicate dict.groupSome(($, val, key) => key.remainder(2n), ($, val, key) => East.greater(val, 10n))
groupSum<K extends DataType, V extends EastType, K2 extends DataType>(keyFn: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], IntegerType | FloatType>): DictExpr<K2, IntegerType | FloatType> Sum values in each group dict.groupSum(($, val, key) => key.remainder(2n))
groupMean<K extends DataType, V extends EastType, K2 extends DataType>(keyFn: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], IntegerType | FloatType>): DictExpr<K2, FloatType> Mean of values in each group dict.groupMean(($, val, key) => key.remainder(2n))
groupToArrays<K extends DataType, V extends EastType, K2 extends DataType, U extends EastType>(keyFn: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], U>): DictExpr<K2, ArrayType<U>> Collect elements into arrays by group dict.groupToArrays(($, val, key) => key.remainder(2n))
groupToSets<K extends DataType, V extends EastType, K2 extends DataType, U extends DataType>(keyFn: FunctionType<[V, K], K2>, valueFn?: FunctionType<[V, K], U>): DictExpr<K2, SetType<U>> Collect elements into sets by group dict.groupToSets(($, val, key) => key.remainder(2n))
groupToDicts<K extends DataType, V extends EastType, K2 extends DataType, K3 extends DataType, U extends EastType>(keyFn: FunctionType<[V, K], K2>, keyFn2: FunctionType<[V, K], K3>, valueFn?: FunctionType<[V, K], U>, combineFn?: FunctionType<[U, U, K3], U>): DictExpr<K2, DictType<K3, U>> Collect elements into nested dicts dict.groupToDicts(($, val, key) => key.remainder(2n), ($, val, key) => key, ($, val, key) => val)

Standard Library: See STDLIB.md for dict generation functions.


Struct

Struct fields are accessed directly as properties.

Example:

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<Fields[field]> Access struct field person.name

Notes:

  • Structs are immutable (use spread to create modified copies)

Variant

Variants represent tagged unions (sum types).

Example:

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<T> Pattern match on all cases opt.match({ Some: ($, x) => x, None: $ => 0n })
unwrap(tag?: string): ExprType<Cases[tag]> Extract value (errors if wrong tag) opt.unwrap("Some")
unwrap(tag: string, defaultFn: ($) => Expr): ExprType<Cases[tag]> 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")