-
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
Constructors, Prototypes, Inheritance. #17
Comments
Nice bug. You're running into a JavaScript (mis)feature -- if you return an object from a constructor, that object will be the evaluated result of It would be ideal if CoffeeScript could detect constructor functions, and not add the return statement to their final lines, but unfortunately because any JavaScript function can be used as a constructor, I'm not sure that this is possible. It might be better just to document that |
Well to be honest, the sort way returning works now is sort of magic and weird to me. I'm used to do an explicit return and if none was given it returns none or nil. So it seems to me that behavior would be a better fit in combination with javascript wouldn't it? |
I'm not willing to give up implicit returns, just to fix this special case for constructors. They're critical for the whole "everything should be an expression" idea that underlies CoffeeScript. I guess CoffeeScript could suppress the auto-return for functions that have names starting with a capital letter, CamelCasedClassStyle, assuming that they must be a constructor function, but that would also be quite strange if you aren't expecting it. |
... a case for having a fully-fledged class model, e.g. class Test1 { initializer: => this.test = "test" testFunction: => print("It works!") } |
@weepy: One of the reasons I dislike this idea is it is alot of reinvention. JS's prototype model means you can easilly roll mixins and other complicated things into the object system, whereas that sort of Class model makes it needlessly restrictive (i.e like Java) As for the constructor thing, I don't like enforced naming styles. Can't we just add a "constructor" annotation?
|
I didn't mean that it should do anything like Base2, the code i suggested would compile like so : var Test1 = function Test1() { this.initialize() } Test1.prototype = function() { this.test = "test" } Test1.prototype.testFunction = function() { print("It works!") } You could take or leave the initialize idea (it's only an idea to make inheritance of the constructor more straight forward). Another alternative would be to alias the prototype keyword (e.g like jQuery to fn) |
Right, I am not suggesting changing semantics, but to fully convey the existing semantic richness of the prototype object system, you will need to come up with tons of new syntax, which I think is completely unneeded. You're trying to cram Java like semantics into prototypal OO, and I don't like it. |
I don't think I am. Really I'm just trying to figure out a different keyword to "prototype" which is both intimidating and confusing (particularly, to newcomers). It's also a pain to type :D |
If we're going to address this issue, our answer needs to be capable of expressing the full semantic richness of JS's prototype system, as kamatsu says. The problem with exposing all of that plumbing directly is that it's very easy to break or mangle your prototype chain by accident. I don't think that there's a place for Ruby or Java-esque classes in CoffeeScript. But we should have a convenient syntax for accomplishing the same patterns with prototypes, without the risk of messing up the chain.
|
First off, CoffeeScript is lovely. Thanks for spending your time on it. "You're running into a JavaScript (mis)feature..." Seems there are at least 3 solutions here:
See http://gist.github.com/267191 (a mod of http://gist.github.com/266191) |
Hey Alson. Thanks for the patch. I have a couple of concerns:
So I'm thinking more along the lines of a complete complete class-definition syntax, if we pursue this. You'd have to have a clean way of saying: here is a class, this is its name, these are its methods, these are its properties, this is the constructor. |
A lot of the current verbosity in creating a class lies in repeatedly writing
Could compile into:
|
I'd don't like how it looks like it's a method on Bird itself, prefer sthing like Bird::fly wind => airspeed * wind * effort |
Yeah, you're right, and it doesn't even save that much. Nevermind. |
Taking the example from the current documentation, what do y'all think of something like this:
All methods and properties would be defined on the prototype, and the constructor would return "this", naturally. |
How would you go about adding further methods to the prototype. Perhaps Also, in this example, how would the base constructor get called from the subclasses? |
If you wanted to add methods to the prototype from outside, you can just add them:
You should be able to call your ancestor's constructor using |
Sorry - just to be clear, in this instance I.e. as with normal JS, the constructor is not subclassed (something that's unfortunately confusing for newcomers). |
Hmm, in the case above, subclasses like Snake should call Animal's constructor unless they choose to override it, no? It might be tricky to implement. |
That's what it should do, but as you say implementation might be tricky. Coffee could keep track of implemented classes, but seems out of the question. I suggested an implementation where each constructor automatically called an 'initialize' function with the inbound params which would then be subclassed automatically. The only problem I can see with this is that it means that every class would have an initialize function. |
Jeremy, w.r.t. creating a real Class mechanism: one of the aspects of CoffeeScript I really like is that it's basically just pretty JavaScript. Creating a Class mechanism would add to CoffeeScript something that is not within JavaScript, so I'd be less excited about that. If you delete the word "Class" from your sample code, wouldn't you have clean CoffeeScript that is concise and doesn't imply anything special? Animal: constructor: name => this.name: name move: meters => alert(this.name + " moved " + meters + "m.") Snake extends Animal: speed: 5 move: => alert("Slithering...") super(this.speed) subclasses like Snake should call Animal's constructor unless they choose to override it, no? From my perspective, Javascript doesn't do that, so I wouldn't expect CoffeeScript to do so. Alson |
Yeah, I agree completely, which is why there's been so much discussion on this page, and why we haven't moved forward with any of the proposals yet -- I don't think that my most recent posted one is very satisfactory. Ideally, we can come up with something that keeps JavaScript's prototype flavor (avoiding |
One last note: object prototypes already have a constructor member, so 'constructor' is probably a bad term to use (lest people be confused about whether they're modifying widget.prototype.constructor or not). Maybe '_'. Animal: _ : name => this.name: name move: meters => alert(this.name + " moved " + meters + "m.") Snake extends Animal: _ : name => super(name) speed: 5 move: => alert("Slithering...") super(this.speed) This syntax is pretty nice and is just sugar. 'Snake extends Animal' is a little complicated because the constructor needs to be pushed up to run before the object is extended... |
The way I tend to write objects in JS is perhaps appropriate here:
I believe inheritance is a broken model (I am a haskell programmer by trade) so I tend to use mixins exclusively. Anyway, the only problem faced by my object model in coffee is that I have to add "this" at the end. The model you are proposing is different, and unfortunately doesn't allow me to make private data via closure. That's why I'd rather a smaller change that just prevented the unsightly "this" from appearing at the end. |
I was just playing with the example at the start - it's quite odd, since it works if the return value is a string, but not an array or and object. Quite odd?! |
We could do somethign like: Animal: name --> ::name = name ::move = meters --> alert(::name + " moved " + meters + "m.") Animal::hello = --> alert("hello") (I was just also experimenting with using = and --> in above) |
or use @ in place of ::, for something more rubyesque. |
I'm getting a bit confused in this thread... It started off simple (constructor behavior is unexpected) and got pretty complicated (I think there are now a number of separate features within this thread). Maybe we could move this discussion off onto a wiki page and do the following:
Assuming that y'all are okay with that, I've added a wiki page to my repo and made you all collaborators. Feel free to edit to taste, to add example code, etc. See here: And shouts out to any Haskellers who're reading... |
sorry alson - here's another idea to add to the melting pot: supress the return value if the function starts with a capital (it would still be possible to explicitly return from there functions). |
yes i agree, |
It's not a problem to collide with JS keywords -- I'm using "extends" and "super", after all. They won't be present in the final JS. |
Right, |
A question for Jeremy: is CoffeeScript a sugaring of Javascript (better syntax; same semantics)? Or is CoffeeScript a fixing/normalization of Javascript (better syntax; some different semantics)? If it's sugar, then I tend to agree with Kamatsu that 'class' doesn't seem like an appropriate addition. If it's a clean-up/normalization, then adding 'class' is lovely. I tend to use a number of existing JS libraries and would love to use them with CoffeeScript. The semi-guarantee of semantic alignment between JS and CS means that I can do so. If that semi-guarantee is at risk, then CoffeeScript is much less useful to me. |
There should be no risk of semantic mis-matching between CoffeeScript and JavaScript. We're not adding anything to the runtime -- no extensions of core objects, no assumptions of special predefined helper functions, as convenient as it might be to have them. On the other hand, CoffeeScript is only going to provide syntax for "the good parts" of JavaScript. Ideally, you wouldn't be able to write CoffeeScript that ever creates a warning in JSLint, without it being a syntax error in CoffeeScript first. We're not there yet, but it's a nice abstract goal. We're definitely wandering away from "same semantics" with some of the syntax that compiles into larger chunks of JS: The existence operator Still, anything that you can write in JavaScript, you should be able to write in CoffeeScript -- including integration with JS libraries. If you can't, please file a bug. |
|
Except class isn't at all what it is. It has nothing but wrong connotations. The C++/Java choice of the "class" keyword is even a misappropriation from type theory that unfortunately permeated the lexicon. Constructor is a good keyword because you are defining a constructor for an object. You are in no way specifying a class. |
Weepy,
We're constructing whatever Javascript is constructing with its 'new' operator... 'constructor' is a notion from Javascript so would be good to mirror in CoffeeScript. In Javascript new Klass(); behaves differently from Klass(); CoffeeScript does not mirror this behavior so constructing an object in CS is error-prone if you expect CS to have JS's behavior. Another possible solution to the issue with 'new' behavior is to adopt Javascript's behavior and wrap the function with a 'return this'. Wouldn't be pretty since every function would have to set something like 'coffee_script_last_this = this', then 'k = new Klass()' would compile to: new Klass(); k = coffee_script_last_this; Hackety way to ignore Klass's return value... Probably not thread safe... |
fair enough guys - you're probably right. constructor it is ? |
OK. I've updated the gist to use constructor. Can I get feedback please. http://gist.github.com/gists/267997 |
LGTM! |
Just making sure that we have all of the bases covered (I liked Kamatsu's example code). In functions, we have notations to declare:
If we're going to adopt Ruby-ish syntax, then weepy's proposal looks good. constructor cons : s => priv_storage : s # var priv_storage = s; @pub_storage : s # this.pub_storage = s; @@pro_storage : s # this.prototype.pro_storage = s; These notations, suitably modified, should also work when applied to an object: priv_storage : s #var priv_storage = s; obj@pub_storage : s # obj.pub_storage = s; obj@@pro_storage : s # obj.prototype.pro_storage = s; One issue is that this notation could cause confusion for Ruby programmers since '@' deals more with privacy than with storage location (both priv_storage and @pub_storage are stored locally, but priv_storage is only available while inside the object). [ARGH... Github's sometimes-Markdown, sometimes-Textile markup is killing me.] |
Alson, I think you've confused the prototypes with constructor's here. I think it's more like: constructor Obj : s => storage : s # var storage = s; @storage : s # this.storage = s; @@storage : s # this.constructor.storage = s; // or Obj.storage = s storage : s #var local_storage = s; Obj@storage : s # Obj.prototype.storage = s; // mostly useful for instance variables and functions Obj@@storage : s # Obj.storage = s; // this is a bit pointless? Essentially @ is used for instance variables. I'm not sure whether the @@ syntax is useful enough to include? |
Doh. Never type before having coffee... I guess I'm a little confused by the syntax, so was hoping to straighten it out a bit. From your sample it appears that @ sometimes refers to this and sometimes to prototype. An alternative might be to use '.' for instance and '@' for prototype. This would be simple sugar and would match up well with JS: constructor f : => a : 1 # var a =1; .b : 2 # this.b = 2; that.c : 3 # that.c = 3; that@d : 4 # that.prototype.d = 4; that..d : 4 # alternative syntax for that.prototype.d = 4; a : new f() .d : 5 # error: no 'this' available a.d : 5 # a.d = 5; a@d : 6 # a.prototype.d = 5; a..d : 6 # alternative syntax would be lovely to get Jeremy's thoughts here.. [hard to do this via the phone browser!] |
Sure, Alson. Sorry to have been so quiet. This is all a little too nebulous to try and get into CoffeeScript 0.2, so I've been trying to get that ready to release before turning back to this issue. It's a pretty epic thread though -- clearly doing JavaScript inheritance correctly is something that everyone has an interest in seeing happen. I'm happy to let folks come to consensus on a reasonable syntax before trying to build the thing. But still, some opinions:
Gotta run... |
To me the reason to use
Here, there is clearly a semantic difference between Regarding the issue with nothing being "private", why carry over this notion from Ruby? I see the @ sign as signifying "public" here, as in, "this property is publically accessible for all objects created by this constructor". |
re: @Property: this implies the addition of the uniqueified 'this', right? (this.this_12345 = this) Jeremy: thanks for stopping by! ;) 'constructor' notation: this seems error prone. If you forget to include 'constructor' then funky and very hard to detect errors occur. (yes, I have totally flip flopped.) I'm a fan of actually implementing 'new' in CS so that it operates the same as in JS. Might be ugly, but it seems like the best thing to do... 'this' and 'prototype' : since CS is just sugar, I'd prefer to let people elide bits that can reasonably be inferred and 'this' and 'prototype' can be inferred in the following: .prop # this.prop inst..prop # inst.prototype.prop Simple to implement and tightens up the code. It includes a slight risk of turning typos into valid statements. |
@jashkenas: whoa whoa whoa, did you just say nothing is really private in javascript? Where did you get that notion? behold!
|
Yeah, yeah, local variables are only accessible from within the closure, sure. Maybe it's just because it doesn't feel like a real private variable (but I'm probably just being old fashioned). Note that when you add methods like this, within the constructor, you have to re-create them every time you instantiate a new object (not-so-efficient), and they're not shared with other instances of the class. And yes, there are ways around this, but they're fairly awkward, which is an opportunity for us to address. |
I never meant @ to mean anything about public and private, or to point to _this_12345 idea ( a separate discussion). It's just an alternative syntax to my @prop compiles to this.prop Bird@fly compiles to Bird.prototype.fly I like it because:
Lastly I'd like to make the case of @ over . and :: -- It stands out more and doesn't get confused with assignment (:) |
@documentcloud: If the constructor adds methods, the efficiency cost is about as low as a hashtable lookup on each method creation. This is not C and we're already talking about awful performance generally for most JS VMs, a variable assignment is the least of your worries. As for privacy, closures and objects are actually proven equivalent, so your idea of a closed variable being not a "real" private variable is hogwash, you can mathematically reason about either identically. Also, they are shared with other instances of the prototype (not class), provided that prototype's constructor calls the previous constructor. This provides a convenient mechanism for implementing mixins and a very expressive object system. A true prototype object system such as Potion's would eliminate the out-of-constructor prototype manipulation, which is just a hack that JS adds. |
I'm splitting this ticket out into several ... it's getting too confused. |
Thanks for splitting it weepy, retiring this thread from the open issues... |
As of Issue #210, CoffeeScript has classes, which fixes the constructor / return value trouble. |
Fixes decaffeinate/decaffeinate#710 The lexer generates fake tokens for interpolated heregexes, and the ending tokens were being placed where the start (inclusive) and end (inclusive) index were one past the end of the heregex. This meant that in a case like `[a ///#{b}///]`, the end tokens of the heregex and also the implicit function call end were all being placed at the `]`, so the AST location data would say that the function call ends at the end of the `]`. This caused decaffeinate to insert the close-paren after the `]`, which is wrong. To fix, I can just subtract 1 from the position of those ending heregex tokens so that their end lines up with the end of the heregex itself. This is similar to previous fixes that changed `OUTDENT` and `CALL_END` tokens so that the end of the token lines up with the end of the AST node.
Case one works, case two doesn't. Or am I missing something really obvious here?
http://gist.github.com/266191
The text was updated successfully, but these errors were encountered: