-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Comments
From @carlmathisen on July 24, 2016 21:27 It is unfortunate that ES6 generator uses You're probably right, it's hard to parse and distinguish between the two if decorators were to keep the same symbol.
Granted, when transpiled today it doesn't make sense - So your first list item, come up with an alternative syntax for decorators, is probably the best solution. Maybe use |
From @rattrayalex on July 25, 2016 0:7 Sorry, BC? EDIT: ah, Backwards Compatible. Yes, I'm increasingly coming around to that. |
From @rattrayalex on July 25, 2016 1:3 Let's brainstorm other ideas...
Any other ideas? |
From @carlmathisen on July 25, 2016 6:26 Regarding Btw, CoffeeScript's official docs on generator functions:
|
From @JimPanic on July 25, 2016 7:29 @carlmathisen That's a good point re Edit: whoops, this is the thread about decorators. Sorry. :) |
From @JimPanic on July 25, 2016 7:46 I've just looked at decorators in Python, Elixir and ES6. They all use In the long run, it might be a good idea to find a different shorthand for |
From @carlmathisen on July 25, 2016 18:30 Although I understand the wish for a "standard" decorator syntax, I disagree with deprecation of If we decide to reserve a word or symbol for decorators, it could just as well be Example:
Btw, here is a discussion on decorators over at CoffeeScript's own issue tracker: #76. Interesting read, even though the issue is quite old. |
From @JimPanic on July 25, 2016 19:32
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?
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. |
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) |
From @JimPanic on July 25, 2016 20:50
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. |
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 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. |
From @rattrayalex on July 28, 2016 17:15
Yes, please do! |
From @carlsmith on July 28, 2016 18:0
# 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. |
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? |
From @carlsmith on July 28, 2016 21:36 You put the names of the decorators in front of the class. Assume Rabbit = singleton class extends Animal
constructor: (name) ->
@name = name |
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. |
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 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 ParametersThe @-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 employees.forEach(
@memo 10
(each) -> console.log each
) This is what we have currently:
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™. |
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. |
From @JimPanic on July 29, 2016 5:37 @carlsmith I can only second that! Thanks @rattrayalex! |
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. |
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 :) |
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 ( |
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 ( |
From @dadleyy on July 29, 2016 14:13 @carlmathisen thats why (specifically the concept of a 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 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" |
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 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. |
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! |
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 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 square = deco(
function (x) { return x * x }
) From there, it's fairly obvious which different ways you could possibly express the same thing. |
From @carlsmith on July 30, 2016 23:51
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. |
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. |
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. |
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. |
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. |
From @sonhanguyen on Dec 10, 2017 @light24bulbs decorators do work on class level and a class is basically a function with extra semantic. |
From @GeoffreyBooth on Dec 10, 2017 Hey folks, A few points:
|
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 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 |
This may be a dumb question, but... has anyone proposed
As an alternative syntax? Decorators would be lovely to have in coffee for implementing React HOCs. |
Also it should be noted that backticks will not work for class decorators. |
What's the advantage, besides being able to break it over two lines?
|
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 |
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 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?? |
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 It would be interesting to see an example with |
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:
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! |
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. |
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. |
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 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. |
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 |
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 |
Decorators are currently Stage 2. Per our policy, we will add this syntax when it reaches Stage 4. |
@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.
|
@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? |
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. |
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. |
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. |
I'd expect |
Thanks @GeoffreyBooth. That makes sense. |
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'sthis.
shorthand.We could:
this.
shorthandthis.
shorthandThoughts?
Copied from original issue: coffeescript6/discuss#9
The text was updated successfully, but these errors were encountered: