Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Discussion] The ES6 future of CoffeeScript. Looking for a ES6-friendly CoffeeScript replacement. #128

Open
lolmaus opened this issue Jan 21, 2015 · 39 comments

Comments

@lolmaus
Copy link
Contributor

lolmaus commented Jan 21, 2015

ES6 is gaining a lot of spread. It introduces a lot of new concepts into JS. Here are nice overviews: short, comprehensive.

And people already use those features! Thanks to the effort from such projects as 6to5, es6-transpiler and Traceur, for a modern JS developer who does not use CoffeeScript, there is simply no reason not to use ES6 nowadays. Many modern JS projects are already being written in ES6. Moreover, popular JS frameworks already demand that their users code in ES6 out of the box (this includes Angular 2 and EmberJS).

CoffeeScript is essentially locked at ES5. Some ES6 features are accessible via backticks, but others directly conflict with the CS syntax, including said backticks. With main contributors having explicitly refused to support ES6, CS is doomed to become a thing of the past.

This worries me a lot. I still use CS for all my projects, but i will doubt using CS in a new project.

My problem is that i absolutely adore CS syntax. I worship it. After getting used to CS, i can't stand vanilla CS anymore.

Here's an extract from React sources i saw today (by the way, React is written in ES6):

for (var key in props) {
  if (!props.hasOwnProperty(key)) {
    continue;
  }

  style[key] = props[key];
}

This can be rewritten in CoffeeScript as:

for own key, value of props
  style[key] = value

JS code is more than 100% larger than CS with zero benefit!

Back to the matter. With CS maintainer having doomed CS to be obsolete, we need a replacement.

I suggest that here we:

  1. Collect references to and discus JS preprocessors that implement CS-like syntax while maintaining ES6 compatibility. I failed to google up any but maybe you're luckier.
  2. Discus starting a new preprocessor project that would implement CS-like syntax while maintaining ES6 compatibility.
    • We should start discussing the new syntax. Some of core CS features conflict with ES6, namely backticks and fat arrows, others become redundant (splats, comprehensions, classes, etc). I suggest we give precedence to ES6 and change CS convensions.
    • Should we start from scratch or fork CS?
    • Anyone feels skilled enough to be a primary maintainer/contributor of the CoffeeScript killer? :D I don't. :(
@sukima
Copy link
Contributor

sukima commented Jan 21, 2015

A few notes, You're comparing ES6 using ES5 as an example:

for (var key in props) {
  if (!props.hasOwnProperty(key)) { continue; }
}
// VS
Object.keys(props).forEach(function(key) {
  var value = props[key];
}

With that said the design goal of CoffeeScript is to support all browsers down to ES3 (enter IE6 stage left). Because of that any ES6 is incompatible with ES3 syntax. The only way to by pass this is with backticks. This is expected as CS was designed to trans-pile to JavaScript which needs to be compatible.

For now backticks are the way to manage this and I use them in my Ember-CLI project which is an es6-transpiler project. It is likely that in the future as the rest of the world finally moves from the old JavaScript and ES6 gains more traction that CS will diverge and there will be an CoffeeScript 3 version and a CoffeeScript 6 version. I doubt this will not be driven by the core CoffeeScript dev team but instead by a forked team.

Anyway enough speculation. I feel the goal of this Cook-book project is to offer patterns / recipes for such a case when a user wishes to use some of the ES6 syntax in the current version of CS. An example might look like:

`import Ember from 'ember'`
`import MyModule from '../my-module'`

MyClass = Ember.Object.extend
  foo: "bar"
  baz: -> "baz"

`export default MyClass`

@backspaces
Copy link

It might be worthwhile separating out the syntax issues from semantics/polyfill issues.

So array comprehensions are syntax (alas not in es6 but still supported in some of the transpilers) but a lot of the improvements in JS objects/Arrays/iterables etc not syntax and are available to CS. Modules are sorta in the middle and easily managed with backticks or direct use of SystemJS.

This would help me just understand how bad it would be to just jump to es6! Array Comprehensions still are huge for me.

James Long (@jlongster) had a nifty suggestion for the TC39 committee: when considering a change in syntax to Javascript, build a SweetJS Macro of it for testing. I'm wondering how hard it would be to do that for any much-loved CS syntactic feature not in ES6? Build macros for them?

Then we'd be OK with es6 itself, filling in with Sweet macros any features we badly miss.

@sukima
Copy link
Contributor

sukima commented Jan 22, 2015

For the rather mind-numbing discussion about this very topic check out issue jashkenas/coffeescript#3162.

IMHO

I found I mostly agreed with the following quote from @bjmiller:

The most important feature of ES6 is still not implemented anywhere, and is likely to arrive last because it doesn't change/affect syntax and is apparently harder to do than I think: Tail call optimization. The most divergent (and, widely in use already) feature in ES6 is generators, and the next release of CS will have full support for them.

ES6 modules are kind of terrible, and no one can figure out why the TC didn't just use CommonJS, which everyone loves. Browserify handles modules and modularity better than the ES6 "modules" feature for the client side, and any use cases that fall outside of what Browserify does are probably handled well by Webpack.

So much of ES6 is straight up hype. "Solutions" that we've already solved ourselves by building libraries, or which only solve problems that come out of a textbook on computing theory.

With all that said, a small subset of ES6 things should probably be allowed to "pass through" to JS without the use of backticks, because some people will be happier that way, and everyone else can just ignore it. Especially if it's easy to implement, with no special syntax. But, even if it isn't, CS still does what it does very well, and you can build anything that you need using it.

ES6 will be obsolete before CoffeeScript is.

@aurium
Copy link

aurium commented Jan 23, 2015

Hi @lolmaus, you told "Some of core CS features conflict with ES6, namely backticks and fat arrows", but reading http://6to5.org/docs/learn-es6/#arrows we see "Arrows are a function shorthand using the => syntax. They are syntactically similar to the related feature in C#, Java 8 and CoffeeScript. They support both expression and statement bodies. Unlike functions, arrows share the same lexical this as their surrounding code." Is this not exactly the CS fat arrow behavior?

In the example we can see:

v => v + 1;
(v, i) => v + i;
v => {
  if (v % 5 === 0)
    fives.push(v);
};

I sure could rewrite this in CS as:

(v)=> v + 1
(v, i)=> v + i
(v)=>
  if v % 5 is 0
    fives.push(v)

So... I don't see a problem to translate CS's => to ES6's =>


About the backticks (Template Strings), i don't see any problem. CS has a way better design in this area. While JS has ' and " with the same meaning, CS has ' as a JS equivalent (plus multiline) string definition; " as a multiline template string (exactly like ES6 backticks); and backticks with a transcendental meaning like we have in Ruby, Perl, PHP...

I don't see any problem in translating CS's double quote to ES6's backticks, on the contrary, that is much better to my keyboard, and to the coding culture.


I believe CoffeeScript needs a "target parameter", or a "feature translation configuration", allowing us to compile CS to old JS or bleeding edge JS, or also to special environments.

So, is there a real translation problem between CS and ES6 or is this only a FUD?

@lolmaus
Copy link
Contributor Author

lolmaus commented Jan 23, 2015

@aurium,

Compiling CS fat arrow to ES6 fat arrow will be a breaking change. The awesomest feature of CS fat arrow is being able to reference both local scope via this AND outer scope via this. This is impossible with ES6 fat arrow.

As for backticks (and other constructs including the fat arrow) is that they mean different things. It's fine for CoffeeScript, but if we start a new preprocessor, we should embrace the ES6 syntax and use other syntax constructs for preprocessor features.


@bjmiller in the CoffeeScript thread has stated an awesome idea, you should totally read his original comment.

For the features that have syntax conflicts between CS and ES6, CS does a better job. A CoffeeScript coder will never feel a need in those ES6 features.

  • Rest and sperad? What a weird way to call splats.
  • Fat arrows? Oh, you can't pass them as callbacks to libraries, because some libraries fucking bind callbacks to objects and suppose you this the shit out of 'em?
  • Multiline strings? Without interpolation?
  • Destructuring assignment? But you can't use it to assign instance attributes in class?
  • Classes, you say? Oh, but you can't define properties, only methods, and still have to do this.foo = foo in the constructor?
  • Comprehensions? Don't make me laugh!

Other features either don't conflict with ES6 syntax or can be passed through backticks.

Also, it makes perfect sense to use CoffeeScript AND a ES6 transplier like 6to5. Or you can use libraries like RSVP to have more powerful alternatives to certain ES6 features.

Can anyone name a feature where CoffeeScript falls short?

@backspaces
Copy link

Heh. Good thing workflow tools (Gulp etc) handle transpilation so well. I
doubt there are many of us willing to give up both CS and ES6. And
because TC39 is headed to yearly editions, we'll seldom use vanilla JS,
there'll be yet another ESnext.

On Fri, Jan 23, 2015 at 12:48 PM, Andrey Mikhaylov (lolmaus) <
notifications@github.com> wrote:

@aurium https://github.com/aurium,

Compiling CS fat arrow to ES6 fat arrow will be a breaking change. The
awesomest feature of CS fat arrow is being able to reference both local
scope via this AND outer scope via this. This is impossible with ES6
fat arrow.

As for backticks (and other constructs including the fat arrow) is that
they mean different things. It's fine for CoffeeScript, but if we start a
new preprocessor, we should embrace the ES6 syntax and use other syntax

constructs for preprocessor features.

@bjmiller https://github.com/bjmiller in the CoffeeScript thread has
stated
jashkenas/coffeescript#3162 (comment)
an awesome idea, you should totally read his original comment.

For the features that have syntax conflicts between CS and ES6, CS does a
better job. A CoffeeScript coder will never feel a need in those ES6
features.

  • Rest and sperad? What a weird way to call splats.
  • Fat arrows? Oh, you can't pass them as callbacks to libraries,
    because some libraries fucking bind callbacks to objects and suppose you
    this the shit out of 'em?
  • Multiline strings? Without interpolation?
  • Destructuring assignment? But you can't use it to assign instance
    attributes in class?
  • Classes, you say? Oh, but you can't define properties, only methods,
    and still have to do this.foo = foo in the constructor?
  • Comprehensions? Don't make me laugh!

Other features either don't conflict with ES6 syntax or can be passed
through backticks.

Also, it makes perfect sense to use CoffeeScript AND a ES6 transplier like
6to5. Or you can use libraries like RSVP to have more powerful alternatives
to certain ES6 features.

Can anyone name a feature where CoffeeScript falls short?


Reply to this email directly or view it on GitHub
#128 (comment)
.

@sukima
Copy link
Contributor

sukima commented Jan 23, 2015

The awesomest feature of CS fat arrow is being able to reference both local scope via this AND outer scope via this.

I'm not trying to be offensive but this doesn't feel like a feature to me. The mixing of CS and JS to handle scope suggests a abuse of the language. Why not set a variable at the correct scope level instead of swapping @ and this (back ticked).

This is defiantly a code smell and if used needs refactoring.

@sukima
Copy link
Contributor

sukima commented Jan 23, 2015

For the features that have syntax conflicts between CS and ES6, CS does a better job. A CoffeeScript coder will never feel a need in those ES6 features.

Oh I like these, Spot on.

@backspaces
Copy link

@sukima .. oh gawd, thanks for explaining the (kinda cool) difference between @ and this .. depends on context. Er..not that I'd dream of using it! :)

@sleepyfox
Copy link

Regarding your first point:

  1. Collect references to and discus JS preprocessors that implement CS-like syntax while maintaining ES6 compatibility. I failed to google up any but maybe you're luckier.

Might I suggest LiveScript? http://livescript.net/

@lolmaus
Copy link
Contributor Author

lolmaus commented Jan 30, 2015

Thx for your contribution, @sleepyfox.

LiveScript is the only preprocessor i'm aware of that possesses the spirit of CoffeeScript and has a large community. But their ES6 status is identical to that of CoffeeScript. The only difference is that LiveScript haven't closed their ES6 issue ticket, but it's as inactive as CoffeeScript's.

I no longer think that lack of support of native ES6 syntax in CoffeeScript is a pitfall. As i said above, it's not necessary. Also, CoffeeScript has just added support for generators, so it's not forever stuck in ES3 as it seemed before.

I thought of writing an article to coffeescript-cookbook.github.io that compares ES6 features and their CoffeeScript alternatives. Do you guys think it's worth doing?

@sleepyfox
Copy link

What is in ES6?

  • Generators - in LS (and CS since yesterday, which surprised the hell out of me!)
  • Promises - better handled by Bluebird et al
  • Rest parameters - already in CS/LS
  • Default parameters - already in CS/LS
  • Destructuring - already in CS, better implemented in LS with { x } being { x: obj.x }
  • Arrow functions - already in CS/LS
  • Template strings - already in CS/LS
  • Let - already in LS
  • Classes - already in CS/LS
  • Modules - better left to CommonsJS (or any one of a number of alternatives)
  • For...of - already in CS/LS
  • Map/Set/WeakMap/WeakSet - already accessible from CS/LS

So all in all, there's really no reason to drop LS for ES6 as far as I can see, and CS supporting Generators really just means more options for people.

@sleepyfox
Copy link

Oh, and yes, I do think it is worth doing writing an article comparing CS to ES6 features, the more people are informed about this stuff the better off we will be.

@sgrif
Copy link

sgrif commented Jan 30, 2015

Also missing get and set literals for classes.

@ccschmitz
Copy link

For anyone who is interested in a comparison of CoffeeScript and some of the things coming in ES6, I wrote an article to show some of the differences in syntax.

@sukima
Copy link
Contributor

sukima commented Jan 31, 2015

This is going a little off topic but the lack of getters and setters syntax can be worked around by using Object.defineProperty and Object.defineProperties. Not as cool as language support but the functionality is there. This is a kin to es6 modules support via back ticks. Personally I don't see the rush to es6. It's not like this stuff is here today gone tomorrow, Hell look at how long it took to finally say goodby to IE6 and were still stuck at es3 with IE8. We have years to worry about this cruft. Well, that's just my humble opinion.

@sleepyfox
Copy link

I remember from my old Java programming days that using getters and setters was considered a code smell and violated the "Tell, don't ask" rule...

@sukima
Copy link
Contributor

sukima commented Feb 1, 2015

@sleepyfox in general I would agree, especially in most code example on getters and setters. However there is a time and place for them. For example when writing a library where you need to manage change events. An example would be Backbone where it's API requires all assignment and fetching to be done with set() and get() this is to allow the Backbone system to send change events to anyone listening. The advantage is that the application can be data driven instead of DOM driven like most jQuery based pages. Now contrast that to say Ampersand who adds the getter and setters notion. myModel.myProp = 'foo'; will cause a change event because myProp is actually a setter.

So yes it can be abused and violate the "Tell, don't ask" but in the above example you are telling it to set a property who's side effect is intentional, well known, and expected. In conclusion, there is a time and a place for them even if most examples don't teach when and where and instead tend to insight code smells.

@sleepyfox
Copy link

It is pretty simple to wrap state in a closure (use the module pattern) and prevent anyone fiddling with your object's properties. That way you can enforce your class's contract.

@lolmaus
Copy link
Contributor Author

lolmaus commented Feb 5, 2015

I found another ES6 feature that is impossible with CoffeeScript, at least without too much scaffolding: private properties.

Demo: http://goo.gl/juKxyx

Also, async/await: http://goo.gl/wnQS4j (Is IcedCoffeeScript still alive?)

@sleepyfox
Copy link

@lolmaus really? I do three impossible things before breakfast, behold!

class Person
  constructor: (name) ->
    person_name = name
    @get_name = -> person_name

Bob = new Person('Bob')
alert Bob.get_name() # = 'Bob'
alert Bob.person_name # = undefined

Alternatively you can instead apply Crockford's 'module pattern' and use factory functions like so:

makePerson = (name) ->
  do ->
    secret_name = name
    visible =
      get_name: -> secret_name

Bob = makePerson('Bob')
console.log Bob.get_name() # = 'Bob'
console.log Bob.secret_name # = undefined

I hope this helps.

@lolmaus
Copy link
Contributor Author

lolmaus commented Feb 5, 2015

@sleepyfox, a number of issues with your approach:

  1. You're not using the class directive as intended. You have to write all your instance methods inside the constructor method, otherwise they will not close over your private property. This makes the code harder to read: you have to spend time and effort just to grasp what's going on there.
  2. You're not able to add more private properties from outside of class definition (monkey patching).
  3. If you inherit your class into another class, the new class methods will not have access to the parent class' private properties.
  4. You're unable to override the constructor when inheriting, otherwise you will inherit nothing.
  5. You're not able to unit-test private properties, though i'm not sure whether ES6 is better at this.

@sleepyfox
Copy link

@lolmaus

  1. Yes, I'm not using the class directive as intended, that is because the designers did not 'intend' to have private properties for classes, because CoffeeScript is 'Just JavaScript', and by that we mean 'Just ES3'. It is a hack, to be sure, but it is an easy hack. It isn't meant to be a robust implementation of Java-style Class encapsulation.
  2. Yes, I am not able to add more private properties from outside the class. This is a good thing
  3. Yes, child classes will not have access to parent classes private properties. I direct you towards "Favour composition over inheritance"
  4. Yes, I'm unable to override the constructor when inheriting, see 3.
  5. Yes, I'm not able to unit test private properties (or methods). This is a good thing, instead I should be testing the public interface. If something private is so complex that it doesn't obviously have no defects then it needs to be extracted into a separate class and unit tested.

The reality is that JavaScript is not Java, nor is it C#, C++ or SmallTalk. Objects do not work the same way, and inheritance works in a very different way (i.e. Self's prototypical version) than other so-called OO languages. Part of learning how to use JavaScript effectively is in knowing what works, and what doesn't. Trying to write C++ or C# using JavaScript, just because there is thing called Object, or trying to write Java code in CoffeeScript just because there is a thing called Class is an anti-pattern.

Personally I wish @jashkenas had called it something other than Class because I think it gives the wrong impression, but we are where we are.

@lolmaus
Copy link
Contributor Author

lolmaus commented Feb 5, 2015

Hey @sleepyfox, i agree with your reasoning (except for 5: it ruins TDD).

Bu you're mixing two different things here: language capabilities and best practices.

The fact that you're able to shoot into your leg does not mean that gun manufacturers should make it impossible to shoot into legs. Sometimes shooting into a leg is what you need from a gun.

In much the same way, sometimes you do need to deliberately monkeypatch a class. Sometimes you need to simply add a method to a single instance of a class, and that method should have access to that instance's private properties. Sometimes subclassing is the way to go (and it is very often, even in JS. Have a look at Ember).

A JS preprocessor should never limit a user in what he's able to do with JS.

@sleepyfox
Copy link

@lolmaus OK, let's agree to disagree, about testing private methods, sub-classing and monkey-patching.

Guns: remind me never to stand near you on a range.

As far as what a JS pre-processor should or should not do, I think that's really the purview of the person who writes the pre-processor. If we disagree with their premise, we're perfectly capable of writing our own.

Going back to the original point: you said private properties were impossible with CoffeeScript; I showed you two different quite simple ways that they could be implemented, and I'm sure there are more.

In most cases I have observed encapsulation is well enough served by closures and modules to make the need for secure private properties or methods moot. YMMV.

@michaelglass
Copy link
Member

Fun discussion but this is totally off-topic for the coffeescript cookbook

Would like to close this issue. Thoughts?

@lolmaus
Copy link
Contributor Author

lolmaus commented Feb 9, 2015

It indeed does not relate to the cookbook directly, but this project is a community of CoffeeScript enthusiasts, people who should be worried with the indeterminate future of CoffeeScript.

Unless we've got strict policy for the issue queue, i suggest we keep this open for discussion.

@sleepyfox
Copy link

With the addition of generators to CoffeeScript (something that forks like LiveScript have been working with for some time) the last major reason for thinking of swapping to ES6 has been removed for me.

@lolmaus
Copy link
Contributor Author

lolmaus commented Feb 9, 2015

I love CoffeeScript and i can't imagine my life without it. I would hardly be a frontend dev if it weren't for CoffeeScript.

But the matter is that JS is going to evolve further, and CoffeeScript will get more and more out of sync with JS syntax and constructs. It will end up being a weird thing used only by those oldfags who started using it in ES3 era and were too stubborn to move on. It's already being considered an ill manner to use CoffeeScript both in open source and private projects. The JS world is becoming squeamish about CoffeeScript, and not unreasonably.

We do need a preprocessor that posesses all the CoffeeScript goodies (meaningful indentation, implicit return, optional parens and commas, ?, ranges, etc) but at the same time embraces contemporary EcmaScript standard as much as possible:

  • It should be trivial for JS developers to switch to the preprocessor.
  • The code should be understandable to JS devs who never learned the preprocessor.
  • It should compile to contemporary ES* and leave it up to the user whether to leverage a transpiler or a ES*-capable runtime.
  • A transpiler could be integrated into preprocessor CLI as a flag, e. g. nextscript compile foo.next --6to5, and it will produce ES3 code.
  • It should be evergreen (i. e. be kept up to date with ES*) so that it doesn't repeat the fate of CoffeeScript. Thus, it should follow strict semver and be able to produce deprecation warnings.

Thoughts?

@sleepyfox
Copy link

@lolmaus it sounds like you're asking for "a better version of the ES6 draft that looks more like CS", is that what you want?

@lolmaus
Copy link
Contributor Author

lolmaus commented Feb 9, 2015

@sleepyfox, why would i want a different version of an EcmaScript draft? That's ridiculous, no runtime or transpiler would ever support it.

@sleepyfox
Copy link

Because drafts leave draft status to become approved. And then runtimes (eventually) support them.

@backspaces
Copy link

Are there two lists somewhere of the ES6 features, one of syntax changes
(i.e. impact CS), one of object improvements that are already in CS as
soon as they appear in JS?

Then looking at the first list, factor out those features that are not in
CS? For example, modules but not default args or splats or classes?

It would give the conversation a more precise focus: what can't we do in
CS running on top of ES6. Boy would I like that!

-- Owen

On Mon, Feb 9, 2015 at 3:21 AM, Sleepyfox notifications@github.com wrote:

Because drafts leave draft status to become approved. And then runtimes
(eventually) support them.


Reply to this email directly or view it on GitHub
#128 (comment)
.

@lolmaus
Copy link
Contributor Author

lolmaus commented Feb 9, 2015

@backspaces, no such list so far.

Here's what i know so far:

  • Exporting in the beginning of a multiline construct, e. g. export default function() {, without storing the exported feature in a variable.
  • Private properties; the closure hack is not counted.
  • async/await.
  • Getters and setters.

BTW, the CoffeeScript maintainer said that he might consider implementing modules but as an compiler to AMD/CJS rather than transpiler to ES6.

@michaelglass
Copy link
Member

Again, this is totally off-topic. the cookbook is a reference for beginners of either obvious or verifiable best-ish solutions. The end result of this argument will be

  1. not for beginners and
  2. a place where we can fight.

really think this is a distraction for this project and would prefer the conversation moved elsewhere.

@nmccready
Copy link

@michaelglass your probably right; but this does need to be discussed somewhere. Maybe a repo dedicated to this very issue?

@sukima
Copy link
Contributor

sukima commented Nov 5, 2015

I just ran into a CS incompatible issue: Tagged template strings

`hbs\`{{foo-bar}}\``

Does not work. It has to be in ES6 and there is a discussion about this issue.

@jimmywarting
Copy link

@lolmaus You can write it like this without ; and {}. They are not always needed

for (let [key, value] of Object.entries(props))
  style[key] = value

compare that to what coffeescript's for own key, value of props will compile to, what do you prefer if you don't need to support older platforms?

var key, value,
  hasProp = {}.hasOwnProperty;

for (key in props) {
  if (!hasProp.call(props, key)) continue;
  value = props[key];
  style[key] = value;
}

@auvipy
Copy link

auvipy commented Jan 3, 2018

FWIW, coffeescript 2.x generate ecmascript6 compatible javascript

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests