Parameters

Status: implemented

  • Fixed names bind positional arguments; missing arguments are undefined. Extra arguments are permitted. The rest parameter is the array of arguments after the fixed prefix. Each invocation has its own rest array, and repeated reads within it return the same array. Parameters may shadow outer names, but must be distinct and cannot collide with body declarations. Each one, fixed or rest, is a name by the binding rule: (eval) => 1 and (...let) => 1 are refused, while (from, then) => [from, then] is a function.
  • A default, = value after a fixed parameter, a name or a pattern, is the parameter's value where its argument is undefined — missing, or passed as undefined — and the argument where it is anything else, null included. The default is evaluated only where it is taken, at each call: (a = {}) => a answers a fresh object at each call, and (a = null.x) => a throws only when called without a. Where it is taken, it is evaluated at the call, before the body, whether or not the body goes on to read the parameter: (a, b = null.x) => a === 5 ? b : a throws when called without b, f(6) included, as JavaScript's does, the parameter being an entry of the body as a const is. It is resolved in the function's own scope, before the body: it reads the scopes around the function, through the function's frame as the body does, the function itself, and the parameters before it, (a, b = a) => b. A read of a later parameter or of the parameter itself, which JavaScript throws on when the default is taken, is refused rather than read from a scope around the function: const not found where no scope around binds the name, and capture shadowed at the parameter where one does, a read inside a function the default writes included. A body const that shadows a name a default read is refused the same way, capture shadowed, as one that shadows a name the body read is. The parameters from the first default on, and the rest parameter after them, are read from the rest array, so the graph needs no node of its own: (a = 5) => a is (...r) => r[0] === undefined ? 5 : r[0], the outputs writing that lowered form (default parameters).
  • f.length is the number of fixed parameters, including unused ones and counting a pattern as one, before the first parameter with a default, as JavaScript's is: ((a, b = 1, c) => 0).length is 1. A default inside a destructuring pattern does not end the count: (({ a = 5 }) => a).length is 1, while a default on the whole parameter, (({ a = 5 } = {}) => a).length, is 0. The first form still throws for an omitted or null argument; the second permits an omitted argument and still throws for null (destructuring). Rest adds zero. A function has at most 16 fixed parameters, every one written counted, defaulted or not: a 17th is a compile error, and an EDAG function whose length is above 16 is refused by every writer. Wider data reads better as an array or an object, and a rest parameter still takes any number of arguments. The EDAG interpreters retain the function's length, body and evaluated captures as represented values; conversion to ordinary runtime callables happens at an explicit target compile/load boundary (EDAG values).
  • An empty parameter list binds no name at all, so a body written under one cannot reach its arguments: the arguments array is named by the parameter and by nothing else, and a word the list does not spell is unbound here exactly as any other unbound word is. Nothing else distinguishes the two lists. () => 1 and (...args) => 1 denote the one function, the one node ['=>', 0, [], 1] — though each arrow written is a function of its own (below) — and a body const may take the name a parameter would have taken, there being no parameter to collide with.

Examples

The compiler demo offers the programs parameters in module.f.js, each with its real output.

The demo on this page runs sample.f.js, this page's example as a function of one number — a fixed parameter, a default and a rest parameter — with every output fjs compile makes of it: the expression graph (EDAG), the Rust and the WebAssembly that Rust builds to. Its Run button calls the default export with 1 and 4, as JavaScript and as WebAssembly, and shows what each call returned or threw.

const f = (x, y = 10, ...rest) => [x, y, rest.length];
export default (...a) => [f(a[0]), f(a[0], a[0] * 2, 7, 8)];

Reference

Demo

In FunctionalScript

fjs compile accepts this module, and it runs as JavaScript and as WebAssembly alike.

A FunctionalScript module and what fjs compile makes of it: the expression graph it lowers to, the Rust it compiles to on the NaNVM runtime, and the WebAssembly that Rust builds to. Run calls its default export with 1, then 4, first as the JavaScript it already is and then as WebAssembly, and shows what each call returned or threw and how long one call takes, timed over 1000 calls.

FunctionalScript source:

/**
 * The example of [`README.md`](./README.md) (Parameters) as a function of
 * one number, for the page's WASM demo: a fixed parameter, a default and a
 * rest parameter.
 *
 * @module
 */

/** @type {(x: number, y?: number, ...rest: readonly number[]) => readonly number[]} */
const f = (x, y = 10, ...rest) => [x, y, rest.length];
/** @type {(...a: readonly number[]) => unknown} */
export default (...a) => [f(a[0]), f(a[0], a[0] * 2, 7, 8)];

Expression graph (EDAG) that fjs compile lowers it to:

=>lengthframe 0body0=>[]=>lengthbody1[][]01[]012arg 0?:.()callee...frame 0[]()callee...frame 0[]?:condthenelse===10..objkey.slice()"length"[]0[]0123.*78===leftright.undefined.()objkey...rest"slice"[].objidxrest0.objidxrest0*leftright.2.objidxrest0[]01.objidxrest0

The same graph, as fjs compile writes it to a .edag.data.js file:

const $0=["rest"];const $1=[".",$0,0];const $2=["frame",0];const $3=["rest"];export default ["{}",[[":","default",["=>",0,[["=>",1,[],["[]",[["arg",0],["?:",["===",$1,["undefined"]],10,$1],[".",[".",$0,"slice",["|()",[1]]],"length"]]]]],["[]",[["()",$2,[[".",$3,0]]],["()",$2,[[".",$3,0],["*",[".",$3,0],2],7,8]]]]]]]];

Rust that fjs compile writes from it, on the NaNVM runtime:

// @generated by `fjs compile`. Do not edit: recompile the source module instead.

use nanvm_lib::vm::unstable::{f64_any, strict_eq, string_any, string_key};
use nanvm_lib::vm::{Any, IStaticFunction, Nullish, ToAny, ToArray, ToObject};

#[rustfmt::skip]
#[allow(clippy::if_same_then_else)]
pub fn module<A: IStaticFunction>(runtime: Any<A>) -> Result<Any<A>, Any<A>> {
    let _b9: Any<A> = A::static_function(|_self, _args| {
        let _b514: Any<A> = _args.rest(1).to_any();
        Ok([_args.argument(0), {
            let _b2: Any<A> = (Any::dot(_b514.clone(), f64_any(0x0000000000000000)).end())?;
            if Any::<A>::to_boolean((strict_eq(_b2.clone(), Nullish::Undefined.to_any()))?) { f64_any(0x4024000000000000) } else { _b2.clone() }
        }, (Any::dot((Any::dot(_b514.clone(), string_any("slice")).end_call(&A::frame(_self)[0], || Ok([f64_any(0x3ff0000000000000)].to_array().to_any())))?, string_any("length")).end())?].to_array().to_any())
    }, 1, [runtime.clone()].to_array(), Some("($0,...$1)=>[$0,(()=>{const $2=$1[0];return $2===undefined?10:$2;})(),$1.slice(1).length]")).to_any();
    Ok([(string_key("default"), A::static_function(|_self, _args| {
        let _b1044: Any<A> = _args.rest(0).to_any();
        Ok([(Any::call(A::frame(_self)[0].clone(), [(Any::dot(_b1044.clone(), f64_any(0x0000000000000000)).end())?].to_array().to_any()))?, (Any::call(A::frame(_self)[0].clone(), [(Any::dot(_b1044.clone(), f64_any(0x0000000000000000)).end())?, ((Any::dot(_b1044.clone(), f64_any(0x0000000000000000)).end())? * f64_any(0x4000000000000000))?, f64_any(0x401c000000000000), f64_any(0x4020000000000000)].to_array().to_any()))?].to_array().to_any())
    }, 0, [_b9.clone(), runtime.clone()].to_array(), Some("(...$1)=>[$0($1[0]),$0($1[0],$1[0]*2,7,8)]")).to_any())].to_object().to_any())
}

WebAssembly text (WAT) of the functions compiled from this source, without the NaNVM runtime:

Refused:

File size 159723 exceeds maximum allowed size of 131072 bytes: 'nanvm/demos/wasm/parameters.wat.marked'

Emergent Testing

Idle. Press Run to start the suite.

  • ./limit/proof.f.mjs
  • ./proof.f.mjs