import { Stream } from '../data'; import { ArrayType, BooleanType, DictType, EastFunction, EastType, IntegerType, NonNullable, Nullable, SetType, StringType, StructType, Variable, VariantType, SubType, AstType } from '../east'; import { AbstractMLBuilder, MLModel } from '../ml'; import { MLDescription, MLEvaluator } from '../scenario/SimulationTaskDescription'; import { Builder, ModulePath } from '../template'; import { ModuleBuilder } from '../template/ModuleBuilder'; import { FunctionStatement, FunctionTaskDescription, ProcedureDescription, ProcedureEvaluator } from './FunctionTaskDescription'; import { ProcedureFinalizer } from './Procedure'; /** * A `FunctionBuilder` to build a data transformation in Elara. * * Methods on the `FunctionBuilder` enable you to directly transform data from any number of `input` {@link Stream}s * and output any number of `return` {@link Stream}s. The `body` of the function works similarly to most imperative * languages (like JavaScript/TypeScript) allowing for definition of temporary variables (with `let`), reassignment * of defined variables (with `assign`), branching (with `if`, `ifNull` and `match`), loops (with `while`, `forArray`, `forSet`, `forDict`), * as well as logging and raising errors (with `log`, `warn` and `error`). Inside any of these statements you can use * East {@link Expression}s to access and manipulate data in scope. * * A corresponding {@link Template} can be created using `.toTemplate()`. * * @category Function * * @example * ```typescript * // Create a function to add two integer streams, equivalent to the Javascript function * // * // function f(a, b) { * // let c = a + b; * // return { c }; * // } * // * // f(1n, 2n) == 3n * * const a = new SourceBuilder("a").value({ value: 1n }) * const b = new SourceBuilder("b").value({ value: 2n }) * * const f = new FunctionBuilder("f") * .input("a", a.outputStream()) * .input("b", b.outputStream()) * .body(block => block * .let("c", vars => Add(vars.a, vars.b)) * .return({ c: vars => vars.c }) * ) * ``` **/ export declare class FunctionBuilder = {}, RecursiveInputs extends Record = {}, Functions extends Record> = {}, MLs extends Record = {}, Procs extends Record = {}, Returns extends null | Record = null> extends Builder { private inputs; private recursiveInputs; private defaults; private functions; private mls; private procedures; private statements; private outputs; private random_seed; constructor(name: FunctionName, module?: ModuleBuilder | ModulePath); /** * Create a `FunctionBuilder` to build a data tansformation in Elara. * * Methods on the `FunctionBuilder` enable you to directly transform data from any number of `input` {@link Stream}s * and output any number of `return` {@link Stream}s. The `body` of the function works similarly to most imperative * languages (like JavaScript/TypeScript) allowing for definition of temporary variables (with `let`), reassignment * of defined variables (with `assign`), branching (with `if`, `ifNull` and `match`), loops (with `while`, `forArray`, `forSet`, `forDict`), * as well as logging and raising errors (with `log`, `warn` and `error`). Inside any of these statements you can use * East {@link Expression}s to access and manipulate data in scope. * * A corresponding {@link Template} can be created using `.toTemplate()`. * * @category Function * * @example * ```typescript * // Create a function to add two integer streams, equivalent to the Javascript function * // * // function f(a, b) { * // let c = a + b; * // return { c }; * // } * // * // f(1n, 2n) == 3n * * const a = new SourceBuilder("a").value({ value: 1n }) * const b = new SourceBuilder("b").value({ value: 2n }) * * const f = new FunctionBuilder("f") * .input("a", a.outputStream()) * .input("b", b.outputStream()) * .body(block => block * .let("c", vars => Add(vars.a, vars.b)) * .return({ c: vars => vars.c }) * ) * ``` **/ constructor(name: FunctionName, module: ModulePath, inputs: Record, recursiveInputs: Record, defaults: Record, functions: Functions, mls: MLs, procs: Procs, statements: null | FunctionStatement[], outputs: null | Record, random_seed: string); /** * Define an input streams as an input parameter of function. Each input stream is given a * variable name that is accessible in the `body` of the function. * * @category Function * * @example * ```typescript * // Create a function to add two integer streams, equivalent to the Javascript function * // * // function f(a, b) { * // let c = a + b; * // return { c }; * // } * // * // f(1n, 2n) == 3n * * const a = new SourceBuilder("a").value({ value: 1n }) * const b = new SourceBuilder("b").value({ value: 2n }) * * const f = new FunctionBuilder("f") * .input("a", a.outputStream()) * .input("b", b.outputStream()) * .body(block => block * .let("c", vars => Add(vars.a, vars.b)) * .return({ c: vars => vars.c }) * ) * ``` **/ input(name: Name, stream: Stream): FunctionBuilder; /** * Elara functions can be defined recursively using optional input and output streams. This method defines an "optional" inputs that have an initial default value, and can be provided recurssively by this or downstream functions. * * Note that this interface is experimental and not recommended for usage at this time. It is up to the user to ensure termination and avoid infinite recursion and excessive usage of the Elara cluster. **/ recursiveInput; }) => EastFunction>(name: Name, initial_value: F): FunctionBuilder["type"]; }, Functions & { [K_1 in FunctionName]: { [I in keyof RecursiveInputs]: Stream; } & { [K_2 in Name]: Stream["type"]>; }; }, MLs, Procs, Returns>; /** * Elara functions can be defined recursively using optional input and output streams. This method declares another function that can be called as a result of this function, where "calling" the function implies providing the optional input values defined by `recursiveInput`. * * Note that this interface is experimental and not recommended for usage at this time. It is up to the user to ensure termination and avoid infinite recursion and excessive usage of the Elara cluster. **/ ["function"], F extends FunctionBuilder, R>>(f: F): FunctionBuilder; }; }, MLs, Procs, Returns>; /** * Include a machine-learning function that can evaluated inside the expressions of this `FunctionBuilder`. * * @param ml the {@link MLModelBuilder} model add to the {@link FunctionBuilder} * * @category Function * * @example * ```typescript * // create an ml function * const my_ml_function = new MLModelBuilder("my_ml_function") * .feature("feature1", FloatType) * .feature("feature2", StringType) * .output(FloatType) * * // create the predict_amount function * const feature1 = new SourceBuilder("feature1").value({ value: 3.14 }) * const feature2 = new SourceBuilder("feature2").value({ value: "abc" }) * * const f = new FunctionBuilder("f") * .input("feature1", feature1.outputStream()) * .input("feature2", feature2.outputStream()) * .ml(my_ml_function) * .body(block => block * .let("output", (vars, mls) => mls.my_ml_function(Struct({ feature1: vars.feature1, feature2: vars.feature2 }))) * .return({ output: vars => vars.output }) * ) * ``` */ ml, T extends EastType>(ml: AbstractMLBuilder): FunctionBuilder, T>; }, Procs, Returns>; /** * Include a procedure that can evaluated inside the expressions of this `FunctionBuilder`. * * @param procedure the {@link Procedure} to add to the {@link FunctionBuilder} * * @category Function * * @example * ```typescript * // create a procedure to multiply two numbers * const my_procedure = new Procedure("my_procedure") * .input("x", FloatType) * .input("y", FloatType) * .output(FloatType) * .body(b => b * .return(vars => Multiply(vars.x, vars.y)) * ); * * // create the predict_amount function * const x = new SourceBuilder("x").value({ value: 3.14 }); * const y = new SourceBuilder("y").value({ value: 42.0 }); * * const f = new FunctionBuilder("f") * .input("x", x.outputStream()) * .input("y", y.outputStream()) * .procedure(my_procedure) * .body(block => block * .let("output", (vars, procs) => procs.my_procedure(Struct({ x: vars.x, y: vars.y }))) * .return({ output: vars => vars.output }) * ); * ``` */ procedure, T extends EastType>(procedure: ProcedureFinalizer): FunctionBuilder, T>; }, Returns>; /** * Define the "body" of this function, where computations are performed on inputs and outputs are returned. * The body can be thought of as a block of code containing statements like `let` and `return`, * or even nested blocks of code (using `if` or `while`). This code is executed much like the body of a function * in imperative languages, like JavaScript/TypeScript. * * To use this method, an empty "block" of code is injected on which you can use define * statements using the builder pattern (or fluent code pattern) and return the result. * * @category Function * * @example * ```typescript * // Create a function to add two integer streams, equivalent to the Javascript function * // * // function f(a, b) { * // let c = a + b; * // return { c }; * // } * // * // f(1n, 2n) == 3n * * const a = new SourceBuilder("a").value({ value: 1n }) * const b = new SourceBuilder("b").value({ value: 2n }) * * const f = new FunctionBuilder("f") * .input("a", a.outputStream()) * .input("b", b.outputStream()) * .body(block => block * .let("c", vars => Add(vars.a, vars.b)) * .return({ c: vars => vars.c }) * ) * ``` **/ body; } & { [K in keyof RecursiveInputs]: Variable; }, { [K in keyof Functions]: { [I in keyof Functions[K]]: Functions[K][I]["type"]; }; }, MLs, Procs, null>) => TerminalBlockBuilder>(body: B): FunctionBuilder>>; /** * Set the seed of the random number generator used in the function task. * This is useful to compare two different calculations with the same random noise. * * @remarks A hash of the seed string is used to initialize the random number generator. The function name is used by default. * * @param seed a string to seed the random number generator. * * @category Function */ randomSeed(seed: string): FunctionBuilder; /** * Elara functions can be defined recursively using optional input and output streams. This method returns the input streams defined via `recursiveInput`, which are written to when a function terminates with the `recurse` method. * * Note that this interface is experimental and not recommended for usage at this time. It is up to the user to ensure termination and avoid infinite recursion and excessive usage of the Elara cluster. **/ recursiveInputStreams(): { [K in keyof RecursiveInputs]: Stream; }; /** * Return the output streams for this function. * * @category Function * * @example * ```typescript * // Create a function to add two integer streams, equivalent to the Javascript function * // * // function f(a, b) { * // let c = a + b; * // return { c }; * // } * // * // f(1n, 2n) == 3n * * const a = new SourceBuilder("a").value({ value: 1n }) * const b = new SourceBuilder("b").value({ value: 2n }) * * const f = new FunctionBuilder("f") * .input("a", a.outputStream()) * .input("b", b.outputStream()) * .body(block => block * .let("c", vars => Add(vars.a, vars.b)) * .return({ c: vars => vars.c }) * ) * * const c_stream = f.outputStreams().c * ``` **/ outputStreams(): Returns extends Record ? { [K in keyof Returns]: Stream; } : {}; /** * Return the template elements for this function. * * @category Function * * @example * ```typescript * // Create a function to add two integer streams, equivalent to the Javascript function * // * // function f(a, b) { * // let c = a + b; * // return { c }; * // } * // * // f(1n, 2n) == 3n * * const a = new SourceBuilder("a").value({ value: 1n }) * const b = new SourceBuilder("b").value({ value: 2n }) * * const f = new FunctionBuilder("f") * .input("a", a.outputStream()) * .input("b", b.outputStream()) * .body(block => block * .let("c", vars => Add(vars.a, vars.b)) * .return({ c: vars => vars.c }) * ) * * export default f.toTemplate() * ``` **/ toTemplate(): { streams: Record; tasks: { [x: string]: FunctionTaskDescription; }; }; } /** * A block of code, such as the "body" of a function, consisting of a series of statements like `let` and `return`, * or even nested blocks of code (using `if` or `while`). The block of code is executed much like that * in typical imperative languages, like JavaScript/TypeScript. * * Generally an empty block of code is injected on which you can use define * statements using the builder pattern (or fluent code pattern) and return the result. * * @category Function * * @example * ```typescript * // Create a function to add two integer streams, equivalent to the Javascript function * // * // function f(a, b) { * // let c = a + b; * // return { c }; * // } * // * // f(1n, 2n) == 3n * * const a = new SourceBuilder("a").value({ value: 1n }) * const b = new SourceBuilder("b").value({ value: 2n }) * * const f = new FunctionBuilder("f") * .input("a", a.outputStream()) * .input("b", b.outputStream()) * .body(block => block * .let("c", vars => Add(vars.a, vars.b)) * .return({ c: vars => vars.c }) * ) * ``` **/ declare class BlockBuilder = Record, Functions extends Record> = Record>, MLs extends Record = {}, Procs extends Record = {}, Return extends null | Record = null | Record> { protected vars: Vars; protected functions: Functions; protected mls: MLs; protected procedures: Procs; protected statements: FunctionStatement[]; protected returns: Return; protected n_loops: number; protected label: string | null; terminal: false; /** @internal */ constructor(vars: Vars, functions: Functions, mls: MLs, procedures: Procs, statements: FunctionStatement[], returns: Return, n_loops: number, label: string | null); /** * Define a new variable in the function and assign a value equal to the result of an East {@link Expression}. * * @category Function * * @example * ```typescript * // Create a function to add two integer streams, equivalent to the Javascript function * // * // function f(a, b) { * // let c = a + b; * // return { c }; * // } * // * // f(1n, 2n) == 3n * * const a = new SourceBuilder("a").value({ value: 1n }) * const b = new SourceBuilder("b").value({ value: 2n }) * * const f = new FunctionBuilder("f") * .input("a", a.outputStream()) * .input("b", b.outputStream()) * .body(block => block * .let("c", vars => Add(vars.a, vars.b)) * .return({ c: vars => vars.c }) * ) * ``` **/ let; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction>(name: Name, value: F): BlockBuilder["type"]>; }, Functions, MLs, Procs, Return>; /** * Reassign the value of an existing variable in scope to the result of an East {@link Expression}. * The new value will be visible in later code. * * @category Function * * @example * ```typescript * // Create a function to sum up elements of an integer array, equivalent to the Javascript function: * // * // function sum(array) { * // let ret = 0n; * // for (const x of array) { * // ret = ret + x; * // } * // return { ret }; * // } * // * // sum([1n, 2n]) == 3n * * const array = new SourceBuilder("array").value({ value: [1n, 2n] }) * * const sum = new FunctionBuilder("sum") * .input("array", array.outputStream()) * .body(block => block * .let("ret", vars => Const(0n)) * .forArray( * vars => vars.array, * (for_block, x) => for_block * .assign("ret", vars => Add(vars.ret, x)) * ) * .return({ ret: vars => vars.ret }) * ) * ``` **/ assign; } & { [K in keyof Procs]: ProcedureEvaluator; }) => { type: SubType; ast_type: AstType; }>(name: Name, value: F): BlockBuilder; /** * Insert a new element into an array, set or dictionary. The collection will be mutated in place. * * For arrays, you can insert the `"first"` or `"last"` element (i.e. push to the front or end of the array). * For sets, you can insert a new key. If the key already exists an error will result. * For dictionaries, you can insert a new key and associated value. If the key already exists an error will result (see also {@link update}). * * @category Function * * @example * ```typescript * // Create a function to insert a new element at the end of an array: * // * // function pushLast(array, x) { * // array.push(x) * // return { array }; * // } * // * // pushLast([1n, 2n], 3n) == [1n, 2n, 3n] * * const array = new SourceBuilder("array").value({ value: [1n, 2n] }) * const x = new SourceBuilder("x").value({ value: 3n }) * * const pushLast = new FunctionBuilder("pushLast") * .input("array", array.outputStream()) * .input("x", x.outputStream()) * .body(block => block * .insert( * vars => vars.array, * "last", * vars => vars.x, * ) * .return({ array: vars => vars.array }) * ) * ``` **/ insert; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction>(collection: C, key: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction["type"]["value"]["key"]>, value: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => { type: SubType["type"]["value"]["value"]>; ast_type: AstType; }): BlockBuilder; insert; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction>(collection: C, key: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction["type"]["value"]>): BlockBuilder; insert; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction>(collection: C, key: 'first' | 'last', value: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => { type: SubType["type"]["value"]>; ast_type: AstType; }): BlockBuilder; /** * Update an existing value in an array or dictionary. The collection will be mutated in place. * * For arrays, you can update any element by integer index (the first element has index `0n`). If the index is out of bounds an error will result. * For dictionaries, you can update any existing key to a new associated value. If the key does not exist an error will result (see also {@link `insert`}). * In both cases, you are given access the existing value when computing the new value. * * @category Function * * @example * ```typescript * // Create a function to increment the first element of an array * // * // function incrementFirst(array) { * // array[0] += 1n; * // return { array }; * // } * // * // incrementFirst([1n, 2n, 3n]) == [2n, 2n, 3n] * * const array = new SourceBuilder("array").value({ value: [1n, 2n, 3n] }) * * const incrementFirst = new FunctionBuilder("incrementFirst") * .input("array", array.outputStream()) * .body(block => block * .update( * vars => vars.array, * vars => Const(0n), * (vars, old_value) => Add(old_value, 1n), * ) * .return({ array: vars => vars.array }) * ) * ``` **/ update; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction>(collection: C, key: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction["type"]["value"]["key"]>, value: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }, old_value: Variable["type"]["value"]["value"]>) => { type: SubType["type"]["value"]["value"]>; ast_type: AstType; }): BlockBuilder; update; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction>(collection: C, key: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction, value: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }, old_value: Variable["type"]["value"]>) => { type: SubType["type"]["value"]>; ast_type: AstType; }): BlockBuilder; /** * Delete an existing element from an array, set or dictionary. The collection will be mutated in place. * * For arrays, you can delete the `"first"` or `"last"` element (i.e. pop the front or end of the array). * For sets, you can delete an existing key. If the key doesn't exist an error will result. * For dictionaries, you can delete an existing key (and associated value). If the key doesn't exist an error will result. * * @category Function * * @example * ```typescript * // Create a function to delete and return the first element of an array: * // * // function popFirst(array, x) { * // const ret = array[0]; * // array.shift(x); * // return { ret, array }; * // } * // * // pop([1n, 2n, 3n]) == { ret: 1n, array: [2n, 3n] } * * const array = new SourceBuilder("array").value({ value: [1n, 2n, 3n] }) * * const popFirst = new FunctionBuilder("popFirst") * .input("array", array.outputStream()) * .body(block => block * .let("ret", vars => Get(vars.array, 0n)) * .delete( * vars => vars.array, * "first", * ) * .return({ ret: vars => vars.ret, array: vars => vars.array }) * ) * ``` **/ delete; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction>(collection: C, key: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction["type"]["value"]["key"]>): BlockBuilder; delete; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction>(collection: C, key: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction["type"]["value"]>): BlockBuilder; delete; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction>(collection: C, key: 'first' | 'last'): BlockBuilder; /** * Delete all elements from an array, set or dictionary. The collection will be mutated in place. * * @category Function * * @example * ```typescript * // Create a function to delete and return the first element of an array: * // * // function clear(array) { * // array.len = 0; * // return { array }; * // } * // * // clear([1n, 2n, 3n]) == { array: [] } * * const array = new SourceBuilder("array").value({ value: [1n, 2n, 3n] }) * * const clear = new FunctionBuilder("clear") * .input("array", array.outputStream()) * .body(block => block * .clear(vars => vars.array) * .return({ array: vars => vars.array }) * ) * ``` **/ clear(collection: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction): BlockBuilder; /** * Evaluate a Boolean {@link Expression} and conditionally execute blocks of code depending * on whether the result is `true` or `false`. * * This method creates function block "builders" which you can add statements to. Defining a block for the `false` branch is optional. * * @category Function * * @example * ```typescript * // Create a function to take the square root of a floating-point number, checking that it is positive, equivalent to the following Javascript code: * // * // function sqrt(x) { * // if (x < 0) { * // throw new Error(`Input cannot be negative, got ${x}`); * // } else { * // return { ret: Math.sqrt(x) }; * // } * // } * // * // sqrt(9.0) == 3.0 * * const x = new SourceBuilder("x").value({ value: 9.0 }) * * const sqrt = new FunctionBuilder("sqrt") * .input("x", x.outputStream()) * .body(block => block * .if( * vars => Less(vars.x, 0.0), * true_block => true_block * .error(vars => StringJoin`Input cannot be negative, got ${vars.x}`) * false_block => false_block * .return({ ret: vars => Sqrt(vars.x) }) * ) * ) * ``` **/ if) => TerminalBlockBuilder, F extends (block: BlockBuilder) => TerminalBlockBuilder>(predicate: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction, true_block: T, false_block: F): TerminalBlockBuilder> extends null ? GetReturns> : GetReturns>>; if) => BlockBuilder | TerminalBlockBuilder, F extends (block: BlockBuilder) => BlockBuilder | TerminalBlockBuilder>(predicate: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction, true_block: T, false_block: F): BlockBuilder> extends null ? GetReturns> : GetReturns>>; if) => BlockBuilder | TerminalBlockBuilder>(predicate: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction, true_block: T): BlockBuilder>>; /** * Evaluate a nullable {@link Expression} and conditionally execute blocks of code depending * on whether the result is `null` or otherwise. * * This method creates function block "builders" which you can add statements to. Defining a block for the non-null branch is optional. In this branch the "unwrapped" value (with a non-nullable East type) is provided for use inside the block, which is useful for satisfying type constraints. * * @category Function * * @example * ```typescript * // Create a function to take the square root of a (nullable) floating-point number, checking that it is not `null`, equivalent to the following Javascript code: * // * // function sqrt(x) { * // if (x === null) { * // throw new Error(`Input cannot be null`); * // } else { * // return { ret: Math.sqrt(x) }; * // } * // } * // * // sqrt(9.0) == 3.0 * * const x = new SourceBuilder("x").value({ value: 9.0, type: Nullable(FloatType) }) * * const sqrt = new FunctionBuilder("sqrt") * .input("x", x.outputStream()) * .body(block => block * .ifNull( * vars => vars.x, * null_block => null_block * .error(vars => StringJoin`Input cannot be negative, got ${vars.x}`) * (non_null_block, non_null_x) => non_null_block * .return({ ret: vars => Sqrt(non_null_x) }) * ) * ) * ``` **/ ifNull; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction, T extends (block: BlockBuilder) => TerminalBlockBuilder, F extends (block: BlockBuilder, value: Variable["type"]>>) => TerminalBlockBuilder>(input: I, true_block: T, false_block: F): TerminalBlockBuilder> extends null ? GetReturns> : GetReturns>>; ifNull; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction, T extends (block: BlockBuilder) => BlockBuilder | TerminalBlockBuilder, F extends (block: BlockBuilder, value: Variable["type"]>>) => BlockBuilder | TerminalBlockBuilder>(input: I, true_block: T, false_block: F): BlockBuilder> extends null ? GetReturns> : GetReturns>>; ifNull) => BlockBuilder | TerminalBlockBuilder>(input: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction, true_block: T): BlockBuilder>>; /** * Evaluate a variant {@link Expression} and conditionally execute blocks of code depending on the variant case/tag.. * * This method creates function block "builders" which you can add statements to. Defining a block for every case/tag is optional. The "unwrapped" associated variant data (with the "unwrapped" East type for that case/tag) is provided for use inside the block. This one of the primary ways to access the associated data within a variant (along with the `Match` expression). * * @category Function * * @example * ```typescript * // Create a function to take the square root of an "optional" floating-point variant, checking that it is not `None`, equivalent to the following Javascript code: * // * // function sqrt(x) { * // match(x, { * // None: () => { * // throw new Error(`Input cannot be .None`); * // }, * // Some: { * // unwrapped_x => return { ret: Math.sqrt(unwrapped_x) }; * // }, * // }) * // } * // * // sqrt(some(9.0)) == 3.0 * * const x = new SourceBuilder("x").value({ value: some(9.0), type: OptionType(FloatType) }) * * const sqrt = new FunctionBuilder("sqrt") * .input("x", x.outputStream()) * .body(block => block * .match( * vars => vars.x, * { * None: block => block * .error("Input cannot be .None"), * Some: (block, unwrapped_x) => block * .return({ ret: Sqrt(unwrapped_x) }) * } * ) * ) * ``` **/ match; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction, T extends { [K in keyof ReturnType["type"]["value"]]?: (block: BlockBuilder, data: Variable["type"]["value"][K]>) => BlockBuilder; }>(variant: V, cases: T): BlockBuilder; /** * Loop over a block of code while a provided Boolean expression evaluates to `true`. The loop when terminate when the expression returns `false`. * * Care must be taken to avoid infinite loops, or the function task may run indefinitely (using up resources on the Elara cluster). * * @category Function * * @example * ```typescript * // Create a function to sum up elements of an integer array, equivalent to the Javascript function: * // * // function sum(array) { * // let ret = 0n; * // let i = 0; * // while (i < array.length) { * // ret = ret + x; * // i = i + 1; * // } * // return { ret }; * // } * // * // sum([1n, 2n]) == 3n * * const array = new SourceBuilder("array").value({ value: [1n, 2n] }) * * const sum = new FunctionBuilder("sum") * .input("array", array.outputStream()) * .body(block => block * .let("ret", vars => Const(0n)) * .let("i", vars => Const(0n)) * .while( * vars => Less(vars.i, Size(vars.array)), * while_block => while_block * .assign("ret", vars => Add(vars.ret, x)) * .assign("i", vars => Add(vars.i, 1n)) * ) * .return({ ret: vars => vars.ret }) * ) * ``` **/ while) => BlockBuilder | TerminalBlockBuilder>(predicate: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction, block: B): BlockBuilder>>; /** * Loop over all entries in an array, providing the value (and optionally the array index, starting at `0n`) to a block of code to execute. * * @category Function * * @example * ```typescript * // Create a function to sum up elements of an integer array, equivalent to the Javascript function * // * // function sum(array) { * // let ret = 0n; * // for (let x of array) { * // ret = ret + x; * // } * // return { ret }; * // } * // * // sum([1n, 2n]) == 3n * * const array = new SourceBuilder("array").value({ value: [1n, 2n] }) * * const sum = new FunctionBuilder("sum") * .input("array", array.outputStream()) * .body(block => block * .let("ret", vars => Const(0n)) * .forArray( * vars => vars.array, * (for_block, x, index) => for_block * .assign("ret", vars => Add(vars.ret, x)) * ) * .return({ ret: vars => vars.ret }) * ) * ``` **/ forArray; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction, B extends (block: BlockBuilder, value: Variable["type"]["value"]>, key: Variable) => BlockBuilder | TerminalBlockBuilder>(collection: C, block: B): BlockBuilder>>; /** * Loop over all entries in a dictionary, providing the value (and optionally the dictionary key) to a block of code to execute. * * Note that the iteration order is sorted by key value. * * @category Function * * @example * ```typescript * // Create a function to sum up elements of a dictionary of integer values, equivalent to the Javascript function: * // * // function sumDict(dict) { * // let ret = 0n; * // for (let [key, value] of dict) { * // ret = ret + value; * // } * // return { ret }; * // } * // * // sumDict(new Map([["a", 1n], ["b", 2n]]) == 3n * * const dict = new SourceBuilder("dict").value({ new Map([["a", 1n], ["b", 2n]]) }) * * const sum = new FunctionBuilder("sum") * .input("dict", dict.outputStream()) * .body(block => block * .let("ret", vars => Const(0n)) * .forDict( * vars => vars.array, * (for_block, value, key) => for_block * .assign("ret", vars => Add(vars.ret, value)) * ) * .return({ ret: vars => vars.ret }) * ) * ``` **/ forDict; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction, B extends (block: BlockBuilder, value: Variable["type"]["value"]["value"]>, key: Variable["type"]["value"]["key"]>) => BlockBuilder | TerminalBlockBuilder>(collection: C, block: B): BlockBuilder>>; /** * Loop over all keys in a set, providing the key to a block of code to execute. * * Note that the iteration order is sorted by key value. * * @category Function * * @example * ```typescript * // Create a function to log all the elements of a set, equivalent to the Javascript function * // * // function logAll(set) { * // for (let x of set) { * // console.log(`Set contains: ${x}`); * // } * // return { }; * // } * // * // logAll(new Set(["a", "b"])); * * const set = new SourceBuilder("set").value({ value: new Set(["a", "b"]) }) * * const logAll = new FunctionBuilder("logAll") * .input("set", set.outputStream()) * .body(block => block * .forSet( * vars => vars.set, * (for_block, x) => for_block * .log(vars => StringJoin`Set contains: ${x}`) * ) * .return({ ret: vars => vars.ret }) * ) * ``` **/ forSet; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction, B extends (block: BlockBuilder, key: Variable["type"]["value"]>) => BlockBuilder | TerminalBlockBuilder>(collection: C, block: B): BlockBuilder>>; /** * Continue from the beginning of the next iteration of a `while`, `forArray`, `forSet` or `forDict` loop. * * This method expects the `BlockBuilder` for the loop you wish to continue. Note that it is possible to continue from within nested loops. * * @category Function * * @example * ```typescript * // Create a function to sum up the positive elements of an integer array, equivalent to the Javascript function * // * // function sum(array) { * // let ret = 0n; * // for (let x of array) { * // if (x < 0) { * // continue; * // } * // ret = ret + x; * // } * // return { ret }; * // } * // * // sum([1n, 2n]) == 3n * * const array = new SourceBuilder("array").value({ value: [1n, 2n] }) * * const sum = new FunctionBuilder("sum") * .input("array", array.outputStream()) * .body(block => block * .let("ret", vars => Const(0n)) * .forArray( * vars => vars.array, * (for_block, x, index) => for_block * .if( * vars => Less(vars.x, 0n), * block => block * .continue(for_block) * ) * .assign("ret", vars => Add(vars.ret, x)) * ) * .return({ ret: vars => vars.ret }) * ) * ``` **/ continue(block: BlockBuilder): BlockBuilder; /** * Break from a `while`, `forArray`, `forSet` or `forDict` loop. * * This method expects the `BlockBuilder` for the loop you wish to terminate. Note that it is possible to break from within nested loops. * * @category Function * * @example * ```typescript * // Create a function to sum up elements of an integer array, equivalent to the Javascript function: * // * // function sum(array) { * // let ret = 0n; * // let i = 0; * // while (true) { * // ret = ret + x; * // i = i + 1; * // if (i >= array.length) { * // break; * // } * // } * // return { ret }; * // } * // * // sum([1n, 2n]) == 3n * * const array = new SourceBuilder("array").value({ value: [1n, 2n] }) * * const sum = new FunctionBuilder("sum") * .input("array", array.outputStream()) * .body(block => block * .let("ret", vars => Const(0n)) * .let("i", vars => Const(0n)) * .while( * vars => Const(true), * while_block => while_block * .assign("ret", vars => Add(vars.ret, x)) * .assign("i", vars => Add(vars.i, 1n)) * .if( * vars => GreaterEqual(vars.i, Size(vars.array)), * block => block * .break(while_block) * ) * ) * .return({ ret: vars => vars.ret }) * ) * ``` **/ break(block: BlockBuilder): BlockBuilder; /** * Produce a log message that will be attached to the executing function task. * * @category Function * * @example * ```typescript * // Create a function to take the square root of a floating-point number, checking that it is positive, equivalent to the following Javascript code: * // * // function sqrt(x) { * // if (x < 0) { * // console.log(`Input cannot be negative, got ${x}`); * // } * // return { ret: Math.sqrt(x) }; * // } * // * // sqrt(9.0) == 3.0 * * const x = new SourceBuilder("x").value({ value: 9.0 }) * * const sqrt = new FunctionBuilder("sqrt") * .input("x", x.outputStream()) * .body(block => block * .if( * vars => Less(vars.x, 0.0), * true_block => true_block * .log(vars => StringJoin`Input cannot be negative, got ${vars.x}`) * ) * .return({ ret: vars => Sqrt(vars.x) }) * ) * ``` **/ log(message: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction): BlockBuilder; /** * Produce a warning that will be attached to the executing function task along with a message. The task will emit the messsage and continue executing. * * @category Function * * @example * ```typescript * // Create a function to take the square root of a floating-point number, checking that it is positive, equivalent to the following Javascript code: * // * // function sqrt(x) { * // if (x < 0) { * // console.warn(`Input cannot be negative, got ${x}`); * // } * // return { ret: Math.sqrt(x) }; * // } * // * // sqrt(9.0) == 3.0 * * const x = new SourceBuilder("x").value({ value: 9.0 }) * * const sqrt = new FunctionBuilder("sqrt") * .input("x", x.outputStream()) * .body(block => block * .if( * vars => Less(vars.x, 0.0), * true_block => true_block * .warn(vars => StringJoin`Input cannot be negative, got ${vars.x}`) * ) * .return({ ret: vars => Sqrt(vars.x) }) * ) * ``` **/ warn(message: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction): BlockBuilder; /** * If this statement is executed, produce an error that will stop the executing function task from completing along with an error message. * * @category Function * * @example * ```typescript * // Create a function to take the square root of a floating-point number, checking that it is positive, equivalent to the following Javascript code: * // * // function sqrt(x) { * // if (x < 0) { * // throw new Error(`Input cannot be negative, got ${x}`); * // } else { * // return { ret: Math.sqrt(x) }; * // } * // } * // * // sqrt(9.0) == 3.0 * * const x = new SourceBuilder("x").value({ value: 9.0 }) * * const sqrt = new FunctionBuilder("sqrt") * .input("x", x.outputStream()) * .body(block => block * .if( * vars => Less(vars.x, 0.0), * true_block => true_block * .error(vars => StringJoin`Input cannot be negative, got ${vars.x}`) * false_block => false_block * .return({ ret: vars => Sqrt(vars.x) }) * ) * ) * ``` **/ error(message: (vars: Vars, procs: { [K in keyof MLs]: MLEvaluator; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction): BlockBuilder; /** * Elara functions can be defined recursively using optional input and output streams. This method results in this function "calling" another function (either itself or one defined by the `function` method) and terminating immediately. The optional inputs to the called function, as defined by `recursiveInput`, must be provided. * * Note that this interface is experimental and not recommended for usage at this time. It is up to the user to ensure termination and avoid infinite recursion and excessive usage of the Elara cluster. **/ recurse; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction; }>(name: Name, inputs: F): TerminalBlockBuilder; /** * Return a set of results from the function. Each result will populate an individual datastream. * * @category Function * * @example * ```typescript * // Create a function to add two integer streams, equivalent to the Javascript function * // * // function f(a, b) { * // let c = a + b; * // return { c }; * // } * // * // f(1n, 2n) == 3n * * const a = new SourceBuilder("a").value({ value: 1n }) * const b = new SourceBuilder("b").value({ value: 2n }) * * const f = new FunctionBuilder("f") * .input("a", a.outputStream()) * .input("b", b.outputStream()) * .body(block => block * .let("c", vars => Add(vars.a, vars.b)) * .return({ c: vars => vars.c }) * ) * ``` **/ ["return"]; } & { [K in keyof Procs]: ProcedureEvaluator; }) => EastFunction>>(outputs: F): TerminalBlockBuilder<{ [K in keyof F]: ReturnType["type"]; }>; } /** @internal */ declare class TerminalBlockBuilder = null | Record> { protected statements: FunctionStatement[]; protected returns: Return; protected n_loops: number; protected label: string | null; terminal: true; /** @internal */ constructor(statements: FunctionStatement[], returns: Return, n_loops: number, label: string | null); } /** @internal */ type GetReturns = T extends BlockBuilder, Record>, Record, Record, infer Returns> ? Returns : T extends TerminalBlockBuilder ? Returns : never; export {};