-
Notifications
You must be signed in to change notification settings - Fork 4
CS2 Discussion: Features: Getters and Setters #17
Comments
Not really a solution, but more of an FYI if anyone were thinking in the same direction I were initially: If we were to just add functions to a prototype object of an ES6 class when transpiling, it won't work well since This works, but we don't have decorators out of the box like CoffeeScript has today: class Person {
speak() {
return "hey"
}
}
class John extends Person {
speak() {
return super.speak() + " dude!"
}
} The following code would allow us to wrap the prototype function with decorators, but John.prototype.speak = () => {
return super.speak() + " dude!"
}
}
In other words, we probably need get/set. |
the example given, class Person
name: readonly ->
return "#{@first} #{last}" is flawed in the sense that the context of |
Extremely good point, @dadleyy. Fat arrow binding doesn't work either, since the scope becomes the prototype declaration of |
I hope this is on topic. Explicit support for getters and setters in the new CS would be good. However, if there were something like this...
the first thing to come to attention is the naming of the property 'id'. In the example above, there is a setter named id, and a property called id. This is obviously not going to work at runtime in ES, as unfortunately, setter and getter names cannot have the same name as any other property. An easy solution would be:
but in an ideal world, it would be great if that was taken care of automatically in transpilation e.g. with a getter/setter | propName collision being rectified in the output code, perhaps using the underscore convention? E.g. in the first example, the output code would have a setter named id, but would store the value on a property named _id. Another thing which would be nice (but difficult) would be implicit getters. I know. I said I hated implicit returns, and now I'm asking for discussion about implicit setters and getters! But hear me out... If a single setter was implemented, wouldn't it make sense in the majority of cases to also have an implicit getter? Such that even though we would only see the typed code from the first example, the output code would look like this:
Data leakage is the risk of course, as is the case with implicit returns, but just throwing it out there. Anyhow, back on point. Support for getter/setter syntax in CS6 would be very good. Especially in the context of decorators...
In the example above, decorators could be used to ensure that only values of type String are provided, and that the setter can only be used once. Subsequent attempts would throw an error. But to really stretch the boat out there, what if property definition was changed slightly? A property is declared as it is now, but, at discretion of the developer, setters or getters could be added only if needed. I suppose I am couching on using decorators as type enforcements on setters here...
Thoughts? |
great stuff @objectkit.
This idea and the example given kinda remind me of objective-c
very interesting! I'm not sure if we need to address this since you're going to have the same problem in es6 - not being able to have a getter/setter with the same name as another property (specifically the one that it is intended to work with). I'd say that it is up to the developer to design around this; what are they trying to do? Is there value of having a getter with the same name as a property or is that just a side-effect of wanting to have logic in a setter but needing to support access to the underlying data? I think there is tremendous appeal to have logic in the class User {
set name(val) {
let {errors} = this;
this.$name = val;
function success() {
errors["name"] = false;
}
function fail() {
errors["name"] = true;
}
this.save().then(success).catch(fail);
}
get name() { return this.$name; }
} On the other hand, I feel like if some kind of mutation needs to happen to the value of a property it's better to come up with a new property and add the getter there. For example, if the developer is trying to: class TokenID {
get id() {
return `${this.token}_${this.id}`;
}
} they really should probably be doing: class TokenID {
get token_id() {
return `${this.token}_${this.id}`;
}
} |
We're also going to want to figure out how to handle defining getters and setters outside the initial definition of classes like the can do with functions now. For example we're currently allowed to: class User
constructor: ->
User::anotherFn = -> what would this look like for getters/setters? If we decide to go with the class User
get full_name: -> I think we might be hurting ourselves in the sense that I'm sure we'd want to avoid having to support something like: User::get full_name = -> Generally speaking I'm more in favor of a language's use of keywords than operators ( class User
full_name: ~> # getter
full_name: (new_val) ~> # setter
User::full_name = ~> # getter
User::full_name = (new_val) ~> # setter |
Just remember that by adding functions on the prototype object and not in the class definition, super isn't available anymore. Thus removing an important feature of ES6 classes. |
@carlmathisen good call - I didn't know that. I wonder why super is restricted that way; isn't it possible to accomplish accessing a super class's prototype somehow anyways? Is there an ask that the compiler always "lifts" prototype definitions into the main definition of classes then? We'd still write: class FriendListManager
constructor: (@user) ->
FriendListManager::addFriend = (other) -> but the compiler would raise class FriendListManager {
/* ... */
addFriend(other) {
}
}; I think this raises complications regarding the compiler's ability to check the presence of the initial definition and what do to if it is missing. Anyways I think we'd want to do some sort of "lifting" for getters/setters right? side note - the mdn website has an issue with their example on the class Square extends Polygon {
constructor(length) {
}
get area() {
return this.height * this.width;
}
set area(value) {
this.area = value; // bad: infinite call stack
}
} |
I think I think the Are there likely technical barriers to using |
What’s our consensus here? Are getters and setters a feature we want CoffeeScript to support? If so, can someone write an example showing the proposed syntax? Ideally the syntax would be backwards-compatible or as close to backwards-compatible as possible. |
I think we should take the same stance on this issue that we are on the module syntax and classes in the next compiler - get the syntax as close to es6 as possible and then it's the job of the next transpiler (babel) to worry about older runtimes. Therefore, my suggestion is that we support: # inside class definition
class DietManager
constructor: -> # ...
get total_calories: ->
total = 0
total += calories for {calories} in @meals
total
set field: (new_value) ->
# inside object literal
current_user =
get full_name: -> "#{@last_name}, #{@first_name}" and then compile that down to it's es6 form: class DietManager {
constructor() { }
get total_calories() { /* ... */ }
set field(new_value) { /* ... */ }
};
var current_user = {
get full_name() { /* ... */ }
}; I will say that I think this is a must-have. There are good reasons to avoid the over-use of these features but I say leave it up to the end user to know the right time and place for them - same is true for classes/generators/etc... |
Personally I think it makes sense to build |
Most definitely. Currently, if I want getters on a class I usually do the following sugar inside my modules (with an example not split up into modules for brevity): attr = (target, name, descriptor) -> Object.defineProperty target, name, descriptor
attr.access = (target, property, get) -> attr target, property, {get}
class Revisions
constructor: (@original = {}) ->
@revision_history = []
attr.access this, "latest", @apply
Revisions::apply = ->
{revision_history, original} = this
result = Object.assign {}, original
# ... use items in store to apply to result ...
result
class UserManager extends Revisions
constructor: (user) ->
super user
# ... methods to update parts of the user, e.g:
UserManager::addFriend = (other_user) ->
{revision_history} = this
revision_history.push {type: "friendship", payload: other_user}
UserManager::commit = (callback) ->
{latest} = this
# save to API...
# ... elsewhere:
manager = new UserManager {name: "danny"}
{latest: user} = manager I just think it would be nice to see this kind of thing be recognized as something that - considering writing it in es6 would be more concise - should make it into cs w/o the need for the user to provide that sugar. I think getters are a nice gateway into very cool patters seen in immutablejs and redux. |
I'm not sure about adding the syntaxes class A
get name: -> # get({ name: function () {} })
set name: -> # set({ name: function () {} })
get name: -> # get({ name: function () {} })
obj = { get name: -> } # Probably a getter
obj =
get name: -> # Getter, maybe?
obj = get name: -> # obj = get({ name: function () {} }) ?
key: get name: -> # { key: get({ name: function () {} }) }
key: { get name: -> } # Probably a getter We could say it's only valid in explicit objects, except that would mean they're not valid in classes. On top of that, I'm not sure that enabling getters and setters - which commit the sins of making expensive operations look cheap, and potentially side-effecting operations look pure - is a good idea. Consider immutablejs, which explicitly uses I think the |
I don't really think this would jibe super well with coffeescript, but in case it's the syntax that's a problem, here's how I'm solving it in LightScript:
|
The Uniform access principle has a long tradition in language design. Getters and setters also allow us to avoid breaking changes when we later decide that a stored property must be computed or delegated. |
I don’t think getters and setters rise to the level of something so abhorred that they should be banned from the language. They’re right there in the MDN example of a class. Ember.js uses them extensively. I understand people can use |
Indeed its an entrenched language feature at this point. I would love a
coffee-esque syntax but I would rather have the option to use them somehow
without going the long way round.
…On Feb 1, 2017 2:51 PM, "Geoffrey Booth" ***@***.***> wrote:
I don’t think getters and setters rise to the level of something so
abhorred that they should be banned from the language. They’re right there
in the MDN example of a class
<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes>.
Ember.js uses them extensively.
I understand people can use defineProperty to create getters and setters
today, but that seems like forcing people to use a workaround.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#17 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABy6x1HeOwsM0LyobENayx60VD4vRh5fks5rYQx4gaJpZM4JY63G>
.
|
As requested by @GeoffreyBooth, moving a comment over to this ticket. As a side note — it would be nice if discussion around language features could happen over on the Now to the meat: If there is some fundamental compatibility reason why we must have getters and setters in CoffeeScript, then that's a little sad, but it's alright. But up until that point, getters and setters have always been an intentional omission from CoffeeScript (like named functions, manual variable declaration, loose equality As you write ES3, you know what's a function call and what's a property access. When you use getters and setters, it's suddenly opaque to you which is which, with the possibilities of serious performance considerations and also side effects in places where you might not expect them. This uncertainty brings with it a little bit of syntax sugar — to make a method call look like a property access. Why not just let a method call be a method call? |
It is nice to be able to use the "computed value"-ness of getters in browser-side rendering where templates come into play: <p>{{diet_manager.total_calories}}</p> although I suppose you could always just calculate the value in code before rendering or have some kind of |
So when I last commented I was thinking about Ember.js’ computed properties, which I assumed use But I think my general point is still valid. There’s a lot of activity nowadays in frameworks that observe plain JavaScript objects (React, Vue, etc.) and re-render as a result of changes. I think it’s only a matter of time before some framework will require the object being observed to use getters and setters; or if one wants to write a component or plugin for one of these libraries, it will need to use getters and setters. (Imagine writing a plugin for a new type of Ember computed property.) Of course you can always go the “long way around” and use One last “real” case: the JavaScript proxy pattern: (function() {
// log all calls to setArray
var proxied = jQuery.fn.setArray;
jQuery.fn.setArray = function() {
console.log( this, arguments );
return proxied.apply( this, arguments );
};
})(); Sometimes I want to modify an imported library function, without forking the underlying library. I might use something like the above. If the library in question used a getter or setter instead of a function, I would need to use a getter or setter to override it. Again, I could use More fundamentally, if it’s not discouraged by MDN—and indeed, it’s right there in their basic |
So the PR for documentation is in progress over at jashkenas/coffeescript#4469 (thanks @mrmowgli). I think the other thing that we have consensus that we should do is have the compiler throw an error when it appears that a class A
get b: ->
c =
get d: ->
e = ->
get f: -> And get({
b: function() {}
}); If anyone needs to call a function named Is everyone okay with this proposal? @connec, what do you think, and how difficult do you think it would be to implement? I’m thinking that perhaps it might not require a change in grammar, since we’re just throwing an error? |
Whilst it is probably a pragmatic way of ensuring we don't get bug reports about @GeoffreyBooth I reckon those could be caught reasonably easily by adding a check to
The case that would be harder to catch with a helpful error would be |
My other motivation for throwing an error is to avoid breaking changes in the future if it turns out we need to support this syntax for some reason. If we make the |
Apologies in advance for just barging in here and commenting on a closed issue, but I've been a big fan of CoffeeScript for quite some time, and I was really stoked when I found out recently that not only did ES6 not kill it, but version 2 is inching closer to a release. 😃 However, I'm a bit curious about your stance on getters and setters. Now, I'm really not in a position to discuss the technical merits much, since I haven't coded much in CoffeeScript or ES6 recently. But I would like to point out that they're in ES6, and CoffeeScript aims to be a superset, so that kinda makes me feel that they should be supported. Is it really that complicated? Or is the argument just that Crockford doesn't like them? Anyhow, whatever the final decision will be, perhaps it would be nice to mention them on the CS2 website. Also, I'd be happy to volunteer if there's anything I could help out with. After having taken a much-needed break from coding, I feel ready to jump back in the game. And what better way to do that than giving back to the community that's given me so much cool stuff to play with in the past? Cheers! 🍻 |
A mention is in the docs on the Something to keep in mind is that getters and setters are supported in CoffeeScript, even version 1.x. It’s just the shorthand syntax that isn’t supported. The note in the docs explains how to use the verbose syntax. We could’ve found some way to support the shorthand syntax or something similar to it, but the short answer is that the grammar is ambiguous (is it Also, CoffeeScript is intentionally not a superset of JavaScript: https://rawgit.com/jashkenas/coffeescript/2/docs/v2/index.html#unsupported. It never has been, ever since the beginning and the lack of Help is most welcome! https://github.com/jashkenas/coffeescript/issues?q=is%3Aopen+is%3Aissue+label%3Apriority |
I think there is a danger of CS evangelising over this when the battle has effectively already been lost in ES6. I have to say that I agree with @wcjohnson above when he said:
Please reconsider. |
Getters and Setters have been in ECMAScript since ES5, when CoffeeScript was still in its infancy. I sincerely doubt the decision was arbitrary, and I know this has been heavily discussed in issues for years.
Getters and setters aren’t even being adopted by full ES6 shops like AirBnB (https://github.com/airbnb/javascript/blob/master/README.md#accessors–no-getters-setters) who are on board with everything else.
While it seems like this is a reasonable request, getters and setters weren’t intended to be used extensively in the ECMAScript.
|
FYI, there’s a really elegant way to use getters / setters in CoffeeScript version 1 and 2 (thanks @carlmathisen) by creating a new method on the Object::property = (name, accessors) ->
Object.defineProperty @::, name, accessors Example use class Person
@property "name",
set: (value) ->
names = value.split " "
@firstName = names[0]
@lastName = names[names.length - 1]
get: ->
"#{@firstName} #{@lastName}"
joel = new Person
joel.name = "Joel Drapper"
console.log joel.firstName
# "Joel"
console.log joel.lastName
# "Drapper"
console.log joel.name
# "Joel Drapper" If you’d rather not risk extending the class BaseObject
@property: (name, accessors) ->
Object.defineProperty @::, name, accessors
class Person extends BaseObject
... |
@joeldrapper This also works in CS1 btw 👍 |
@joeldrapper That's great. If the devs aren't going to add getter/setters to the language, at least could we have this trick in the unsupported section of the manual? |
The docs are in the repo. See the markdown files in |
@GeoffreyBooth thanks for clearing that up. When I searched the page I didn't find anything, but I looked again yesterday and now the lack of the shorthand syntax (including the motivation for it) is present. @joeldrapper I've seen this trick mentioned elsewhere before and I think it's fairly neat, save for the need to extend +1 for @CliffS idea to at least mention the workaround in the main documentation. |
Did anyone mentioned that you can delete @GeoffreyBooth Are you seriously offering to use forks in issues? So that project readme should contain "if you gonna work with this code you need to install fork, cause vanilla coffeescript is incompatible with modern js" |
Call me crazy, but would it be such a bad idea to define getters and setters like Ruby? Methods without arguments are getters and methods ending in
You could force a normal method with no arguments like this |
Issue migrated to jashkenas/coffeescript#4915 |
ES6 getters and setters don’t have there own discussion yet, and they came up in the discussion of decorators, specifically the need the ensure that any syntax that CoffeeScript adopts for defining getters and setters must allow decorators to be applied to the function literals.
This is a general discussion, but to carry over what was brought up already: From the example @carlmathisen used when he raised the issue, we can currently do this (note that
readonly
is a decorator):That works well, but the getter and setter syntax needs to allow decorators to be applied in a similar way (or an alternative must be provided).
EDIT by @GeoffreyBooth Consensus from the below thread:
The
get
andset
shorthand syntax is too infrequently used, and a discouraged practice, for CoffeeScript to support directly. Getters and setters can be created via theObject.defineProperty
method, so they technically already are supported in CoffeeScript; supporting the shorthand syntax as well just makes them more convenient to use, but Douglas Crockford argues that we should rarely if ever be using them.So the task for CS2 is to have the compiler throw an error when it appears that a
get
orset
shorthand syntax keyword is being used. Things like the following:And
set
for all of the same. The above all compiles, which isn’t a good thing. These should throw compiler errors, without prohibiting people from using variables or functions namedget
orset
elsewhere in the code. Basically when a call to a function namedget
orset
is given an argument that is an object with one property, and that property’s value is a function, we throw an error. In other words, this:If anyone needs to call a function named
get
and pass an object, well, they just need to define the object ahead of time and assign it to a variable, and then callget
likeget(obj)
. That’s a reasonable workaround for what should be a tiny edge case. What we shouldn’t do, even though we could, is makeget
andset
keywords. They’re not keywords in JavaScript, and they strike me as quite plausible names for functions, so I don’t want to cause a breaking change for people who have such variable names when we can solve this problem with more precision.The text was updated successfully, but these errors were encountered: