Closed nmn closed 7 months ago
Further explaining my proposal by explaining what the examples would desugar to:
function lookFor(res, status, url) {
return match (res) {
// ...
when ({ status: let status, destination: const url }) if (300 <= status && status < 400):
handleRedirect(url)
// ...
}
}
De-sugars to:
function lookFor(res, status, url) {
let _status, _url;
if (Object.hasOwn(res, 'status')
&& (_status = res.status, true)
&& Object.hasOwn(res, 'destination')
&& (_url = destination, true)
&& 300 <= status
&& status < 400) {
let status = _status, url = _url;
return handleRedirect(url)
}
}
match (creature) {
when (Person { let name, let age }): // ...a
when (let animal = Animal { }): match (animal) {
when (Dog { let name, let breed }): // ...dog-stuff
when (Cat { let name, coatColor: let color }): // ...cat-stuff
// ...
}
}
De-sugars to:
do {
if (creature instanceof Person
&& Object.hasOwn(creature, 'name')
&& Object.hasOwn(creature, 'age')) {
let name = creature.name,
age = creature.age;
// ...a
} else if (creature instanceof Animal) {
let animal = creature;
do {
if (animal instanceof Dog
&& Object.hasOwn(animal, 'name')
&& Object.hasOwn(animal, 'breed')) {
let name = animal.name, breed = animal.breed;
// ...dog-stuff
} else if (animal instanceof Cat
&& Object.hasOwn(animal, 'name')
&& Object.hasOwn(animal, 'coatColor')) {
let name = animal.name, color = animal.coatColor;
// ...cat-stuff
}
}
}
}
I believe most of your concerns are considered in #293. please take a look thanks!
Yeah, I'm so sorry that you went to all this trouble to write out this big proposal, when we'd already decided to do more or less exactly this in #293. ;_; It'll be committed into the repo in a little while, when we finish up the edits.
I know! Our proposals are almost the same.
I want to callout one possible enhancement that I proposed here:
The matcher pattern for objects that are instances of a class:
Person { name }
- Would work exactly the same as { name }
but it would also check that the value being matches is an instance of Person
.
Ah, that's already handled by the existing patterns - Person and {name}
just runs both patterns, one invoking the Person
custom matcher (defaulting to an instance check) and the other checking the properties.
Didn't think of that. Good to know.
By the way, won't using "and" and "or" be problematic? Do we need to use symbols for unambiguous parsing?
Since they’re surrounded by spaces i don’t anticipate any issues.
Motivation
When the patterns used within a match-when expression contain non-literal identifiers, the syntax can look ambiguous. The proposal chooses to always interpret things more like "destructuring" and creates a new binding rather than treat it like a reference.
Let me explain by using the first code sample in the README. Instead of looking at the just the
match
expression itself, consider its use within a function.Based on this proposal
when ({ status, destination: url })
creates two new variablesstatus
andurl
which shadow the function arguments of the same name. This is both confusing and makes it harder to use variables in match expression patterns.Current Solution
In order to get around the problem, the proposal chooses to use the
${}
syntax to "interpolate" values into the match expression.This solution has a few problems:
${}
doesn't make a lot of sense as aninterpolation
mechanic.{ key }
both checks thatkey
exists and makes a bindingkey
that can be used to read.Proposed Solution
My proposal is to make creating new binding more explicit instead. Here's how the original example would be written instead:
The short point of this proposal is that whenever a part of the pattern is to be captured as a variable or constant binding, a
let
orconst
must be used explicitly to do so.Here are all the examples re-written with this in mind:
and
andor
expressions.I'm not particularly fond of introducing new syntax for
and
andor
in Javascript as it doesn't follow any of the syntactic rules already in Javascript. Instead, I would propose the following:Next let's talk about Array length checking. I disagree that
[page]
should only match an array with a single element by default.To be consistent with Javascript as a whole, we should match as long as the elements in the pattern exists. Instead, we should be looking for
undefined
which is what you get when you try to read an element of an array that doesn't exist.This pattern, assumes that you wouldn't explicitly add
undefined
values within your Array, but that should usually be the case anyway. (We can also usevoid
instead ofundefined
if that makes this pattern more viable:If not, we can do this:
The syntax change should apply to
with
as well:match (arithmeticStr) { when (/(?\d+) + (?\d+)/): process(left, right);
when (/(\d+) * (\d+)/ with [let _, let left, let right]): process(left, right);
default: ...
}
Although this entire example feels like something that extends the JS syntax too much IMO.
Interpolations wouldn't need to exist:
Custom matcher protocol interpolations wouldn't be needed. Instead this is how we could do the Option type instead:
At a risk of putting too much in this issue, I propose the following syntax for matching objects that are instances of particular classes:
<ClassName> { <...keys to match...> }
This is already how various testing tools print objects that instances of classes.
This would also make the built-in matchers much simpler:
BUT:
1 instanceof Number
isn't true in Javascript and so I don't think the above should actually work. (This makes me sad, but I believe being consistent is more important than adding the convenience of making the aboce work). Instead we would need some kind of pattern for matching oftypeof
. Without introducing any new syntax the above would need to be written as:Matching Fetch Responses:
Redux Reducer Example:
JSX example:
Advantages of this proposal
Here are some reasons I think the proposal above improves the match expression:
.equals()
method of most test runners. Simple object and array equality where the rules mostly match those of Destructuring and Spreading is what we should aim to do with this propoal.Further Possible Enhancements
Restating Syntax for
instanceof
checksIn the general case, we should be able to match any arbitrary Object by its keys and values:
However, when we want to match objects that are instances of classes, things can quickly become wordy. So I proposed the following syntax:
Using the
Class { ...keys... }
syntax is already familiar to Javascript developers and I don't think it complicates anything.Punning Key Names
You may have noticed that when capturing the value of keys I wrote
{ name: let name }
in my examples. I think that the initial version of this API should enforce this pattern and disallow any object key punning. The pattern{ name }
should simply check for the existence of thename
key and nothing else.As a possible enhancement, we could allow both checking for the existence of key and capturing its value without repeating it:
{ let name }
This still keeps the patterns reasonably readable IMO, so it's worth considering if this keeps a good balance between readability and conciseness.
An extension to the Switch statement
I like that
match
is an expression. But for completeness we should add a small extension to the Switch statement as well.This would world exactly like the
match
expression except for the fact that it is a statement instead.Depending on the various tradeoffs when it comes to adding new syntax, it might even make sense to reuse the
case
keyword along with theis
keyword which is also part of this larger proposal:Prior Art
The Swift Language uses similar patterns in it's own
switch
statement.