Skip to content

Instantly share code, notes, and snippets.

@polotek
Created April 12, 2012 05:51
Show Gist options
  • Save polotek/2364818 to your computer and use it in GitHub Desktop.
Save polotek/2364818 to your computer and use it in GitHub Desktop.
Thoughts on how we evaluate javascript proposals

I promised some people that I would talk a little about my feelings on ECMAScript proposals. I love javascript as a language. Sure, it has lots of warts, some that aren't easily dismissed. But it also has some really nice constructs that allow for elegant code solutions. My preference for javascript is purely subjective, and I don't mind admitting that. But being that I'm a staunch supporter of the language and the community, I'm also pretty opinionated about where it's going. These are just some of my concerns.

Change is good

I should start by saying that I'm not against progress. Whenever I argue with people about javascript proposals, I always get the eventual argument that we have to move javascript forward and we can't be afraid of change. I want to go on the record as saying I want upgrades to javascript. But I don't think progress is a reason not to challenge ideas that I think are not fully formed or potentially problematic. This tension is good, because it keeps the standards makers on their toes. And it makes sure they get alternate input from people who have a different perspective on what makes a good language feature, but who are still invested in moving things forward.

Many ECMA proposals solve problems with the language, that's a given. And you can't really change a well-established language without creating other problems. That's unavoidable and shouldn't be a barrier to progress. My concern with some of the proposals is the impact of these other problems isn't always given enough weight in determining if the change is worth it, https://twitter.com/#!/polotek/status/187219527459811330.

Case in point: fat arrow

I think the recent concensus on fat arrow syntax is a good example to illustrate this. I think it's a decent proposal. I'm not sure I agree that "fixing" dynamic function context with syntax is absolutely necessary. But after talking to several people, I seem to be in the minority, so I can accept that this is happening. It's also a fairly simple proposal on it's face. If you use the => syntax, you'll get a function. And that function will have a few other "nice" properties, like automatic binding to the closest lexical this.

On the face of it, this seems like pure win. People want shorter function syntax and people want to worry less about this. But this proposal has side effects, and more importantly it has affordances, that shouldn't be ignored. Most of these are expressed pretty well in this article by Angus Croll. In short, I'm concerned that people will use fat arrow, a lot, everywhere, and that's a problem.

Fat-arrow isn't just an alternative to normal function syntax. It's a new syntax that also introduces a completely new type of function. Functions that come from fat arrow have significantly different properties from normal functions. Some of the things you expect from normal functions are not applicable to fat arrow functions.

  • Functions have dynamic this, fat functions always have a this that you can't anticipate.
  • Functions can be dynamically dispatched with call/apply. Call/apply works with fat functions, but it will not change the context. Only pass arguments.

This is only a couple of items. There are several more differences that have to do with how the function body works. But these 2 have to do with functions that are passed around. These are differences that every developer who uses apis will have to deal with.

For one example, you if you take an object and mix it's methods onto your own object, you have to be aware of whether the functions are already bound. Otherwise you won't get expected behavior. Yes this is already a possibility with Function#bind or closures, but it doesn't happen very often. It's not a common occurence. And this gets at the heart of my concern. I think it's very possible that fat arrow will become very common. Because as Angus points out, it will also be the shortest way to get a function. Programmers love short syntax. And they will jump on this. Unfortunately those same developers who don't get dynamic context, the ones we are trying to save, may not bother to be selective in when they use fat arrow functions vs normal ones. So we'll get proliferation of these things. And then it'll become a real problem for anyone who is used to taking advantage of the flexible power of javascript.

What are the criteria for Good change?

How big a problem is this? I don't know. It's certainly more of an unknown than the misunderstanding of dynamic this. But from my long history with javascript, I think it's a potentially large issue. I think it's one of those things that gets away from you and eventually people are saying "you can't really trust call and apply, you don't know what kind of function you're getting". Or everyone's code is littered with some isBound type check and they continue to gripe about javascript's inconsistency.

But I could be way off base. This could be totally unfounded. Does anyone know how likely this type of thing is? How was it decided that the benefits of fat arrow outweight these potentially long-lasting detriments? Are there any plans to mitigate? These are the questions I'm left with once a "consensus" has been reached. I would love to have nothing better to do than pore over the es-discuss lists, but I just don't have the time. So I try to give my input on things as they gain enough popularity to get some publicity. And I don't always have constructive alternatives to present.

This is where I'm coming from when I scrutinize harmony proposals. Fat arrow isn't the only example; just the most recent. As an attempt to do more than just gripe, I think what would help is to publish concerns about the proposals and the rationale that minimizes those concerns. And even more helpful would be some talk about what criteria are concidered when evaluating proposals. When is battling current confusion more important than introducing new inconsistency? Why do we feel like the new weirdness will be less onerous?

I hope none of this feels like disparaging the work of standards body, and I hope it serves to clarify my views on progress. I don't know if I'll find more time to be a larger part of es-discuss and get in on these things earlier in the process. But I can promise not to be a person who just complains. Looking forward to feedback.

@dherman
Copy link

dherman commented Apr 12, 2012

Hi Marco, thanks for your thoughts. Here's why I think your characterization of this isn't quite accurate.

In JS today, all functions receive an implicit zeroth argument via method call, and you can explicitly pass the zeroth argument with apply and call. Now, in current JS, when you implement a function, you reference that zeroth argument with this. But you can also write a function that simply never refers to this at all. In such a case, passing the zeroth argument, whether via method call or via apply/call, will never have any effect on the function's behavior. This is already the case in JS today, with no fat arrows.

From the perspective of a caller of the function, a client of the function, fat arrow does not change this at all. The only thing that's different about fat arrow is that the way you implement a function that ignores its zeroth argument actually repurposes the this keyword to refer to something else (namely, the this binding of the surrounding function). But that's purely a matter of how you implement the function -- it's an internal perspective.

This is why I disagree when you say it's a "completely new type of function." From the perspective of a user of the function, it's no different from a function you can write today that simply does not refer to this. On the inside of that abstraction boundary -- i.e., from the perspective of the person who uses a fat arrow to implement a function -- it's simply providing convenient syntax for a common case, namely, when you want to write a function that ignores its zeroth parameter. On the outside of that abstraction boundary, there is nothing strange or uncommon about such a function.

Dave

@BrendanEich
Copy link

Dave wrote "On the inside of that abstraction boundary -- i.e., from the perspective of the person who uses a fat arrow to implement a function -- it's simply providing convenient syntax for a common case, namely, when you want to write a function that ignores its zeroth parameter..."

I'd add "... and either wants to use the enclosing function's |this|, or doesn't want to use |this| at all." Kevin Smith and others found that in JS today 80-90% of function expressions are of this type (best results if you make methods defined in object literals use the new ES6 short-hand first -- these of course want dynamic |this|). And we see lots of people expect to get the outer function's |this| by mistake, e.g. when writing a lambda expression passed to Array#forEach. The Array extras have a protocol for passing |this| down, but people forget, and using .bind(this) on the outside is verbose enough that it doesn't happen.

There's no absolute right or wrong with functions, but I agree with Dave: JS had kind of go-go-gadget functions from day 1, and you could use them (with discipline) as constructors receiving the newborn object as |this|, methods taking dynamic |this|, callbacks usually wanting the enclosing function's |this|, and functions that don't use |this|. Callbacks had to use var self = this; or .bind(this), and arrows are a good fit for the callback case, as for the final case (functions that don't use |this|). Methods have shorthand object literal syntax in ES6. Constructors are pretty much the same.

So arrows hit a couple of use-cases of JS functions without the need to incant var self=this or .bind(this) ("go-go-gadget callback!"). The code analysis showing that arrows hits up to 90% of function expressions is pretty compelling IMHO.

/be

@polotek
Copy link
Author

polotek commented Apr 12, 2012

I think maybe I'm still not clear. Let's go to the code.

// library.js
var Foo = {
  getValue: () => "some value"
  , somethingUseful: () => {
    return this.getValue();
  }
}

// app.js
var Bar = mixin({}, Foo, {
  getValue: function() {
     return "alternate value";
  }
});

Bar.somethingUseful();

This is a valid pattern for re-using and repurposing existing functionality. But the method call on Bar will not refer to the Bar object as this. I will not get my "alternate value". But the only way I can know that is to go into library.js and confirm that it is using fat-arrow. Then I have to decide if I want to edit library.js or find some other way to reuse it's capabilities. As I said, this happens now. But I think it will happen much more frequently with fat arrow. Simply by virtue of the fact that is a preferable syntax for writing functions, and people will use it.

Am I still missing something?

@dherman
Copy link

dherman commented Apr 12, 2012

Fat arrow simply does not use its this-parameter, so that code does not do what you want. That's just not what it's for. Now, maybe you're bummed that you can't use the sweet shorthand that fat arrow gives you to create normal methods. But for that, you can use the method shorthand, which is even more concise:

// library.js
var Foo = {
    getValue: () => "some value",
    somethingUseful() { return this.getValue() }
}

// app.js
var Bar = mixin({}, Foo, {
    getValue() { return "alternate value" }
});

Bar.somethingUseful();

Dave

@polotek
Copy link
Author

polotek commented Apr 12, 2012

I'm perfectly willing to accept that the majority of use cases would benefit from implicit lexical this. I would argue that the above is an intermediate to advanced usage, but that it is quite common in libraries. Not just common, but really important in some cases. What's the alternative?

@dherman
Copy link

dherman commented Apr 12, 2012

That said, I will admit I'm still somewhat sympathetic to the point that IIRC Angus made in his blog post that, if you want the convenient shorthand that fat arrow gives you, you'll also often want an alternative shorthand (perhaps spelled with thin arrow) for functions with the normal dynamic this. Some agree with that; others feel that two new arrow forms is just too easy to confuse. Given that ES6 already has the method shorthand I showed above, and Kevin Smith's evidence, the case for Yet Another arrow form is weaker.

Dave

@BrendanEich
Copy link

In case it's not clear, the answer to "What's the alternative?" is the method definition shorthand used in the object literals in dherman's rewrite at https://gist.github.com/2364818#gistcomment-251610: even the getValue property could be changed from having an arrow function expression as value, to being a method definition shorthand, since it doesn't use |this|:

// library.js
var Foo = {
    getValue() { return "some value" },
    somethingUseful() { return this.getValue() }
}

// app.js
var Bar = mixin({}, Foo, {
    getValue() { return "alternate value" }
});

Bar.somethingUseful();

But the price of this is the { return before and the } after.

/be

@dherman
Copy link

dherman commented Apr 12, 2012

Not sure which thing you're referring to by "the above"...?

But I'll add one thing: maybe it would help tease out the different uses of functions. JS has always had multiple distinct uses for functions:

  • constructors
  • methods
  • local/global functions
  • callbacks

When you write constructors and methods, you want/need dynamic this. When you write local functions and callbacks, you generally want lexical this. JS already makes this possible, but inconvenient, particularly for callbacks. And callbacks really benefit from the conciseness because they're often little glue functions (e.g., the argument to array.map). But you should not use fat arrow for methods; that's really the wrong use case. You should use function, or if you want a more concise form, the method shorthand.

Dave

@gigafied
Copy link

Marco, I can definitely see your point, seeing as I implement a similar inheritance method (using AMD and some helper methods like mixin here), this could be an issue.

However, that is only in theory. Practically, though, I don't think I would ever use the fat arrow syntax on an Object I wanted to use for inheritance purposes and I don't think authors of libraries who intended their Objects to be inherited, would do so either.

There will probably be instances of it happening, where a new library comes out, somebody tries to inherit from some Object and runs into issues. Looking at the source though it would be very clear what the cause of the issue is (if you knew to look for it).

In time, developers will be well aware of when to use/not use the fat arrow syntax.

EDIT: In summary, just as Dave says, "Don't use fat arrow syntax for methods." Short, sweet, good rule of thumb.

@polotek
Copy link
Author

polotek commented Apr 12, 2012

I'm not arguing for the perspective of library.js. I'm arguing for app.js and assuming it's a separate programmer that didn't write library.js. I'm arguing I now can't expect anything I do with those methods to be flexible for mixins or other types of dynamic dispatch. The argument that there are other ways to create functions/methods is insufficient. It suggests that everyone knows what they're doing and is thoughtful enough before hand to use the right kind of functions. I'm arguing that people will use fat arrow because it's there. Because it's new and because people love that it means you don't have to worry about this. I'm suggesting a proliferation of "just use fat arrow" libraries and utilities that stunt composability and reuse.

I did just realize that using fat arrows in object literals like this is just wrong. The context doesn't bind the way you would hope. I guess that does make things different, but it also makes me sad that there's yet a third syntax for functions in order to get the mix of behaviors we all want. And my general question stands. Fat arrow might increase the proliferation of functions that come pre-bound when that is not necessarily what is intended or what is preferable. It's just a consequence of the nice syntax. How likely is this and how much damage will it cause?

@dherman
Copy link

dherman commented Apr 12, 2012

I guess I'm partially sympathetic to this argument. I mean, the sense in which I disagree with "people will use it because it's shorter" is that it's shorter just to write, say, null for the RHS of every function, but you can't do that because it's wrong. :-) But maybe the way I would phrase it is, methods deserve love just like callbacks do, so why can't they have equally sweet syntax? That's why I still find thin arrow attractive. It means you can write:

// library.js
var Foo = {
    getValue: () -> "some value",
    somethingUseful: () -> this.getValue()
}

// app.js
var Bar = mixin({}, Foo, {
    getValue: () -> "alternative value"
});

Bar.somethingUseful();

Now there's literally zero difference in syntactic cost between lexical this and dynamic this; it's just -> vs =>, same character count. But as I say, a lot of people are pretty upset at the idea of two new function shorthands. But I'll admit that I personally find it pretty attractive. I'd even go so far as to say the method shorthand is less attractive than thin arrow.

Dave

@BrendanEich
Copy link

The way I see it, in my original JS ten-days-in-May-95 haste, I folded four (at least!) different use-cases into one syntactic form, and that made the callback use-case error prone, and the no-|this| function use-case a bit verbose (compared to an arrow shorthand).

Making special forms for methods (the definition shorthand in object literals), and for callbacks and local functions wanting outer |this| (fat arrow), helps users who learn these forms avoid mistakes and write more concise code. But there's a learning cost.

The sunk cost of having learned to use var self=this or .bind(this) is not entirely good-sunk-cost (see http://en.wikipedia.org/wiki/Sunk_costs#Loss_aversion_and_the_sunk_cost_fallacy). People keep writing bugs where callbacks or array extra downward funargs are written using 'function' and assumed to get the outer |this|. Tom Van Cutsem (an expert) did it and only caught it a while later. I've done it.

/be

@BrendanEich
Copy link

To Dave's last comment, I originally wrote up arrow function syntax with both -> and => and we cut -> to get consensus. We could try to get -> back in, at some cost and risk of blowing consensus.

I do think the method shorthand is even sweeter than ->, though. Dart went that way for all functions, at the price of mandatory semicolons. Having to write : ( and ) -> instead of just ( and ) is a bit of a drag.

/be

@dherman
Copy link

dherman commented Apr 12, 2012

Brendan: you're forgetting return -- that's a big difference.

Dave

@gigafied
Copy link

I would personally prefer syntax that made it clear you are expecting a zeroth argument.

There is a lot of code that uses call and apply, and yet my main gripe with it is that you are defining what this is dynamically somewhere, yet there is no indication of it outside of the function definition. This is why I like fat arrow/skinny arrow syntax.

Take the following:

forEach(a, function() {
    console.log(this);
});

This is just a small example, but consider that there was a lot more than a single console.log statement here. What would you expect this to equal? It's not very scannable and very easy to overlook.

Here is the forEach method:

function forEach(arr, fn) {
    var i,
        m,
        al = arr.length,
        fl = fn.length;

    for (i = 0; i < al; i ++) {
        fn.call(arr[i], i);
    }
}

Using skinny arrow to say "this expects a zeroth argument" is extremely helpful here.

forEach(a, () -> {
    console.log(this);
});

This makes the above much more readable to me.

This of course assumes that people use => in place of function for most common use cases and only use -> when they really expect a dynamic this.

If you don't add a fat arrow alternative (i.e. skinny arrow), then what you are left with is function and unfortunately, you won't know which reason function was used instead of fat arrow, because it could be:

  • Old habits. Developer was used to writing all functions with function, regardless of whether or not the function expects a zeroth argument.
  • Code was written back in the "ES5 days." Even if people start using ES6 syntax, there will be plenty of cases where a library that uses ES6 features, has plenty of pre-ES6 code.
  • Whether or not it was used because the function expects a zeroth argument.

In summary, I think fat arrows are a good addition, but I think skinny arrows (or another operator that serves the same purpose) would be an even better addition.

If you see function you know it was pre-ES6, if you see => you know what this represents and if you see -> you know that you are expecting a zeroth argument.

@DavidBruant
Copy link

@BrendanEich wrote: "People keep writing bugs where callbacks or array extra downward funargs are written using 'function' and assumed to get the outer |this|. Tom Van Cutsem (an expert) did it and only caught it a while later. I've done it."
=> +1 https://gist.github.com/2279059#file_person.js Line 9-11. It took me a half hour figuring out that the age not increasing came from non-lexical this.

I see a concern that hasn't been discussed in this thread:

@polotek wrote "Programmers love short syntax. And they will jump on this. Unfortunately those same developers who don't get dynamic context, the ones we are trying to save, may not bother to be selective in when they use fat arrow functions vs normal ones. So we'll get proliferation of these things. And then it'll become a real problem for anyone who is used to taking advantage of the flexible power of javascript."
"Then I have to decide if I want to edit library.js or find some other way to reuse it's capabilities. As I said, this happens now. But I think it will happen much more frequently with fat arrow. Simply by virtue of the fact that is a preferable syntax for writing functions, and people will use it."
=> Yes, people will misuse the short syntax. But this is not a new problem.
People generally misunderstand JavaScript. It's probably true for other languages, but JavaScript has a broader audience.
Sometimes, when I view source a website, I notice that people write "new Array()" all over the place and use the "array" as string->value maps. These often come from PHP. This is a harmless mistake except when you want to use a normal 'length' property (true story). They should be using normal objects, but they don't because they misunderstand the language.

I agree this is a problem and this will certainly hit for the shorter syntax as you and Angus predict. Especially when you need to write code that uses or works with code that does this. But this is not a language problem. It's a social problem. It's an education problem.

This is not a problem that the language itself should be aiming at solving in my opinion. This is a problem that can be solved by educating people or by designing a language that compiles down to JavaScript and is clearer to novice programmers.

"What are the criteria for Good change?"
=> My own opinion on this is that at least, education issues should not get on the way.
JavaScript is a programming language, not an easy one, people misunderstand it a lot, but it needs to evolve. Conjecturing on future misunderstanding and misuse doesn't help since it will happen no matter how the language is changed.

Regarding education, I'd like to add a word and say that I wish more people participated in JavaScript documentation. There are dozens of blog posts, tweets, jsPerfs things released each day that just get forgotten over time, because they are not maintained nor updated. Documentation is one key to solve this problem of sustainable education.
Whatever change happens in JavaScript, I hope there will be people expert enough like you, @polotek to document the new feature, how it differs from previous features and common traps. Thanks :-)

@dherman
Copy link

dherman commented Apr 12, 2012

@gigafied:

I find your points pretty compelling. I know that many people think two different arrows is complex, and I believe Jeremy has said that CoffeeScript has suffered from confused users due to the two arrow forms. Yet all the same, the two different arrows just jump out in a nice way, telling you explicitly how this will be bound in the body. Somehow that little bit of grawlixy ASCII art (it's only wafer thin!) stands out nicely amidst a bunch of alphanumeric keywords and identifiers so it stands out, whereas IME I often find that function fades into the background so that I don't notice that it's quietly rebinding this.

But I would encourage people to try writing some example code to see what the alternatives look like "in the wild." Rick Waldron has done some nice experiments with my earlier proposal, which involved both arrows as well as some TCP stuff (which I've recanted on, don't worry). But these kinds of experiments are useful because readability is hard to judge in the abstract. Here's one of Rick's examples:

https://github.com/rwldrn/popcorn-js/compare/tri-lambda

Dave

@gigafied
Copy link

I will try to put together some more examples to illustrate, but my points kind of overlap with Marco's in that while introducing skinny arrow along with fat arrow might introduce some confusion, I think it is a necessary confusion.

What I mean by this is... envision this scenario:

Bob: "Hey what's this => thing do?"
Joe: "Oh, that's just the new way to write function in ES6"
Bob: "Oh, cool."

That is where the conversation ends, and having been around plenty of novice/amateur developers, I know for a fact conversations like this will happen a lot.

This leads to a basic fundamental misconception/lack of knowledge, which can have very detrimental/far reaching effects. Sadly, a lot of people don't understand some pretty fundamental things about the languages they write code in.

By introducing skinny arrow, it forces another conversation to be had:

Bob: "Hey, so I now know what => does, but now I see ->. What does that do?"
Joe: "Oh, that's just another way to write function in ES6"
Bob: "Hmm, how is it different than =>?."
Joe: "I don't know."

Bob now goes and googles it, reads an article on the differences and gains a fundamental understanding of the differences between the two. He then informs Joe and it becomes common knowledge in their workplace.

Without skinny arrow, the second conversation never happens. I believe this is detrimental to JavaScript as a whole.

It's also unfair to all the good functions that exist/will exist that expect a zeroth argument. They are not invited to the party. Yet, all the boring, predictable fatty functions get invited.

Why should lexical-this functions get a new, awesome operator over dynamic-this functions?

I am hereby pronouncing myself as a Function Rights Advocate.

@jfhbrook
Copy link

I originally wrote up arrow function syntax with both -> and => and we cut -> to get consensus.

What about cutting => and getting consensus on -> instead?

@BrendanEich
Copy link

@jesusabdullah: no, see Kevin Smith's results (the "BTF" thread in es-discuss). => covers 90% of (method-definition-shorthand adjusted) code bases studied. Apart from constructors and methods, -> is the less common, more likely to go wrong choice for syntactic shorthand. We should have nothing, =>, or => and -> but not just ->.

/be

@jfhbrook
Copy link

@BrendanEich What code bases did you study? At least anecdotally (I have no idea how you actually measure this), I use the shit out of .call and .apply.

@gigafied
Copy link

@BrendanEich @jesusabdullah

The issue I have is that the fat arrow is not just solving simplifying BTF, it is introducing shorter/cleaner syntax for function declarations. Fat arrow tries to kill two birds with one stone:

  • Shorter syntax
  • Easier way to accomplish BTF

What we end up with, if there is no ->, is code which looks inconsistent and just odd because there is a mix of => and function.

var lexicalFn = (some, args) => {
    console.log(this);
}

var dynamicFn = function (some, args) {
    console.log(this);
}

or worse

var lexicalFn = (some, args) => {
    console.log(this);
}

function dynamicFn (some, args) {
    console.log(this);
}

vs

var lexicalFn = (some, args) => {
    console.log(this);
}

var dynamicFn = (some, args) -> {
    console.log(this);
}

I understand that this is a hard point to argue because it is an addition, not a deletion to the spec. Is it absolutely necessary? No, because function for all intents and purposes accomplishes what we need it to, but at the same time you could argue that fat arrow is unnecessary as well because we already have bind().

For me, it is like adding an increment operator ++, without the decrement operator --, so I'm left with code like:

i ++;
decrement(j);

Even if you could point to analysis and statistics and say, "97% of the time, people increment, they don't decrement variables", I would still argue for adding the decrement operator. Uniformity and symmetry are important, I don't think it is fair to just boil it down to statistics.

I am a fan of the strawman arrows, but only if they come as a package deal. Otherwise, I'm against adding it at all.

@polotek
Copy link
Author

polotek commented Apr 13, 2012

I didn't mean for the discussion to devolve into talk about just fat arrow. I really appreciate all the info here though. It has been helpful. I'm more than satisfied with the answers here on why fat arrow is a Good Thing™.

I guess I'm still not sure how the responses from @dherman and @BrendanEich serve to convey how we plan to mitigate the potential follow on problems with these changes to the language. What I'm reading is "that's not what they're for" or "I don't think that's a problem". I see the stats that suggest that the benefits of the proposal are there. But there's no data to suggest that the pitfalls won't end up being larger than we anticipate. Just like we couldn't anticipate dynamic this being such a huge problem. An advanced feature to be sure, but not a "we have to change the language to protect people from this" level of problem. (maybe Brendan did and it just couldn't be helped)

In fact reading back over the answers here, it seems more alarming. If fat arrows hit 90% of use cases, why wouldn't people just use them all the time? This seems to suggest they'll even appear in places where they shouldn't. The combination of short syntax and the fact that it's probably 90% what you want is a recipe for the same type of blind usage that makes people use === everywhere without thinking about it.

It's also relevant to note that my first inclination when using fat arrows was that they could go into an object literal as methods. It turns out that was wrong. But it also suggests that it's non-intuitive. And instead of working this out between plain old functions and a new, second type of function, we instead point people to yet a third way to create functions. I would argue that the method shorthand syntax also suggests that the functions will be bound. Not to lexical this but to the object being defined. It looks like they belong to the object because they are part of the syntax of defining it, but that's not the case. So what we're looking at is, we've attempted to separate the various usages of functions and given them their own syntax to help differentiate intent. But we've made it less intuitive to reason about the behavior of these, or to learn that behavior and keep it in your head without the need to look it up again.

This is the type of confusion and inconsistency I'm worried about. So outside the context of fat arrow, I want to pose the original questions again. How much weight do these concerns get? How do we decide how likely they are to be a problem or how do we mitigate them? What criteria does TC39 use to answer these questions?

@BrendanEich
Copy link

@polotek: you're right, => could be overused and then we'll have wrong-this bugs of the reverse kind: lexical when the author wanted dynamic.

One thing I believe will help here: method definition shorthand is even sweeter than any arrow after a parameter list after a : after a property name.

We could try for -> too, working on TC39 members in advance of the May meeting. I'll report back on how that goes.

/be

@polotek
Copy link
Author

polotek commented Apr 13, 2012

A small aside on the subject of whether it's confusing to have both -> and =>. I think it is. But maybe that's because there's a disparity between the distance between the problems and the distance between the solutions. Let me try to explain that.

The problems are these.

A) The function keyword is too long. A shorthand syntax is badly desired. I would add the assumption that besides this, and the dynamic context (see B), that functions are generally well regarded and we don't to mess with those semantics.
B) The dynamic context feature of functions is very confusing. But more than that, the data suggests that the default is uncommon case, while the common case (lexical this binding) is unwieldy to achieve and easily forgotten.

So we have 2 solutions.

a) Use -> for functions. They behave just like the keyword, but they're short and sweet. Yay!
b) Introduce a new syntax that makes the common case of lexically bound functions explicit and intentional. This is also a good idea. Let's use =>. And that's where it gets confusing.

Because we've taken a small change to the functions we know and love, and a big change to introduce a new construct with new semantics, and made the syntaxes very similar. So these 2 concepts that should be kept at arms length from each other are now separated by only one character choice. That may be where the confusion lies. Short arrow and fat arrow look very similar, so you want to group them together in your mind. But they actually suggest very different usages and you should probably treat them as separate concepts.

The obvious question from this line of reasoning is can we offer a different syntax for one or the other so they solve the same problems, but aren't so easily conflated?

@gigafied
Copy link

@polotek

When I think of it in terms of -> is "old" function and => is "new" function I can see the argument where they are two different enough things for it to cause confusion and be an issue.

However, I don't think -> should be a replacement for function, I think it should mirror the type of function created with => (with the one difference being lexical vs dynamic this).

With that in mind, when I think of it in terms of -> and => denote functions and - means dynamic and = means lexical, they really aren't that different. The one character difference is there for the sole purpose to say "this function has/doesn't have lexical scope". In these terms, I think it is perfectly acceptable. This means that functions created with -> would also not have access to an arguments variable.

constructor goes away from lexical-this functions as a side-effect. You don't even need to list it along with the differences between the two types of functions created with -> and => because in no case would it have any relevance/usefulness with hard-scoped objects anyway.

If implemented in the way I described above, this leaves us with four different types of functions.

  • Lexical-this functions
  • Dynamic-this functions
  • Methods
  • "Old" functions (i.e. using function)

With this in mind, I think ES6 should deprecate function, leaving us with only the three. function can still of course, be supported, even though advised against. Then, at some later date, probably when I have grey hair, function could be made obsolete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment