For the purposes of this investigation, I am breaking down the various statements within ECMAScript into several broad categories:
- Leaf Statements:
- Declaration Statements -
class,function,var,let,const,import,export - Abrupt Completion Statements -
throw,return,break,continue - Other -
debugger
- Declaration Statements -
- Branching Statements:
- Control Flow Statements -
if,switch,try - Iteration Statements -
for..in,for..of,for,while,do..while - Other -
with,block
- Control Flow Statements -
Leaf statements are those statements that do not contain an embedded Statement and generally exist either to add a declaration or to perform a single action.
class declaration statements already have an expression form.
function declaration statements already have an expression form.
A missing capability in JavaScript for functional programming is the ability to introduce an intermediate binding as part
of an expression. For example, Haskell provides the let ... in ... expression, allowing you to introduce a variable as
part of an expression:
f :: s -> (a,s)
f x =
let y = ... x ...
in yWe could consider allowing variable declarations in an expression context, given the following rules:
varexpressions are hoisted, just asvarstatements.letandconstexpressions remain block scoped, just asletandconststatements.- This includes TDZ.
- A
var,let, orconstexpression may only declare one variable. Any comma (,) is treated as part of a comma expressions. - Add a lookahead restriction to ExpressionStatement for
varandconst. Lookahead forletmay be trickier.
Example:
const x = a && (let b = a.b) && b.c;import statements already have an expression form in the form of import(...).
export statements do not have an expression form. Further investigation is necessary to determine whether this would make sense.
throw statements already have a proposal for an expression form, allowing the following motivating use cases:
- Parameter initializers
function save(filename = throw new TypeError("Argument required")) { }
- Arrow function bodies
lint(ast, { with: () => throw new Error("avoid using 'with' statements.") });
- Conditional expressions
function getEncoder(encoding) { const encoder = encoding === "utf8" ? new UTF8Encoder() : encoding === "utf16le" ? new UTF16Encoder(false) : encoding === "utf16be" ? new UTF16Encoder(true) : throw new Error("Unsupported encoding"); }
- Logical operations
class Product { get id() { return this._id; } set id(value) { this._id = value || throw new Error("Invalid value"); } }
As with throw statements, there exists a possible benefit for allowing return in an expression position:
function checkOpts(opts) {
let x = opts?.x ?? return false;
/* ...do something with x... */
}ECMAScript does currently support a return-like behavior in an expression via generator functions, yield, and generator.return().
However, this interaction is somewhat esoteric.
It could be valuable to allow continue and break in an expression position. Consider the following:
for (const x of array) {
const y = x.y ?? continue; // continue if `x.y` is not present.
}Branching statements are those statements that contain an embedded Statement. This category includes all control-flow statements,
iteration statements, with statements, and blocks. For any of these statements, it becomes much more complicated to determine what
constitutes the result of the expression. As a result, we will need to more fully investigate the semantics of any expression form
for these statements.
Currently there are several questions we need to consider:
- How do we define the result for the expression?
- Will there be a mechanism for an early result in these expressions, similar to existing abrupt completions and shortcutting?
- How do
return,break,continue,await, andyieldbehave in these expressions?
if statements already have an expression construct in the form of a conditional expression ... ? ... : .... While it could be
feasible to allow if as an expression, its possible such a construct could be confusing.
There is no existing expression form for switch. The closest existing form would be leveraging a series of conditional expressions.
A possible approach to switch as an expression form is the match expression proposal.
There is no existing expression form for try. There are several more specific expression forms that are currently under proposal
that cover some of the use cases for a try expression, such as Optional Chaining (?.) and Nullary Coalesce (??).
In the API space we already have an expression for asynchronous try ... catch in the form of Promise.prototype.catch, and we
will soon also haev an expression for asynchronous try ... finally in the form of Promise.prototype.finally. However, we have no such
API for synchronous try ... catch ... finally.
Further investigation is needed to determine if a try expression is viable.
Iteration statements add more complexity to the branching statements category, as they allow for executing the same embedded Statement repetitively. As a result, we have even more questions to consider:
- Should an iteration expression have a single result (e.g. last value), or multiple results (e.g. an Iterator or an Array)?
- In the "last value" case, what if the last expression is a
continueorbreakexpression? - Do we need expression forms to distinguish between
map-like behavior andreduce-like behavior? - We previously had a proposal for iteration expressions in the form of Array and Generator Comprehensions. Should we revisit these instead?
In general, use of with is discouraged. As such, it may not make sense to have an expression form of with.
block statements already have a proposal for an expression form in the do expressions proposal.
If we progress along the path of adding more expression forms for existing statements, what does this mean for do expressions? In all
likelyhood, do expressions are still valuable in that they:
- Allow for an expression form for Blocks.
- Introduce a block scope for lexical declarations like
let,const, andclass.
In any event, many of the same issues around semantics that need to be resolved for these expression forms also exist in the do
expression proposal and will need to be investigated in depth.