Those of us who celebrate Christmas or Hannukkah probably have strong memories of the excitement of December. Do you remember the months leading up to Christmas, when your imagination exploded with ideas, answers to the big question “What do you want for Christmas?” As a kid, because you aren’t bogged down by adult responsibility and even the bounds of reality, the list could range anywhere from “legos” to “a trip to the moon” (which is seeming like will be more likely in years to come).
Thinking outside of an accepted base premise—the confines of what we know something to be—can be a useful mental exercise. I love JavaScript, for instance, but what if, like Christmas as a kid, I could just decide what it could be? There are small tweaks to the syntax that would not change my life, but make it just that much better. Let’s take a look.
As my coworker and friend Brian Holt says,
Get out your paintbrushes! Today, we’re bikeshedding!
Template Literals
First off, I should say, template literals were quite possibly my favorite thing about ES6. As someone who regularly manipulates SVG path strings, moving from string concatenation to template literals quite literally changed my damn life. Check out the return of this function:
function newWobble(rate, startX) {
...
if (i % 2 === 0) {
pathArr2[i] = pathArr2[i] + " Q " + in1 + " " + QRate;
} else {
pathArr2[i] = pathArr2[i] + " Q " + in2 + " " + QRate;
}
...
return "M" + pathArr2.join("") + " " + startX + " " + (inc * (rate*2) + rate);
}
Instead becomes
const newWobble = (rate, startX) => {
...
if (i % 2 === 0) {
pathArr2[i] = `${pathArr2[i]} Q ${in1} ${QRate}`;
} else {
pathArr2[i] = `${pathArr2[i]} Q ${in2} ${QRate}`;
}
...
return `M${pathArr2.join("")} ${startX} ${(inc * (rate*2) + rate)}`;
}
…which is much easier to read and work with. But could this be improved? Of course it can!
There is a small bit of cognitive load incurred when we have to parse ${x}
, mostly due to the very nature of the characters themselves. So, what if template literals lost the dollar sign and moved to square brackets instead? Rather than:
return `M${pathArr2.join("")} ${startX} ${(inc * (rate*2) + rate)}`
…we can have something like:
return `M[pathArr2.join("")] [startX] [(inc * (rate*2) + rate)]`
…which is much more streamlined.
Ternary operators
Ternary operators are interesting because in recent years, they have not changed, but we did. A lot of modern JavaScript makes heavy use of ternaries, which causes me to revisit their syntax as it stands now.
For instance, a one-liner like:
const func = function( .. ) {
return condition1 ? value1 : value2
}
…is not so hard to read and grok. But here’s what I’ve been reading a lot lately:
const func = function( .. ) {
return condition1 ? value1
: condition2 ? value2
: condition3 ? value3
: value4
}
This is much harder to read, mostly because the colon :
gets lost depending on your code editor and syntax highlighting settings. And, what if someone isn’t properly formatting that code? It can easily become:
const func = function( .. ) {
return condition1 ? value1 : condition2 ? value2 : condition3 ? value3 : value4
}
…in which case the colons are extremely hard to see at a glance. So what if we used a visual indicator that was a little stronger?
const func = function( .. ) {
return condition1 ? value1 | condition2 ? value2 | condition3 ? value3 | value4
}
A pipe doesn’t break up the flow, yet still separates in a way that is not as easy to get lost in the line.
Arrow Functions
I’m going to have a mob after me for this one because it’s everyone’s favorite, but arrow functions were always a miss for me. Not because they aren’t useful—quite the opposite. Arrow functions are wonderful! But there was always something about the legibility of that fat arrow that irked me. I am used to them now, but it troubled me that when I was first learning them, it took me an extra second or two to read them. Eventually this passed, but let’s pretend we can have our cake and eat it too.
I am definitely not suggesting that we still use the word function
. In fact, I would love it if arrow functions weren’t anonymous by nature because:
const foo = (y) => {
const x
return x + y
}
…is not quite as elegant as:
const foo(y) => {
const x
return x + y
}
In my perfect world, we would drop the function and the arrow so that we could have something that resembles more of a method:
foo(y) {
const x
return x + y
}
and an anonymous function could simply be:
(y) {
const x
return x + y
}
Or even a one liner:
(y) { y += 1 }
I know many people will bring up the fact that:
- arrow functions have one-liners that do this, and
- I disliked the curly brackets in the template literals above
The reason I like this is that:
- some encapsulation can provide clarity, especially for logic, and
- curly brackets are a stronger visual signal, because they’re more visual noise. Functions are important enough to need that sort of high-level visual status, whereas template literals are not.
OK, now let’s go one step deeper. What if we always had an implicit return on the last line? So, now we could do:
foo(y) {
const x
x + y
}
Or…
(y) {
const x
x + y
}
If we didn’t want to return, we could still say:
foo(y) {
const x
x + y
return
}
Or, better yet, use a special character:
foo(y) {
const x
x + y
^
}
This way, anytime you wanted to return a different line instead of the last, you could use return and it would work just as normal:
foo(y) {
const x
return x + y
const z
}
What a world it could be, eh?
What Now?
People invent new languages and rewrite compilers for the very reason of having a strong opinion on how a language should pivot or even how it should be written at all. Some of my favorite examples of this include whitespace, which is a programming language created from all tabs and spaces, and Malbolge, which was specifically designed to be impossible to program with. (If you think I’m a troll for writing this article, I got nuthin’ on the guy who wrote Malbolge.) From the article:
Indeed, the author himself has never written a single Malbolge program
For those more serious about wanting to develop their own programming language, there are resources available to you, and it’s pretty interesting to learn.
I realize that there are reasons JavaScript can’t make these changes. This article is not intended to be a TC39 proposal, it’s merely a thought exercise. It’s fun to reimagine the things you see as immovable to check your own assumptions about base premises. Necessity might be the mother of invention, but play is its father.
Many thanks to Brian Holt and Kent C. Dodds for indulging me and proofing this article.
Interesting. What I wish existed: Array.prototype.last, because I don’t like [list.length-1].
Good news! There’s a stage 1 proposal for that https://github.com/keithamus/proposal-array-last
Nice, thanks for the link, Simeon!
OMG. Thank you so much. I am learning to
understand
JS. ^^I like the approach, but I’m not sure I actually like any of the specific ideas. The whole point of this is to foster discussion, though, right? So here are my thoughts :)
Using a pipe in the ternary operator is interesting, but conflicts with existing syntax. This one doesn’t actually go far enough, IMO. Better to add a language feature (pattern-matching maybe?) that adds a cleaner if-elseif syntax for expressions.
const foo = (x) => { ... }
is clunky, but I’d be worried thatfoo(x) { ... }
might not make it obvious what’s going on. That’s a minor complaint, though, I mostly like this one, FWIW.Thanks for writing this, definitely a fun thought exercise!
IMO implicit return would be a net-positive change, but we’d need to pair it with better ergonomics for returning undefined like a standard non-returning function.
What if we paired it with another syntax tweak. Since JS uses semicolons (;) as statement terminators, what if the interpriter inserted an
undefined
statement between consecutive semicolons. For example,;;
would be interpreted as;undefined;
For example, a standard function
Could be written in Sarah’s proposed syntax as
I agree that template literals are AMAZING, but beyond that I disagree with most of this. The implicit return for example…you are wanting to eliminate the need for the word
return
. Yet require it when you want avoid
function? That’s just shifting the “issue”.Welcome to CoffeeScript!
One thing that always bugged me with CoffeeScript is the awkward way to make a function void:
Actually, replace
console.log
with something that returns a value.Hmm, I never really missed JS templates (kept them all inside non-executing script tags). Mixing technologies inside components is quite modern, but it’s not the only way to produce clean and manageable code.
This example shows how to separate template from js code (jQuery style). Easy to embed in almost any CMS. Easy to maintain with svn, git etc.
It’s very easy to construct own abstractions on top of this pattern without implementing tens (vue, react) or hundrets (angular) of new semantics.
Sounds like you’re ready for functional! Try a language that’s based on ML. You’ll find that a lot of what you’re looking for is hiding there. Given your love for JS perhaps Elm is the easy forward… If not then maybe F# (a personal favourite) or Haskell (if you dare!).
You’re assuming I haven’t already played around with Elm etc ;)
If your Ternary is that complicated, wouldn’t normal if syntax be much cleaner than this new suggested Ternary anyways?
Yep, I’m with you. I’m not the only one writing the code I maintain, though, and a lot of people are writing ternaries this way lately.
This is one of the reasons why Ternarys are a bad idea in the first place.
If you really want to write an if/else then just do that!
I’d personally write this:
as:
Yeah, that probably would have been better.
CoffeeScript’s implicit returns have been the cause of a lot of very hard to track down errors in my experience. In a language that has so many side effects and isn’t strictly functional, I really think it’s best we be explicit about what we’re intending to return.
Interesting. Look like you create your own CoffeeScript :)
I love some of your ideas, especially implicit returns. I’ve worked with languages with implicit returns and it’s really nice to have. I haven’t had the need to explicitly return
undefined
ever. It just makes sense, just like what we will get withdo expressions
.To battle nested conditional expressions (which I avoid anyway) I would rather see something like pattern matching in function signatures and function overloading. So instead of this
You get
Of course the
const
keyword doesn’t seem right when overloading but you get the idea :DOh by the way we might get pattern matching, just not in function signatures… but it’s still way better than nested conditional expressions. Currently only stage 0 though.
YES! High fives
How about automatic currying like this:
So that curried behaves like
Just like Ramda.curry
I love curry(ing)
I love it, Mr Curry
I like your ideas on how to simplify the language, a very inspiring post! When thinking about it, I would also like to be able to skip the keywords
let
,const
andvar
when declaring a variable (like Python).Example: when typing
x = 1
it would be treated as a scoped variable, just like using alet
today. If doing that today, I think the variable will be treated as a global (var
) and every linter and IDE in the world would probably go crazy.This is exactly the kind of simplification I can get down with.
Unfortunately, the ternary can’t use a pipe instead of a colon because the single pipe character already is a “bitwise or” operator.
(Object-oriented) design patterns, for recurring problems.
In description or template format (not static “finished” copy+paste snippets) so that each pattern can be tailored to resolve the problem for a specific situation or a specific environment.
I like the idea of the simplified function formatting, it would also make it much easier to refactor code into/out of classes, etc. The only part of it that I don’t like is removing the arrow syntax one liners.
The current syntax is easier to read when used in such a way. HOWEVER, maybe it would be nice if the arrow syntax ONLY worked with single line expressions, which automatically return the values.
That would allow us to do stuff like the following:
https://gist.github.com/AnastasiaDunbar/b87aa100af309c3eba23a27f7ae0d617
Template Literals
I’d like to keep using
${}
instead of[]
because it would be a pain to escape the characters and also Ruby uses#{}
which is similar to JavaScript, I think using two characters to begin a string interpolation feels better even though you might hate the dollar sign.Ternary operators
It would be confusing to newcomers who have used C-languages to use
|
instead of:
for the ternary operator, and I see no sugar in this.Arrow Functions
There’s a problem with this which is local scoping:
Great article, love the analysis of where JS syntax could go.
arrow functions may make the code easier to write, but they are confusing to read. whenever i see => i think of =. it needs to be a different symbol, one that is not used as an operator.