JavaScript Pattern Matching Proposal
github.com
github.com
I mean, that one (of two) motivations for adding a feature to a language is "Terser, more functional handling of Redux reducers", just feels wrong and casual.
However, having gotten used to having them, I can safely say I'll always feel hobbled to work in a language that doesn't have them, so I'm very much in favor of adding them to JS, whether or not this is the proposal that wins out over time.
Not to side-track the issue, but I keep hearing this. I'm wondering if this is true, and if so, how such a statement is verified. I'm currently a full-time JS dev, but have written my own programming languages, have professionally written in C, C++, C#, F#, Ruby, and others, and have dabbled in many other languages (and would love to professionally work in Clojure). [EDIT: Er, to the "non-mainstream" point, I've dabbled in Rust, Clojure, OCaml, Haskell, PureScript, Erlang, Elixir, and Elm. I don't know many JS devs that have done all of that, but most people I know have tinkered in odd non-mainstream languages.]
Most JS devs I know are not single-language devs. Am I just a super anomalous member of the JS community?
... Anyway, back on topic, I'm a big fan of pattern matching, so I'm all for seeing this proposal become a reality.
Curious to know what rankings you are referring to though.
[1]: https://insights.stackoverflow.com/survey/2018/#technology
EDIT: saw the sibling thread.
Other folks are citing Github and Stack Overflow, but they are just reporting on proportions used on their own sites, and I would not be at all surprised to find that both those sites are more popular with web developers than say Java or C/C++/C# or Visual Basic devs (all those languages outrank javascript on Tiobe).
I don't think he is odd in any way.
It's certainly a tired narrative that a lot of the churn in the front-end javascript framework world comes from developers who aren't aware of well-recognized patterns and techniques from other languages. But stereotypes and tired critiques almost always have some basis in reality and it does seem like the JavaScript world is rediscovering a few lessons the hard way rather than learning from the experience of other language communities.
We have differing definitions of friendly. Since I understand types and operator overloading, I know that 1 and "1" are different. I also know that the operator + does traditional math addition and concats Strings. If I didn't, it would probably drive me bonkers that "1" + 1 is "11" in JS instead of 2 or even "2". Having knowledge about types and overloading automatically tells me that String + Int = String.
I'm not saying that JS is bad since it lacks static typing. I'd argue that lack of static typing makes a language tougher to learn rather than more approachable. The barrier of entry is lower, but makes comprehension tougher.
I think it's a bit of a wolf in sheep's clothing. It makes it harder to master, but less intimidating when you're first starting out.
Also, I probably shouldn't have used the term static typing, since the issues I'm talking about are more associated with weak typing rather than dynamic typing.
1. What percentage of JavaScript/front-end web developers are familiar with any other programming languages?
2. What percentage are familiar with a certain specific subset of other programming languages, namely functional or functional-ish languages with pattern matching as a core construct?
And you're treating an answer to (1) as an answer to (2).
So I'd take it more as "You know this mediocre compromise we had to do because JS is missing this core language feature? Well, there you go".
Essentially it's a switch variant that is (a) confusingly named 'match' (bad) (b) returns a value (good?) (c) and is actually a kind of function (wha?), since it (d) supports recursion, but (e) doesn't require break (good), (f) looks like a bag of inline functions but isn't (bad).
How about provide a replacement for switch, with a name that isn't confusing that doesn't require break but otherwise has switch semantics, isn't recursive (we have a mechanism for that) and has the matching behavior suggested? Assuming we stick with 'match' it would look like:
foo = match(x) { case {y: 1} : /* result if x.y === 1 */; ... }
Seems far less confusing.
Would love for this to land :-)
a. Yup, that's what it's called in OCaml, Scala, F#, etc. Might be a little confusing at first, but it's all about finding a "match" for your value, which is different from a guard in an "if" or a "switch".
In an "if", you need to provide a boolean (or a value coercible to a boolean). In a "switch" you don't provide the boolean, but under the hood the switch is just iterating through and comparing your value to all cases: it computes the boolean for you by testing if two values are equal.
Finding a "match" is different, because you're not comparing values, you're also comparing structure. So for example you can do things like
match (v) { case { x, y: 3 } => ... case { y: 3, contents: { name: 'Foo', error }} => ... case { x, y } => ... }
And it wont just compare "v" against all cases, do a deep comparison, check the existance of certain keys and bind variables as necessary so you can use them on `...`.
b. More so than "returning a value" I like to think of it as "resolves to a value". Thinking of it as "returning" can be confusing since it might make it sound too much like a function.
Switch/if are statements. They control flow, and ask the computer to do something. Another statement is assignment; `var a = 0;` "does" something, but it doesn't represent a value.
Expressions like `3 + 2 + x`, `f(x)/2`, and `match({x:3}) {...}` represent a value that hasn't been computed, and then resolve to one.
c. This is why I prefer to think of it as an expression: cause expressions resolve to values. Function calls are expressions too!
d. If you're in a function, all expressions support recursion since you can mix them up. A
e. Awesome.
f. I think that's on purpose, because in a way each case acts a bit like a funciton. You can define "arguments" in it that get bound to values, and they resolve to another value.
Switch can also be implemented as a jump table in some cases.
'x => x = 1' in Javascript, is an expression that resolves to a function. I do understand the difference, and it's important.
'match(x) { {x} => foo }' is not a bag of functions but a new use of the '=>' operator that is confusing/surprising. I can get over it, but why should I have to?
'match(x) { {x} : foo }' would be less surprising.
You're saying 'match({x:1}) { ....}' is an expression, but is 'match(x) { {x} => foo }'?, What does it resolve to? It smells like it either isn't an expression (just as you can't write x = switch(x) {...};) or it's a new kind of function.
Pattern matching can be exhaustive or non-exhaustive. Basically, if a match construct is going to be an expression it has to yield a value* for any input value, ie. there has to be a case arm for every possible input. Usually match blocks have a way to declare a "default" or "else" arm for those values that don't match anything else. Some languages with static type systems can statically figure out whether a given match expression is exhaustive or not. But in JavaScript's case that's most likely not an option. The JS way is probably yielding `undefined` if a value fails to match anything.
* or diverge, ie. loop forever, or exit via nonlocal means such as exceptions.
The kind of people who will jump in and use it will be the people who are used to have it called match.
Better yet, looking up match statement will show how matches work in the functional approach.
I am super happy they are calling it match.
Changing `case` in JS to these entirely different semantics would break pretty much everything, which is likely why a new keyword was needed.
It would also remove the duplicity in the first example shown where match(res) matches on the statusCode field (I guess because `200` is declared as a literal ? while the variable is extracted out. Which also means you cannot match against variable content.
It would also retain a level of readability while the proposed syntax has no readability for the uninitiated. Doing that too much makes the language harder to read and learn for beginners.
My basic stance is that a language does not have to be perfectly concise but a good compromise between readability first and density second.
It is always nice to optimize a language for experts but the majority of users of a language such as javascript are not veterans.
You seem to have quite strong opinions with regards to what's good and what's bad considering you don't have enough experience to recognize language features which have existed for decades outside the JS environment.
So, keep the feedback coming, and don't worry that you'll have to learn this syntax tomorrow. It's still very early, and I wouldn't be surprised if there are at least a couple major revisions before anything like this lands in ECMAScript.
You're right that Babel (and TypeScript!) implement features earlier than they land in the spec, but at least TS does so only when it's almost for sure going to make it in (around stage 3) and people probably shouldn't be using stage 0-2 plugins for anything but experiments, IMO.
Given current speed of iteration I think you can cut that in half. The key will be implementations - Babel plugins help with that.
return match(user, [
[{first: $, middle: $, last: $}, (f, m, l) => f + ' ' + m + ' ' + l],
[{first: $, last: $} , (f, l) => f + ' ' + l],
[_, 'unknown']
]);... so calling Object.keys() is not necessarily going to use a consistent ordering. I think that is why they have to use the more verbose syntax in the other library.
I think you could do something like: {first: $.0, middle: $.1, last: $.2}, (f, m, l) => ... pretty easily, though.
Example:
// simple factorial
const factorial = n => match(n)
.when(0, 1)
.otherwise(n => n * factorial(n - 1));
// walking a tree
class Tree {
constructor(left, right) {
this.left = left;
this.right = right;
}
}
class Node {
constructor(value) {
this.value = value;
}
}
const T = (l, r) => new Tree(l, r);
const N = v => new Node(v);
const walkT = t => match(t)
.when(Node, v => console.log(v.value))
.when(Tree, t => { walkT(t.left); walkT(t.right)})
.otherwise(_ => 'error');
const mapT = (f, t) => match(t)
.when(Node, v => N(f(v.value)))
.when(Tree, t => T(mapT(f, t.left), mapT(f, t.right)))
.otherwise(_ => { throw new Error('error') });
Works also on deeply nested objects. const match = require('pmatch-js')
const _ = require('lodash')
const fizzbuzz = x => match(x)
.when(a => a % 3 == 0 && a % 5 == 0, 'fizzbuzz')
.when(a => a % 5 == 0, 'buzz')
.when(a => a % 3 == 0, 'fizz')
.otherwise(a => a)
console.log(
_.range(1, 101).map(fizzbuzz).join(' ')
)
But maybe you mean something totally different :-)EDIT: Ah I think I know what you mean. Your lib takes an array of tuples to define the patterns. Sorry for the confusion.
[...Array(100).keys()].map(v=>v+1)
A little bit longer but no dependencies. import _range from "lodash/range";Aren't decorators also in the works?
1. Regex style: new match(input, options);
or built in function match(foo, bar);
or like switch match (foo){ cases
}
But, to have the switch statement syntax and return a value seems like a less than good way to implement this.
This is basically what they could translate into without having if/else or nested ternaries.
I'm not sure if it is much better than either but I like it more.
Babel supports it if you want to live on the bleeding edge: https://babeljs.io/docs/plugins/transform-do-expressions/
It would be kind of neat if JSX implicitly used "do" expressions in children expressions, though the ternary operator is already very useful in JSX, and more concise [1]
1. https://prettier.io/playground/#N4Igxg9gdgLgprEAuc0DOMAEBhCB...
Not to mention the if/else expression, a ? b : c
Then you HAVE TO HAVE types, otherwise reasoning about effects in these constructed statements would break your mental spine.
Blasphemy.
Additionally, this would be the first "native" way to do deep equality testing of objects. I can see it being abused to do simple one-off checks that could otherwise have been done more simply in an imperative way.
Do we really need to save the five lines of code at the cost of new syntax? This doesn't seem to prevent any significant amount of toil, since it can be pretty easily unrolled into existing JS syntax.
At least for me, I feel I'd be using this a lot for the boring but common use case of checking for empty arrays and such:
const doSomethingToArr = arr => match (arr) {
[] => whatever,
[x] => whatever,
[x, ...xs] => whatever
}And it isn't four lines saved, but four lines times the number of uses in the entire code base.
Why would you believe that?
And if you do believe that why don't you use a code golfing language?
Should someone now have to point out to you that a million-line function is worse than a ten-line function for calculating fizzbuzz? Is that meaningful discourse in your book?
In general I believe that number of lines of code is correlated with number of bugs, but I would hesitate to say that it is proportional to. Going in with the explicit goal of reducing the number of lines could easily lead to more bugs, not less.
Having less lines can make extremely unreadable code. Readable code with an error is easy to fix, unreadable code is not.
At the very least the ambiguity of doing TWO things (matching AND destructuring) should not be allowed since it changes the outcome based on whether a value is a literal or a variable with the exact same literal assigned to it (at least that's how I understand it from reading the examples)
In a language that started with an object model that has grown a lot of features, trying to jam pattern matching into it after the fact grows a lot of corner cases fast. For instance, consider:
a = {x: 1}
a.toString = function() { return "magic: " + this.x }
Should a pattern match with the string "magic: 1"? Well... probably, because it turns out that "magic: 1" == a is true. But if we do that, how does the pattern match tell a is not in fact a string, if we want to do that? You can answer that question, but the answer will raise further complications of its own. Or you could build a pattern match system around ===, which will raise its own issues. And goodness help the pattern matching syntax if it decides that both are too useful to ignore (a defensible position) and now the syntax needs to support both....I'd say this proposal is about 10 times too short to be useful, and by the time it's long enough to be useful, it'll be clear that almost nobody will want to learn how to take the already sloppy Javascript equality situation and then learn how to lay down a pattern matching language on top of that.
By contrast, since Erlang was built on pattern matching from day one, = and == have almost no such questions about them. It is very clear what they do. I did have to check what 1 == 1.0 was, even after years of use of Erlang, since I never used floating points in Erlang to speak of. (It is true, which technically I find a bit weird. I'm not sure there's another example of values of different types that can be equal. Note "" == [] because double-quotes are defined as producing lists and strings aren't actually a type, so that's not an exception.)
I agree with the sentiments of some of the other commenters -- it's much less troublesome to match on tuples (Erlang) and predicate clauses (Prolog) than it is to match on JavaScript objects. With tuples/clauses, the constructor syntax mirrors the pattern matching syntax, so it's trivial to read the code at a purely syntactic level to debug pattern matching issues, and this would simplify automatic static analysis as well. As soon as you can add "fields" to an object at arbitrary (non-local) points in the code, local syntactic analysis becomes intractable.
What I've seen doing fairly fast and loose prototyping and product iteration in Elixir over the last 2 years, is that patten matching can help create better boundaries and data guarantees within a codebase. A bug might still cause a runtime exception, but investigating and fixing the bug can be wayyyyy less painful than in JS, Ruby and friends when a nill/null/undefined/some-other-garbage can be passed very far through the stack before it eventually causes an issue.
Having a pattern match in the code, even a weak, non-typed pattern match on literals or basic data types, can act as an assertion in your code. "This is what the data needs to look like here if anything after is going to work". I think there is a probably an antipattern in taking this too far, coupling all sorts of your codebase to lower-level data structures There are better controls and safety mechanisms that can be implemented, but it can help a lot when there are specific parts of a codebase that are hotspots for issues, or where you're just trying to isolate where a data inconsistency is originating.
Exactly. That's one of the key points I make when talking about Erlang: the = sign specifically, and pattern matching more broadly, are like having full-time, production assertions everywhere.
One key to the success of that in Erlang/Elixir, of course, is that you have the infrastructure and language support to manage widespread assertions that can fail.
Exhaustiveness checking is what elevates pattern matching to the other side of the expression problem, which is about getting feedback from the compiler to help you write programs.
It's been possible since the '70s but I've seen a lot more talking about it in the last 5-10 years.
[1] https://github.com/norvig/paip-lisp/blob/master/lisp/patmatc...
In the modern acception of tree patterns (as opposed to text patterns aka regular expressions), I guess it comes from ML (and possibly prolog but prolog's unification goes even further?): it doesn't look like ISWIM had tree patterns and I can't find older references.
I was going to specialize in AI in my major, but I guess it was about 25 years too early. But that’s another tangent.
Aside from full packages like what lispm mentioned, implementing pattern matching (and later, full Prolog-style unification) in Lisp is a very common exercise in beginner Lisp textbooks, going back decades.
Permissiveness is good for small projects but it can feel like getting into a suit tailored specifically for everyone else as projects get bigger.
When you get to the scale of enterprise software, though, I'd agree with you that the best thing to do is probably not to allow any macros at all. The inevitable abuse at the hands of inexperienced developers would quickly overtake any gains from more responsible macros without perhaps some clever and/or toilsome code review processes.
Additions such as those do not have to be one lonely programmer's macros, they can be libraries widely accepted by the community.
I had a similar thought when reading the ECMAscript extension proposal: I'm glad that in the languages I use, features like that can be provided by libraries.
None the less, the opposite is also true. Languages that have macro facilities can aid in writing more legible code. (See `threading` in clojure), or the `loop` macro and regular expression macros in common lisp.
Some people are just terrified of any new abstractions, I guess, preferring to work with an endless series of tally marks, rather than these obfuscating “multiplication” and “exponent” complications (exaggerating to make a point - abstract != unintelligible).
Alas, that tends to be the steady state for Enterprise development :-(
(I know that’s not literally what you said, but that’s where that “middle ground” attitude leads to: the bottom)
The benefit is that your polyfill/conversion to something browsers support today is handled by someone else, and your actual code base can point a reader to the proposal to at least make some sense later on.
Not that I imagine many people would encourage using a stage 0 proposal, but it's probably a bit better than everyone writing their own adhoc implementation.
The definition bit looks like a function definition but behaves entirely differently. Why? Why not make it a constructor like everything else? People could look at it and be like "oh a match object" instead of wondering if you overloaded function.
The selectors look like json, only assignable. Again make it obvious.I mean we could even just make it json why not.
const my_arrow_function = arrow_function(){}
arrow_function my_arrow_function(){}
arrow_function(){}
delegate(int x){ return 10; }
(But yeah, nowadays that would be:) (int x) => 10Is the same phenomenon somehow tasteful when it comes to ECMAScript {({..., sy: nt => ax, ...}({,})} ?
It's the object/class additions where I think it's all going a bit C++.
Start with prototypical inheritance, but with a Java-style `new` keyword that makes everything confusing. Next bolt on classes on the one hand, while correcting your prototype system `Object.create` on the other.
R has 3 different object systems and none of them are any good. How long before JavaScript catches up?
We have different ways of doing the same thing, and you have to know the "right" and "wrong" ways based on whatever was the latest proposal.
We have fringe proposals that are just based on something cool some other language did, and they might stick or they might not.
We'll soon have the really obscure constructs that nobody uses except for this guy on the 3rd floor, and he's really vocal about it so watch out.
Maybe we'll even finally get macros in JavaScript [1] soon.
1: http://peter.michaux.ca/articles/macros-in-javascript-please
https://github.com/zkat/proposal-pattern-matching
zkat (who is also an npm employee) is a leading committer to the tc39 version, so I think there's some chance that many aspects their version will be adopted into the proposal.
let visFilter = 'foo';
const newState = match (action) {
{type: 'set-visibility-filter', filter: visFilter} => console.log(visFilter)
}
What happens?Is it checking if action.type === 'set-visibility-filter' && action.filter === 'foo' or is it destructuring the value of action.filter into visFilter?
To me this is pretty average and seems like it will cause magic numbers/strings/etc if I can't use constants where it makes sense. The proposed solution is to do something like
{ status: 200 } if (x === foo) => // do something
which just seems (for this type of use case, maybe not all) like more boilerplate code for no real benefit over just doing something like if (res.status === 200 && res.x === foo) //do something
I would much prefer if there was some sort of special operator used to either check equality or to destructure when matching so it is obvious what the code is doing.There is also a lot of "we shouldn't break that" messages, and people reply with "oh we already screwed that up here and there, so it's fine"...
https://doc.rust-lang.org/stable/book/second-edition/ch06-02...
Here is the syntax for different languages:
- Rust https://doc.rust-lang.org/stable/book/second-edition/ch06-02...
- Haskell http://learnyouahaskell.com/syntax-in-functions
- Scala https://docs.scala-lang.org/tour/pattern-matching.html
So I can do
let name = obj?.data[i]?.name;
And not get error can't read property data of undefined, when there is something wrong, just have undefined as value.
To fix this I've to write redudant checks for each level of nested property. Or do stuff like (obj || {}).prop....
I think this is wrong. The proposal says that a pattern like `{x}` matches if the object supports `ToObject` and if its `x` property is not undefined. It doesn't say anything about requiring that the object have no other own-properties. This is consistent with how destructuring already works in JS (it ignores any extra properties).
I can understand why though they went with similar syntax to a switch statement though
1. https://github.com/tc39/proposal-pattern-matching#motivating...
I think the HTTP response is a great example. It can have many status codes which can determine how you want to proceed with that response. If you've worked with handling HTTP responses, you've most likely implemented some version of a 'match' operation before.
let day;
switch (date) {
case 1:
day = "mon";
break;
case 2:
day = "tues";
break;
default:
day = "wed";
break;
vs let day = match(date) {
1 => 'mon',
2 => 'tues'
} const getDate = (date) => {
switch (date) {
case 1:
return 'mon'
case 2:
return 'tues'
default:
return 'wed'
}
}
let date = getDate(1)
Which is, of course, still less terse. What I normally use when I have cases like this is an object-as-a-map. IE: const dates = {
1: 'mon',
2: 'tues'
}
let day = dates[3] || 'wed'; function getDate(date) { .. }
to this: const getDate = (date) => { ... }
The first version is more readable. We instantly see that it is a function and in a second case it looks like a constant at first. Also without the equal and arrow sign it looks simpler. new Promise(function (resolve, reject) {
methodOne(data, function (error, response) {
if (erorr) {
reject(error);
} else {
resolve(response);
}
})
})
vs new Promise((resolve, reject) => methodOne(data, (error, response) => error ? reject(error) : resolve(response))); var numbers = [1, 2, 3, 4].map(x => x * x);
var bestUsers = users.filter(u => u.getRating() > 100);
But for a case when you have a large non-anonymous function, `function` keyword suits better. You don't need to use `const` keyword just becase it is something trendy now.In your example, the code with arrow functions is smaller, but it is not more readable. Because there is no indentation, it is difficult to understand how code is nested. I cannot read that.
It can be rewritten using `deferred` pattern:
var deferred = new Deferred;
methodOne(data, function (error, response) {
if (erorr) {
deferred.reject(error);
} else {
deferred.resolve(response);
}
});
return deferred.getPromise();
This way we can get rid of a callback in the Promise constructor. Please note that our code now looks sequential and we clearly see what happens after what. Asynchronous code is difficult to write and read; therefore we must put an extra effort to make it easier.In my opinion it is generally bad idea to nest more that 1-2 levels of functions inside each other.
> Starting from Gecko 30, this object is obsolete and should not be used anymore. Use the new Promise() constructor instead (or use the above backwards/forwards compatible Deferred function given below). For example, the equivalent of
Seems like it's obsolete. [0]
If you want to write async code, use async / await.
const run = async () => {
const response = await new Promise((resolve, reject) => methodOne(data, (e, res) => e ? reject(e) : resolve(res)));
};
If you spend enough time with fat arrow, it's as easy to read as `function` is. On top of that, IMO it looks cleaner. It also allows you to do scope binding in a different manor which in React is much better.This:
<a onClick={this.onClickEvent.bind(this)}>Click Me</a>
becomes this: <a onClick={(event) => this.onClickEvent(event)}>Click Me</a>
[0] https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_...Then you can write your own Deferred implementation. I didn't even know it was implemented in browsers.
const run = async () => { ... }
How to read this? run is a constant that equals the result of calling function async() thas maps to something in curly brackets? This is confusing.> If you want to write async code, use async / await.
That is a good idea, but it has nothing to do with arrow functions.
> It also allows you to do scope binding in a different manor
`this` binding in JS in classic functions is broken by design, so yes, that is the advantage of arrow functions.
I need to look at it more closely, as some languages have matching be an expression, rather than a statement or block like the conventional switch.
It doesn't add something that you can't already do- tcomb and other libraries offer functions that mimic it- but it changes the way you can express certain ideas without needing them, in a way that is already familiar from other languages.
EDIT: yes, it does look like an expression, which means it can be used in many more places than a standard switch.
I see what you did there...
As another commenter said, it's like conditionals on steroids.
So this:
function getStatus(status) {
if (status === 200){
return "Success";
} else if (status === 401){
return "Fail!";
}
}
var myString = getStatus(response.status);
Now becomes this: var myString = match (status) {
{200} => "Success",
{401} => "Fail!"
} var myString = match (status) {
200 => "Success",
401 => "Fail!"
}```
switch(foo)
{
case TextBox t:
Console.Writeline(t.Text);
break;
case TextBox when t.Text = "Bob":
Console.WriteLine("Hello Bob");
break;
case Combobox c:
Console.WriteLine($"{c.SelectedItem}");
break;
case null:
Console.WriteLine("Ooops, null!");
break;
case int i when i == 5:
Console.WriteLine("got an int, and it was 5!");
break;
case default:
// handle the default case.
break;
}
```IN languages like F#, pattern matching can compile time check you have covered all bases as well.
e.g, this won't build
````
type VariableResult =
| E of string
| V of string
let result = V "variable"
match result with
| E e -> printf "was error"
```As i haven't told it how to handle the V case for that discriminated union. So you get nice compile time checking.
Ugh, how do you format code?
C# doesn't really deserve the name of pattern matching, just a type-based switch (with guards), you can't match on values let alone destructure them.
> Ugh, how do you format code?
4 spaces indent.
I personally wish the syntax was similar to overloading functions in other languages, but I doubt that's possible due to the way destructuring & default values were implemented.
Basically, it's just a proposition for the moment, there's no telling if that's gonna be in the language or not. It's only at stage 2 that it is likely that the feature will be added in the language.
The TC39 process: https://tc39.github.io/process-document/
In terms of chances of standardization, that's a step up from "Hey, I have a great idea", buy there are plenty of Stage 0 proposals that go nowhere because they lose steam.
I think the metric they look for is who is using the Babel plugin for it. If people are doing that, then they it has a better shot of progressing.
Stage 4 is the "this is getting standardized next time" stage
The closest thing I can think of is ES6 modules, because the module loader spec wasn't finished, but the syntax still lived on.
Now, stuff getting kicked down from Stage 3, that has happened.
switch(expr) {
case 'foo':
break;
case { foo: bar }:
break;
}
Wouldn't strike me as all that strange. It just shifts the semantics from "are you exactly this" to "do you look like this". For non-object primitives (e.g. string or number), I don't think those two things are functionally different.Granted, it doesn't help make switch any easier to learn, but I _do_ think it makes switch more _rewarding_ to learn. Right now, switch is basically just a restrictive, potentially terser version of an if statement. This would make switch actually useful to learn.
It might even allow us to add some more semantics to switch over time (e.g. constructor matching with something like 'case Number').
(EDIT: thanks to armandososa for teaching me a new thing!)
switch(expr) {
case 'foo': break;
case { foo: bar }: break;
}
https://news.ycombinator.com/formatdoc switch(expr) {
case 'foo':
console.log("a string")
break;
case { foo: bar }:
console.log("foo is", bar)
break;
}
But how would this work? switch(expr) {
case 'foo':
case { foo: bar }:
console.log("foo is", bar)
break;
}
Moreover, a big selling point of pattern matching is it is an expression. Keep in mind though case points to a statement list, How do we resolve to a value?With a "return"?
function f() {
switch(expr) {
case 'foo':
return 4 // This makes f return
}
}
function g() {
var x = switch(expr) {
case 'foo':
return 4 // But this doesn't. Is this confusing?
}
}
The value of the last statement? function g() {
var x = switch(expr) {
case 'foo':
4; // This should resolve to 4
break; // but is the break necessary now?
}
}
IMO switch sytnax is just legacy left behind by C, and not the best one to keep around, especially given the semantics of pattern matching.[1]: https://en.wikipedia.org/wiki/Duff%27s_device, not sure if this works on JavaScript (I doubt it), but the point is the switch cases essentially work like "goto"s.
Sure is. Or a degenerate one if you prefer.
> match is a reducible structured control-flow construct whose power comes from destructuring.
Match on sum types with no associated data and you have a switch.
> match is much, much more like an if-else chain than a switch.
Depends on what you're matching.
switch (x) {
case 0:
while (i < l) {
s *= i;
case 1:
i += 1;
}
}
cannot be rewritten with a simple match. You'd have to reloop the CFG. var x = match (response) {
case { status:200 }: true;
case { status: 404 }: new NotFoundError;
case Number: Math.PI;
case SomeClass: 1;
case /^http/: "http error";
default: -1;
};The leading curly braces in the linked proposal _are_ a bit odd to see, though the repetition of `case` isn't too fun either.
For a default I'd expect there to be another pattern.
> [About variable name patterns binding to the name] Eliminates the need for an else/default leg, because assignment to any variable will be sufficient. JS programmers are already used to assigning variables that are then ignored (in functions, in particular), and different people have different tastes in what that should be. _, other, etc, would all be perfectly cromulent alternatives.
When reading the code, that means you need to hunt for the match or switch statement to know what happens at the end of a case clause.
IMO, if one is willing to correct historical errors, even if they are engrained, Apple’s Swift language makes the best choice here. It requires an explicit fallthrough to fall through to the following case, and goes even further than what you propose by only having a switch keyword (https://developer.apple.com/library/content/documentation/Sw...)
So instead of match (x) { expr... } you would have something like match(x, [ fn... ]) or match(x) { name: fn, name2: fn2 } where fn is like (objectToMatch) => { expr }
var x = match (response) {
case { status:200 }: true;
case { status: 404 }: false;
case Number: 0;
case SomeClass: 1;
default: -1;
}; var x = (switch(true){
case response === { status: 200 }:
return true;
case response === { status: 404 }:
return false;
case response === Number:
return 0;
case response === SomeClass:
return 1;
default:
return -1;
});No. That would make the actual switch statement a value, rather than make switches expressions. And your version is completely different as you make cases into guards rather than labels or patterns.
How on earth is it bizarre to make neighborhood roads worse to drive on for commuter traffic? And defunding public transport?
You can have a debate about these things, but calling it bizarre betrays the sort of "reasoning" that is dominated by an ideological anti public-anything bias.
Why Not?
Python is my favourite example to pick up on this.
Target at beginners and deemed as simple, yet I doubt anyone is able to know Python semantics since version 1.0 and by looking at a random codebase is able to state what is the minimum Python version required to run the code without errors.
Also I very much doubt anyone knows Python's library cover to cover.
Languages get complex because real world has complex needs.
Even Go, the new poster child of simplicity, now has quite a few warts, because not everyone doing software like Google.
if(val==1) var res = 1;
if(val==2) var res = 2; const var =
val == 1 ? 1 :
va1 == 2 ? 2 :
null;