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

CS2 Discussion: Features: decorators #4917

Closed
coffeescriptbot opened this issue Feb 19, 2018 · 70 comments
Closed

CS2 Discussion: Features: decorators #4917

coffeescriptbot opened this issue Feb 19, 2018 · 70 comments

Comments

@coffeescriptbot
Copy link
Collaborator

From @rattrayalex on July 23, 2016 2:51

Decorators are an increasingly popular feature of ES6, particularly with React development.

However, they use @atSyntax, which clashes with coffeescript's this. shorthand.

We could:

  1. Come up with an alternative syntax for decorators
  2. Come up with a new syntax for this. shorthand
  3. Keep both as they are and just work hard to parse them
  4. Get rid of this. shorthand
  5. Don't add decorators
  6. Any other ideas?

Thoughts?

Copied from original issue: coffeescript6/discuss#9

@coffeescriptbot
Copy link
Collaborator Author

From @carlmathisen on July 24, 2016 21:27

It is unfortunate that ES6 generator uses @ as well, but we should try to keep as much BC as possible - especially when it comes to basic stuff like this shorthand that literally everyone uses.

You're probably right, it's hard to parse and distinguish between the two if decorators were to keep the same symbol.

class A
  @decorator
  func: ->

Granted, when transpiled today it doesn't make sense - @decorator just becomes A.decorator;, but it is still valid JS. Who knows, it might even trigger a getter function in someone's app. Framer JS, for instance, heavily uses function getters for their properties with Object.defineProperty. https://github.com/koenbok/Framer/blob/master/framer/BaseClass.coffee#L20

So your first list item, come up with an alternative syntax for decorators, is probably the best solution. Maybe use * since it's an addition of information and functionality, but not sure if mixing in an operator is a good idea.

@coffeescriptbot
Copy link
Collaborator Author

From @rattrayalex on July 25, 2016 0:7

Sorry, BC?

EDIT: ah, Backwards Compatible. Yes, I'm increasingly coming around to that.

@coffeescriptbot
Copy link
Collaborator Author

From @rattrayalex on July 25, 2016 1:3

Let's brainstorm other ideas...

class A
  @decorator
  func: ->
class A
  %decorator  # maybe?
  func: ->
class A
  ^decorator  # I don't think so
  func: ->
class A
  &decorator  # actually, not bad – I like that it implies you're adding something
  func: ->
class A
  *decorator  # perhaps too similar to generator syntax? But I don't think actually problematic.
  func: ->

Any other ideas?

@coffeescriptbot
Copy link
Collaborator Author

From @carlmathisen on July 25, 2016 6:26

Regarding *, it's good point that it might be confused as a generator.

Btw, CoffeeScript's official docs on generator functions:

CoffeeScript functions also support ES6 generator functions through the yield keyword. There's no function*(){} nonsense — a generator in CoffeeScript is simply a function that yields.

@coffeescriptbot
Copy link
Collaborator Author

From @JimPanic on July 25, 2016 7:29

@carlmathisen That's a good point re yield! It might be a good idea to keep this as-is for now and make sure the current syntax works with ES6 generators.

Edit: whoops, this is the thread about decorators. Sorry. :)

@coffeescriptbot
Copy link
Collaborator Author

From @JimPanic on July 25, 2016 7:46

I've just looked at decorators in Python, Elixir and ES6. They all use @decorate syntax. Lua doesn't seem to have a special decorator syntax as far as I understood it (http://lua-users.org/wiki/DecoratorsAndDocstrings).

In the long run, it might be a good idea to find a different shorthand for this. and deprecate its current incarnation in favour of a "standard" decorator syntax embraced in quite a few languages and tools. The current CS syntax is heavily inspired by Ruby in this regard, which (as far as I can tell) doesn't sport syntax-backed decorators at all and has a vastly different stance on classes, modules and methods than JS does.

@coffeescriptbot
Copy link
Collaborator Author

From @carlmathisen on July 25, 2016 18:30

Although I understand the wish for a "standard" decorator syntax, I disagree with deprecation of @ shorthand for this. Decorators are merely a nifty feature, and shouldn't take a driver's seat on how a language is designed. The @ for this isn't just a random symbol either, it has a semantic meaning to it and it is very easy to automatically understand what it means.

If we decide to reserve a word or symbol for decorators, it could just as well be decorate. This might break some apps having a variable named decorate, but it's better than breaking 100% of CS code with @.

Example:

class A
  decorate readonly, deprecated
  func: ->

Btw, here is a discussion on decorators over at CoffeeScript's own issue tracker: #76. Interesting read, even though the issue is quite old.

@coffeescriptbot
Copy link
Collaborator Author

From @JimPanic on July 25, 2016 19:32

Decorators are merely a nifty feature, and shouldn't take a driver's seat on how a language is designed. The @ for this isn't just a random symbol either, it has a semantic meaning to it and it is very easy to automatically understand what it means.

You're probably right. 👍

Thinking about your proposal and how decorators seem to be implemented (and without having read #76), are we not be able to use decorators right now, without the keyword?

readonly = (fn) -> ((...) -> ...)

decorated_fn = readonly (foo) -> foo

I used this type of function "decoration" before. So introducing a keyword like decorate for language supported decorators is certainly a better option than what I proposed.

@coffeescriptbot
Copy link
Collaborator Author

From @carlmathisen on July 25, 2016 20:6

Your suggestion is basically what Jeremy says in #76 (tldr: no need for language support, just create a wrapper function)

@coffeescriptbot
Copy link
Collaborator Author

From @JimPanic on July 25, 2016 20:50

On 25 Jul 2016, at 22:06, Carl Mathisen [email protected] wrote:

Your suggestion is basically what Jeremy says in #76 (tldr: no need for language support, just create a wrapper function)

Ah, cool. But there might be implications that this doesn't cover. I don't know how far language support is adding additional features in ES201*. I'm pretty sure any distinction for decorators is a good thing to be able to create proper tools to use this feature.

@coffeescriptbot
Copy link
Collaborator Author

From @dadleyy on July 28, 2016 15:50

I know coffeescript today is against adding them but regarding getters and setters -- would decorators be used for this? If not, is it worthwhile opening a new issue and having the discussion there or is it assumed that they won't make it into this new project? Personally I really enjoy using getters in browser-side UI code. I have found it helpful to keep track of arbitrary business logic rules (a user can do this, can't do this, etc...) by writing a flags getter on some of my component delegates/services/controllers:

class DietManager {
  get flags() {
    let {meals} = this;
    let total_calories = 0;
    for(let i = 0, meal_count = meals.length; i < meal_count; i++) {
      total_calories += meals[i].calories;
    }
    return {total_calories};
  }
}

At least, I think the above is a good example of a use for getters.

@coffeescriptbot
Copy link
Collaborator Author

From @rattrayalex on July 28, 2016 17:15

@dadleyy

is it worthwhile opening a new issue and having the discussion there

Yes, please do! getters/setters would likely be an appropriate issue title.

@coffeescriptbot
Copy link
Collaborator Author

From @carlsmith on July 28, 2016 18:0

-1 on this. CoffeeScript already has decorators. ES3 already has them too. Every language with first-class functions supports decorators. What ES6 is adding is just syntactic sugar for passing a function to a decorator. CoffeeScript already has syntactic sugar for doing this, and it literally couldn't be improved on. You just put any expression (typically a name) that evaluates to a decorator in front of the function expression you want to decorate. If you have a decorator named memo, you can just put memo in front of anything you want to decorate.

# regular square function
square = (x) -> x * x
# memo decorated square function
square = memo (x) -> x * x

We don't need ES6 sugar in CoffeeScript. That's the whole point of CoffeeScript.

Decorators are an awesome feature, but every language already has them. It's just that some languages make applying them awkward. Python without special syntax:

def square(x): return x * x
square = memo(square)

Python with special syntax:

@memo
def square(x): return x * x

There's absolutely no need for special syntax in CoffeeScript. If we had to use parens for invocations, it'd be different:

square = memo((x) -> x * x)

From experience, I can say confidently that you never need those parens in practice. You can always use the name of a decorator as if it's a prefix operator.

Things are less pretty if you use functions that return decorators, but it still works well enough. You just wrap the decorator expression in parens:

square = (memo 10) (x) -> x * x

That could be a little bit prettier with special syntax, for example:

@memo 10
square = (x) -> x * x

But, it's a pretty big change to add minor convenience to a rare construct, and the new syntax would be awful when using functions as args:

employees.forEach(
    @memo 10
    (each) -> console.log each
)

That's a mess, especially when we already have:

employees.forEach (memo 10) (each) -> console.log each

Decorators never nest any deeper than that. You always either use a decorator, or call a function that returns a decorator (so you can parameterise the decorator). They are the only two forms decoration takes.

@coffeescriptbot
Copy link
Collaborator Author

From @rattrayalex on July 28, 2016 21:30

@carlsmith have you used es6 decorators? The examples you provide are not valid (they can't be user on top level functions).

How can one decorate a class in coffeescript with equivalent elegance to es6?

@coffeescriptbot
Copy link
Collaborator Author

From @carlsmith on July 28, 2016 21:36

You put the names of the decorators in front of the class. Assume singleton is a class decorator:

Rabbit = singleton class extends Animal
  constructor: (name) ->
    @name = name

@coffeescriptbot
Copy link
Collaborator Author

From @carlsmith on July 28, 2016 21:38

There's no such thing as ES6 decorators. There's only ES6 decorator invocation syntax, so it's really just a question of what's the most elegant way to pass a function literal or class literal to another function. There's no such thing as a top-level function in CoffeeScript either. All functions are expressions.

@coffeescriptbot
Copy link
Collaborator Author

From @carlsmith on July 28, 2016 22:17

People need to keep in mind that a decorator is just a normal, higher-order function, like map or filter. Decorators are a functional programming pattern. Languages can add sugar to make using the pattern prettier, but it is only sugar. If a language supports first-class functions, then it supports decorators, pretty much by definition.

Most languages require that the arguments for any invocation are wrapped in parens, so passing a function or class literal to the decorator requires the literal to be wrapped in parens.

let square = deco(function(x) { return x * x });

Many languages allow (e.g. JavaScript) or require (e.g. Python) you to use function statements. A statement is not an expression, and can not be directly passed to a decorator as an argument. You must write the function first, then pass a reference to it to the decorator afterwards, assigning the result back to the original name.

function square(x) { return x * x }
square = deco(square);
def square(x): return x * x
square = deco(square)

In CoffeeScript, we can always prepend a decorator expression (typically a name) to a function or class literal.

square = deco (x) -> x * x

In other languages, they provide the decorator invocation syntax to work around limitations that CoffeeScript doesn't have (we don't have to wrap an argument in parens, and we don't have function statements).

@deco
def square(x): return x * x

That syntax also allows you to pipeline decorators.

@deco1
@deco2
def square(x): return x * x

In CoffeeScript, it's still a one-liner.

square = deco1 deco2 (x) -> x * x

Parameters

The @-based sugar allows you to use an invocation that returns a decorator, and decorate the new function with the result.

@decoWithArg(arg)
def square(x): return x * x

In CoffeeScript, you can easily do the same thing. You just need a pair of parens to keep things together properly.

square = (decoWithArg arg) (x) -> x * x

CoffeeScript's decorator invocation syntax works much better than @deco when applied to expressions. This is what CoffeeScript would look like if we accepted the new proposal:

employees.forEach(
    @memo 10
    (each) -> console.log each
)

This is what we have currently:

employees.forEach (memo 10) (each) -> console.log each

In its defence, the @-based syntax was never designed to be applied to expressions. It's designed for function statements, which CoffeeScript correctly removed as one of JavaScript's Bad Parts™.

@coffeescriptbot
Copy link
Collaborator Author

From @carlsmith on July 28, 2016 23:23

Just wanted to add a massive thank you to @rattrayalex for setting this discussion up, and for moderating all of these issues. There's a lot of technical conversation taking place across a growing pool of threads, and while it's stuff we all love getting into, it must be a challenge to stay on top of everything. It's a valuable conversation that we need to have about the future of CoffeeScript, and your enthusiasm and the hard work you've put into hosting this exchange are greatly appreciated mate.

@coffeescriptbot
Copy link
Collaborator Author

From @JimPanic on July 29, 2016 5:37

@carlsmith I can only second that! Thanks @rattrayalex!

@coffeescriptbot
Copy link
Collaborator Author

From @JimPanic on July 29, 2016 5:53

Those are very good points, @carlsmith.

To illustrate something like typespecs and inline (decorated) docs working already:

square =
  doc "squares a number and returns the result",
  typespec "(number): number",
  memoize (x) ->
    x * x

…will result in:

var square;

square = doc("squares a number and returns the result", typespec("(number): number", memoize(function(x) {
  return x * x;
})));

square = doc("", typespec("(number): number", function(x) {
  return x * x;
}));

Does look quite neat to be honest. 👍

I used higher-order functions as decorators before in Coffeescript, but sincerely thought the ES6 standard had some more features than just another invocation syntax.

@coffeescriptbot
Copy link
Collaborator Author

From @carlsmith on July 29, 2016 7:4

@JimPanic - Nice! I've never seen it done quite like that before. That's really cool. I'll be ripping that idea off at some point!

There's a similar thing where you define a collection of functions that can take normal args or a hash that meets some criteria, and always return a hash that meets the criteria. Then you can pass args to any of the functions, then pass the result (recursively) to other functions in the collection:

file = openInEditor writeToDisk newFile "foo.coffee" # process works from right to left

In practice, the names will normally be shorter:

file = edit set create "foo.coffee"

Your example is different and more clever though still. Mixing the patterns from functional programming with the stuff we normally do with objects and impure functions never gets tired :)

@coffeescriptbot
Copy link
Collaborator Author

From @JimPanic on July 29, 2016 11:3

@carlsmith Thanks! Be my guest :)

I used your outlined approach as well a few times; but I can't find it anymore online. I used it to describe properties for objects (readonly yaddayadda 'foo', object). That code might be lost in a backup though.

@coffeescriptbot
Copy link
Collaborator Author

From @carlmathisen on July 29, 2016 13:31

I must admit I haven't used decorators that much, so I'm loving the latest development in this thread. I'm definitely sold on:

file = openInEditor writeToDisk newFile "foo.coffee" # process works from right to left

One thing though, consider this example from http://github.com/wycats/javascript-decorators

class Person {
  @readonly
  name() { return `${this.first} ${this.last}` }
}

I've seen such class method decorators in the React community as of late.

This works just fine today with CoffeeScript's classes:

class Person
  name: readonly ->
    return "#{@first} #{last}"

But how could we translate this to ES6 class methods in CoffeeScript, though? The key/value way of setting up functions in prototype objects doesn't apply in a ES6 class since it has a special function syntax like above (name(){} instead of name: function(){}).

@coffeescriptbot
Copy link
Collaborator Author

From @dadleyy on July 29, 2016 14:13

@carlmathisen thats why (specifically the concept of a readonly decorator) I think we need getters/settings in cs6 so badly (moreso getters imo - I do think there is a good argument against side effects in setters but thats for another topic); your example could become:

class Person
  get directory_name: -> "#{@last}, #{@first}"

johnny = new Person "johnny", "appleseed"
console.log johnny.directory_name # logs "appleseed, johnny"

I don't think this covers all of the options available in object descriptors though, but a property defined on an object having only the get method is at least readonly in the sense that:

let user = {
  get directory_name() {
    return "appleseed, johnny";
  }
}

console.log(user.directory_name) // logs "appleseed, johnny"
user.directory_name = null
console.log(user.directory_name) // still logs "appleseed, johnny"

@coffeescriptbot
Copy link
Collaborator Author

From @rattrayalex on July 30, 2016 3:9

Some really great discussion here.

Regarding the class question, for something like this:

@connect(someStore)
@pureRender
export default class MyComponent extends React.Component {
  // ...
}

@carlsmith it sounds like you're suggesting this:

export default MyComponent = connect(someStore) pureRender class MyComponent extends React.Component
  # ...

and one could also do this:

class MyComponent extends React.Component
  # ...
export default connect(someStore) pureRender MyComponent

I suppose the pattern above really isn't so bad – the only downside is that it appears that all the decorators and the entire class decoration must appear on one line (testing with MyThing = wrap wrap2 class MyThing extends AnotherThing at coffeescript.org). That could become cumbersome; it sucks when you have no choice but to have a 100-character line of code.

Any ideas how that could be resolved?

Either way, I'm leaning towards concluding this thread with "no syntax sugar for decorators in CS6", with the hope that we add some helpful examples with the abovelisted patterns.

@coffeescriptbot
Copy link
Collaborator Author

From @rattrayalex on July 30, 2016 3:12

Oh, and thanks very much @carlsmith and @JimPanic for the kind words 🙂 it's very nice to hear.

If you or anyone else has feedback for how I've been conducting things so far, please reach out (anonymously, if you like, via mailbox service) - email is in my github profile.

Big thanks to all those who have been contributing so much of their own time and energy thus far!

@coffeescriptbot
Copy link
Collaborator Author

From @carlsmith on July 30, 2016 23:49

@rattrayalex - Exactly. There's a couple of ways you can break up the longer lines when you're using decorators, but yeah, the way you did it is correct. It can get a bit wordy in some cases, as you showed, it's not unrealistic to end up with export default on class with a couple of decorators with args etc., but it's not that common, and relatively simple to break down. Another option is to just use parens:

export default deco0 deco1(
  class extends Animal
)

If you just think of decorator syntax like...

@deco
function square(x) { return x * x }

...as just sugar for...

function square(x) { return x * x }
square = deco(square);

...it's kind of as though the @deco part is an invocation that wraps everything afterwards in parens, but the name is moved to the front as well:

square = deco(
function (x) { return x * x }
)

From there, it's fairly obvious which different ways you could possibly express the same thing.

@coffeescriptbot
Copy link
Collaborator Author

From @carlsmith on July 30, 2016 23:51

But how could we translate this to ES6 class methods in CoffeeScript, though? The key/value way of setting up functions in prototype objects doesn't apply in a ES6 class since it has a special function syntax like above (name(){} instead of name: function(){}). -- @carlmathisen

That's a really good point. We need to consider that when we look at getters/setters. Good thinking. I've created a new issue for getters and setters (#17), and mentioned it there.

@coffeescriptbot
Copy link
Collaborator Author

From @rattrayalex on September 9, 2016 15:39

Closing as this has been marked as "no action" in https://github.com/coffeescript6/discuss/blob/master/Features.md

I'm personally not opposed to re-opening in the future.

@coffeescriptbot
Copy link
Collaborator Author

From @light24bulbs on Dec 10, 2017

Actually makes more sense than the @ as well. I agree with the + for decorator syntax.

And just a note on unfinalized JS features. That's simply the state of JavaScript these days. If you aren't incorporating these features before they get 'finalized', you're years out of date and everyone stops using you. Node just went LTS with version 8 last month, and it has decorators. They're had it for over a year in non LTS.

There's no great risk to supporting this feature, it's going to be simple to implement, and it's a deal breaker for coffeescript if it doesn't have it.

@coffeescriptbot
Copy link
Collaborator Author

From @sonhanguyen on Dec 10, 2017

one thing I'd like to add is that if we do, I really hope it'd be like python decorators which you can apply to any function, not like es6/java decorators which are restricted to methods.

@coffeescriptbot
Copy link
Collaborator Author

From @light24bulbs on Dec 10, 2017

Functions are hoisted in JavaScript, that's why they didn't do it in es6.

I think coffeescript should have a 1 to 1 reproduction of decorators. Just pass the syntax right through.

@coffeescriptbot
Copy link
Collaborator Author

From @sonhanguyen on Dec 10, 2017

@light24bulbs decorators do work on class level and a class is basically a function with extra semantic.

@coffeescriptbot
Copy link
Collaborator Author

From @GeoffreyBooth on Dec 10, 2017

Hey folks,

A few points:

  • Our policy on new ECMAScript features is here. In short, we generally don’t add a feature to CoffeeScript until its syntax is finalized in ECMAScript; or we add it but output it using non-experimental syntax. We added support for object destructuring/rest syntax in this fashion, where we output the same polyfill as Babel while we wait for the feature to reach Stage 4.
  • If anyone wants to implement decorators and output the result as Stage 4 ECMAScript, please go right ahead. Such a PR would be welcomed.
  • @carlmathisen is right, you can use decorators now with backticks and Babel.
  • Node supports decorators?

@coffeescriptbot
Copy link
Collaborator Author

From @MadcapJake on Jan 5, 2018

I've been thinking about decorators in CoffeeScript as I wanted a way for a base class to assemble a map of events to methods (for a child class) in a DRY fashion

When you place anything in front of a method body in CoffeeScript, the compiler will convert it to a property on the class prototype. So I had to use @:: due to the assignment of the method not actually happening inside of the class body.

class EventManager
	constructor: -> @events ?= {}
	via: (evt, fn) -> (@events ?= {})[evt] = fn.bind(this)
	trigger: (event) -> @events[event]?()

class Task extends EventManager
	completed: no
	toggleCompleted: @::via 'done', ->
		@completed = yes
		console.log "Toggling completed!"

t = new Task()
t.trigger 'done'

Just some food for thought that method decorators from a parent class will require the use of :: to reference.

@cha0s
Copy link
Contributor

cha0s commented May 6, 2018

This may be a dumb question, but... has anyone proposed

@@ClassDecorator arg
class DecorateMe

  @@PropertyDecorator arg
  foo: ->

    ...

As an alternative syntax? Decorators would be lovely to have in coffee for implementing React HOCs.

@cha0s
Copy link
Contributor

cha0s commented May 6, 2018

Also it should be noted that backticks will not work for class decorators.

@carlsmith
Copy link
Contributor

What's the advantage, besides being able to break it over two lines?

(ClassDecorator arg) class DecorateMe
foo: (PropertyDecorator arg) ->

@cha0s
Copy link
Contributor

cha0s commented May 7, 2018

Chaining, as well as complex invocation of the decorator itself.

Your examples are simple enough with one decorator and one arg. Compare some code:

Without decorators:

import {formValueSelector} from 'redux-form'
import {connect} from 'react-redux'

class Component

  ...

Component = connect((state, props) ->

  selector = formValueSelector props.form

  return {
    someState
    commands: mapCommandStateToProps state
    thing: selector state, 'thing'
    ...
  }

) Component

import deco from '...'
Component = deco(
  configure: styles: '...'
) Component

import {reduxForm} from 'redux-form'
Component = reduxForm() Component

export default Component

Now, the same sort of chained higher-order component composition, expressed with (as I've defined) decorators:

import {formValueSelector, reduxForm} from 'redux-form'
import {connect} from 'react-redux'
import deco from '...'

@@connect (state, props) ->

  selector = formValueSelector props.form

  return {
    someState
    commands: mapCommandStateToProps state
    thing: selector state, 'thing'
    ...
  }

@@deco configure: styles: '...'

@@reduxForm()

export default class Component

  ...

This is much more in the spirit of coffee's elegance in expressing functional composition IMO

@carlsmith
Copy link
Contributor

carlsmith commented May 7, 2018

The example is really difficult to understand. The code is cryptic before and gone altogether afterwards. I don't use those libraries, but if I'm being honest, they have badly designed APIs or the code is unrealistic.

At the top, you seem to be decorating the lambda with connect, then decorating Component with the result:

Component = connect((state, props) ->
  return { ... }
) Component

Is this "complex invocation", or is it just "overly compounded"? I honestly don't think I've ever needed to wrap a decorated function in parens, and if the decorator is parameterless, I just use it as a prefix operator, something more like this:

deco = connect (state, props) ->
  return { ... }
Component = deco Component

I'm sorry. I really don't understand the example code, and didn't want to be rude and just dismiss your point. It's obviously an intelligent point. I just don't get it :(

The next example could be written like this, but again this is not like anything I normally see:

Component = (deco configure: styles: '...') Component

Functions are typically decorated when they are defined, and that's what decorator syntax is normally for. Decorators are typically designed so you don't need parameters, or you only need something simple if you do, a regex or an int. It's not common to see nested hashes in a decorator expression.

Your example of how things would be with the new syntax seems to just magically make stuff disappear. I don't know how to do that with the current syntax.

If you have an example that's a bit clearer, I'm happy to have another honest crack at seeing an advantage. Otherwise, maybe someone else gets what I'm missing??

@carlsmith
Copy link
Contributor

The syntax also potentially problematic. I understand it looks like the decorator syntax from other languages, which is a pro, but it also looks like CoffeeScript's this shorthand, even though it's an unrelated feature. I would expect it to reference window or arguments or something similar to this.

It would be interesting to see an example with @@ decorator syntax interspersed with @name expressions, just to better visualise how well they contrast.

@cha0s
Copy link
Contributor

cha0s commented May 7, 2018

As far as syntax goes, that's a matter of opinion. I write a lot of coffee and I don't see @@ becoming too noisy because it lives outside of class and property definitions, whereas @ lives inside them.

I'm not sure what you aren't getting about the example code. In all cases the signature is, essentially:

(args...) -> (Class) -> DecoratedClass

Decorators are simply syntactic sugar. You invoke the decorator, it returns a function, then you pass the class (constructor) to that function which returns a new class (constructor). Then the next decorator gets that new class, does something and then returns yet a new class, etc. It's functional composition.

I believe the concept you aren't understanding is functional composition. In React this is expressed as Higher-Order Components.

The same thing applies to Mixins. Mixins can be accomplished very easily with decorators.

Think about it like a 'stream', with every 'transform' wrapping and returning a new class, and the final result is the class result of all those wrappings. Hopefully that helps understand!

@carlsmith
Copy link
Contributor

I get it: Each invocation effectively augments the function returned by the previous invocation by defining and returning a new function that references the last by closure, only using classes as functions.

I don't get how an operator that lets you express a decorator on the previous line (instead of immediately before, on the same line) makes most of the code that made up the bottom half of your script no longer necessary.

@cha0s
Copy link
Contributor

cha0s commented May 7, 2018

ES7 decorators are only syntax sugar. "Under the hood", they are doing exactly what I'm describing here. It's even the same with properties, but I kept the example to classes for simplicity and clarity.

The whole point is that if you have 3 decorators, the original class/prop is passed to the result of the decorator, then that result is passed to the result of the next decorator, etc. It's a way to implement functional composition (a very powerful concept) in a very elegant way.

It eliminated so much code which was essentially the 'boilerplate' required to express functional composition using the existing decorator-less syntax.

@carlsmith
Copy link
Contributor

In my world, this...

@deco
def func(): pass

...is basically shorthand for this...

def func(): pass
func = deco(func)

...and this...

@deco(None)
def func(): pass

...is basically shorthand for this...

def func(): pass
func = deco(None)(func)

You seem to be explaining one of the ways decorators can be used, but the point is why does CoffeeScript need more syntax?

In Python, callables are generally defined in statements, where def and class are keywords that must be the first token on a strictly indented newline. Being able to stack up (strictly indented) decorator statements before the callable statement overcomes a limitation of Python's syntax that CoffeeScript doesn't have.

Even in JavaScript, where you can define a function as an argument to a decorator recursively, and assign a name to the result, you still end up having to wrap everything in parens, though it can be done:

let result = (
    deco(null)(
    memo(
    sumo("slap")(
        function (arg) {
            alert(arg);
}))));

In CoffeeScript, if you can fit the decorators on one line, you can drop the messy outer parens:

result = (deco null) memo (sumo "slap") (arg) ->
    alert arg

Or just have one set if the params push it over eighty characters:

result = (deco null) memo (sumo "slap") (
    (arg) -> alert arg
)

If a library has lengthy decorator names, just alias them to something shorter, or you can refactor, wrapping the decorator chain into a single function.

CoffeeScript nailed its function definition and invocation syntax, so we can do things exactly like decorating functions and classes without special syntax. You just express the thing you want, however you want to express it. I'm not against more functional syntax if it improved things still further, but nobody so far (and it's been discussed at length already (though no harm in continuing)) has provided any example (that I understood) that couldn't be refactored into idiomatic CoffeeScript.

@cha0s
Copy link
Contributor

cha0s commented May 7, 2018

Sure, it can always be 'refactored'. That's not really the point. This:

result = (deco null) memo (sumo "slap") class SomeClass

is honestly nasty to look at. I guess it's a matter of opinion, but yeah. Ech.

@@deco null
@@memo()
@@sumo 'slap'
class SomeClass

...if there's a world where the first is considered 'idiomatic' and the second isn't, then it's an unjust world :3

@cha0s
Copy link
Contributor

cha0s commented May 7, 2018

Also bringing python into it is totally irrelevant afaict. Decorators are ES7. This is basically just the next step of JS. We've already done async. Hey... you could've just refactored async/await/etc as well... ;)

@GeoffreyBooth
Copy link
Collaborator

Decorators are currently Stage 2. Per our policy, we will add this syntax when it reaches Stage 4.

@carlsmith
Copy link
Contributor

carlsmith commented May 7, 2018

@cha0s - But the point was to show that the current syntax doesn't force you to do anything terrible, even when you have three decorators and a couple that take args. It doesn't make sense to add new sugar because some code would be nicer.

In more commonplace situations, the advantage of the proposed syntax is lost entirely. This...

f = sumo (arg) ->
    alert arg

... just becomes this...

@sumo
f = (arg) ->
    alert arg

If the current syntax is fine for decorating most things, and the proposed syntax only lets users avoid refactoring fairly uncommon expressions, is it really worth adding sugar for?

You may be right. I'm not convinced, but, it's not me you have to convince. Best wishes anyway.

  • P.S. I mentioned Python as an example of a language where there is a clear advantage to the syntax.
  • P.S.S. The implicit invocations you called "nasty" are still idiomatic :)

@carlsmith
Copy link
Contributor

@GeoffreyBooth, just to be clear, are you saying this syntax will not be added to CoffeeScript until it reaches Stage 4, or that it will be added once it reaches Stage 4?

@GeoffreyBooth
Copy link
Collaborator

Our policy is that we generally don't support new syntax until it reaches Stage 4. That doesn't mean we necessarily will support it at Stage 4, but the default assumption is that we will. The only reasons we wouldn't would be if it somehow conflicted with something else in CoffeeScript, like how let/const conflicted with automatic variable declaration; or if we agree that the new feature was a terrible idea, though I hope by this point with the current process in place there shouldn't be too many more "bad parts" entering JavaScript.

@vendethiel
Copy link
Collaborator

vendethiel commented May 7, 2018

...if there's a world where the first is considered 'idiomatic' and the second isn't, then it's an unjust world :3

if that's all you need, then something like:

decorate = (xs, c) ->
  xs.reduceRight ((o, d) -> d o), c
decorate [
  deco null
  memo
  sumo "slap"
] class SomeClass

should foot the bill.

@cha0s
Copy link
Contributor

cha0s commented May 8, 2018

That reducer is actually what I currently do use for composition/mixins. Decorators are even sexier.

I'm not going to die without them but if coffee is meant to be elegant and expressive then decorators are a great fit.

@Inve1951
Copy link
Contributor

Inve1951 commented May 8, 2018

The syntax also potentially problematic. I understand it looks like the decorator syntax from other languages, which is a pro, but it also looks like CoffeeScript's this shorthand, even though it's an unrelated feature. I would expect it to reference window or arguments or something similar to this.

I'd expect @@whatever to translate to this.constuctor.whatever. Like a shorthand for accessing static class members.

Repository owner deleted a comment from factoidforrest May 8, 2018
@carlsmith
Copy link
Contributor

carlsmith commented May 8, 2018

Thanks @GeoffreyBooth. That makes sense.

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

6 participants