Block Params S1
- Stage: Stage 1
- Status: Active
- ECMAScript edition: —
- Synchronized: Aug 28, 2026
- 中文译文 · Source repository
This proposal explores a syntactic simplification that allows omitting parentheses around the last parameter when it is a lambda in function calls, enabling userland DSLs. The proposal discusses use cases like control flow, builders, layout, and configuration, and explores issues like Tennent's Correspondence Principle, forward compatibility, completion values, and scoping. A polyfill exists but is not yet recommended for production.
The README below comes from the upstream repository and may contain outdated stage or status metadata. Use the proposal details above as the current source of truth.
Early feedback from @adamk, @domenic, @slightlyoff, @erights, @waldemarhowart, @bterlson and @rwaldron (click here to send feedback).
Block Params
This is a very early stage 1 exploration of a syntactical simplication (heavily inspired by Kotlin, Ruby and Groovy) that enables domain specific languages to be developed in userland.
It is a syntactic simplification that allows, on function calls, to omit parantheses around the last parameter when that's a lambda.
For example:
Functions that take just a single block parameter can also be called parentheses-less:
We want to enable the ability to nest block params (e.g. to enable paired block params like select/when, builders and layout), and we are currently exploring using a sigil (e.g. possibly consistent with the bind operator ::) to refer to the parent block param:
Arguments can be passed to the block param:
To preserve Tennent's Corresponde Principle, we are exploring which restrictions apply inside the block param (e.g. because these are based on arrow functions, break and continue aren't available as top level constructs and return may behave differently).
While a simple syntactical simplification, it enables an interesting set of userland frameworks to be built, taking off presure from TC39 to design them (and an extensible shadowing mechanism that enables to bake them natively when/if time comes):
Here are some interesting scenarios:
- flow control (e.g. lock, unless, guard, defer, foreach, select)
- builders (e.g. map, dot, data)
- layout (e.g. html, android)
- configuration (e.g. node, makefiles)
- others (e.g. regexes, graphql, testing)
And interesting applications in DOM construction:
This is early, so there are still a lot of areas to explore (e.g. continue and break, return, bindings and this) as well as strategic problems to overcome (e.g. forward compatibility) and things to check feasibility (e.g. completion values).
There is a polyfill, but I wouldn't say it is a great one quite yet :)
It is probably constructive to start reading from the prior art section.
Use cases
A random list of possibilities collected from kotlin/groovy (links to equivalent idea in kotlin/groovy at the headers), somewhat sorted by most to least compelling.
flow control
lock
Perl's unless
Swift's guard
- aka assert
Swift's defer
- aka run
C#'s foreach
VB's select
C#'s using
builders
maps
dot
custom data
layout
kotlin's templates
android
Configuration
node
makefiles
Misc
regexes
graphql
testing
Applications
One of the most interesting aspects of this proposal is that it opens the door to statement-like structures inside expressions, which are most notably useful in constructing the DOM.
Template Literals
For example, instead of:
or
One could write:
JSX
For example, instead of:
One could write:
Extensions
This can open a stream of future extensions that would enable further constructs to be added. Here are some that occurred to us while developing this.
These are listed here as extensions because I believe we don't corner ourselves by shipping without them (i.e. they can be sequenced independently).
chaining
From @erights:
To enable something like
You'd have to chain the various things together. @erights proposed something along the lines of making the chains be passed as parameters to the first function. So, that would transpile to something like
Another notable example may be to enable try { ... } catch (e) { ... } finally { ... }
functization
From @erights:
To enable control structures that repeat over the lambda (e.g. for-loops), we would need to re-execute the stop condition. Something along the lines of:
We would want to turn expr into a function that evaluates expr so that it could be re-evaluated multiple times. For example
TODO(goto): should we do that by default with all parameters?
Areas of Exploration
These are some areas that we are still exploring.
Tennent's Correspondence Principle
To preserve tennent's correspondence principle as much as possible, here are some considerations as we decide what can go into block params:
returnstatements inside the block should either throwSyntaxError(e.g. kotlin) or jump to a non-local return (e.g. kotlin's inline functions non-local returns)break,continueshould either throwSyntaxErroror control the lexical flowyieldcan't be used as top level statements (same strategy as() => { ... })throwworks (e.g. can be re-thrown from function that takes the block param)- the completion values are used to return values from the block param (strategy borrowed from kotlin)
- as opposed to arrow functions,
thiscan be bound.
Forward Compatibility
If we bake this in, do we corner ourselves from ever exposing new control structures (e.g. unless () {})?
That's a good question, and we are still evaluating what the answer should be. Here are a few ideas that have been thrown around:
- user defined form shadows built-in ones
- sigils (e.g. for! {})
In this formulation, we are leaning towards the former.
It is important to note that the current built-in ones can't be shadowed because they are reserved keywords. So, you can't override for or if or while (which I think is working as intended), but you could override ones that are not reserved keywords (e.g. until or match).
Completion Values
Like Kotlin, it is desirable to make the block params return values to the original function calling them. We aren't entirely sure yet what this looks like, but it will most probably borrow the same semantics we end up using in do expressions and other statement-like expressions.
scoping
There are certain block params that go together and they need to be somehow aware of each other. For example, select and when would ideally be described like this:
How does when get resolved?
The global scope? If so, how does it connect with select to test bar with foo?
From select? If so, how does it avoid using the this reference and have with-like performance implications? perhaps @@this?
return
From @bterlson:
It would be great if we could make return to return from the lexically enclosing function.
Kotlin allows return from inlined functions, so maybe semantically there is a way out here.
One challenge with return is for block params that outlive the outer scope. For example:
Note that Java throws a TransferException when that happens. SmallTalk allows that too, so the intuition is that this is solvable.
continue, break
continue and break are interesting because their interpretation can be defined by the user. For example:
Whereas:
It is still unclear if this can be left as an extension without cornering ourselves.
We are exploring other alternatives here.
bindings
From @bterlson:
There are a variety of cases where binding helps. For example, we would want to enable something like the following:
foreach (map) do (key, value) { ... } to be given by the foreach function implementation.
To be equivalent to:
Exactly which keyword we pick (e.g. in or with or : etc) and its position (e.g. foreach (item in array) or foreach (array with item)) TBD.
Another alternative syntax could be something along the lines of:
Or
We probably need to do a better job at exploring the design space of use cases before debating syntax, hence leaving this as a future extension.
Polyfill
This is currently polyfilled as a transpiler. You can find a lot of examples here.
npm install -g @docscript/docscript
Tests
npm test
Status
You really don't want to use this right now. Very early prototype.
Prior Art
The following is a list of previous discussions at TC39 and related support in other languages.
TC39
- block lambdas and discussion
- Allen's considerations on break and continue
- javascript needs blocks by @wycats