Superclass fields and methods initialized in inconsistent orders?

Topics: Language Specification
Nov 28, 2012 at 6:55 AM

TypeScript seems to have a strange way of initializing methods and fields. Fields are initialized in the order superclass->subclass, while methods are created in the order subclass->superclass. For instance, take a look at this code:

class User {
    constructor () {
        console.log("Field from: " + this.field);
        console.log("Method from: " + this.method());
    }
    field: string = "User class";
    method(): string { return "User class"; }
}

class RegisteredUser extends User {
    field: string = "RegisteredUser class";
    method(): string { return "RegisteredUser class"; }
}

var registeredUser = new RegisteredUser();

In my opinion, it would make most sense to have this output:

Field from: RegisteredUser class 
Method from: RegisteredUser class

Failing that, this would at least be consistent:

Field from: User class 
Method from: User class

But instead, this is what we get:

Field from: User class 
Method from: RegisteredUser class

And of course, that makes no sense at all. Is this as-designed? Or a bug? Or just part of the spec that hasn't been thought through yet?

Nov 28, 2012 at 1:47 PM
Edited Nov 28, 2012 at 1:51 PM

My gut feeling is that this does not have a lot to do with typescript. This is simply an artifact of the underlying idiomatic javascript that is emitted for classes by way of prototype based inheritance. It seems people are already starting to forget that typescript is not a first class language generating bytecode or assembly. You guys must be doing something right :)

If you think this behaviour is genuinely avoidable, take a look at the generated javascript and try reordering the statements to see if you can come up with the initialization ordering you want, while keeping the inheritance intact.

Coordinator
Nov 28, 2012 at 2:59 PM

Oisin is correct, the initialization order is the same as happens if you took the declarations and translated them to JavaScript (which we do).  The reason here is that the class syntax is a layer of syntactic sugar rather than the style of class declarations in other languages which do not have an ordering (and which may reorder for you behind the scenes).  Because we try to be very careful not to move code around and to keep it as close to what you originally wrote, you do have to be aware of ordering in cases like this.

Nov 28, 2012 at 4:48 PM
Edited Nov 28, 2012 at 4:50 PM

Agreed that TS is doing a good job of hiding these things behind the scenes, though periodically these things peak through in ways that seem very unintuitive without close study of the emitted JS. (See here for more examples: http://blog.wouldbetheologian.com/2012/11/various-typescript-weirdnesses.html.) 

To a user of the language - not to someone designing the language, or trying to figure out which JS constructs should map to which TS constructs - it's a bit of a minefield. There's nothing in the structure of TypeScript itself which tells me, "You can depend on methods to be initialized correctly, but not on fields." My own opinion is that when it comes to the emitted JavaScript, it should be a secondary concern to make it look like idiomatic JavaScript (which is a badly broken language, pushed far beyond anything it was ever intended to do), and should instead try to make a nice beautiful language that behaves in reasonable ways (C# is a good model), even if that's somewhat different from what you would end up writing yourself. TS already makes you think that you're almost in C# - I think it should basically go all the way, and do more work for you behind the scenes to result in JS that behaves in intuitive ways. Think of JavaScript as MSIL or ByteCode, that needs to behave in certain ways publicly, but behind the scenes can do all sorts of unobserved private things.

Beyond that, given that TS very much does reorder things in certain instances - for instance, it always puts the constructor at the top of the emitted JavaScript no matter where you put it in your code, and then it moves the field initializers in-between the _super() call and the rest of the constructor - it seems awkward that TS doesn't change things around in this instance.

That said, perhaps I'm just too used to C#, and I'll get used to these weirdnesses in time. I doubt any language is without them, and JavaScript is particularly rife, but it does seem like TS also has more than its fair share at the moment.

Nov 29, 2012 at 3:49 PM
Edited Nov 29, 2012 at 3:54 PM

Here's another way to put it. Currently, TypeScript forces you to spend quite a bit of brain cycles (not to mention debugging time) on worrying about things that you shouldn't have to worry about, and that's not good. For instance, you have to worry about what "this" points to - because if you call a class method normally, it points to one thing, whereas if you call the same method from a callback within the same class, it points to something else entirely. The point of a decent language is to let you avoid wasting brain cycles thinking about stuff like that, and spend those same cycles (and debugging time) on things that actually add value to your program.

The same is true here. If you call a method in the constructor, a field can point to one thing; but if you call the same method later on, that very same field won't just have a different value - it'll be pointing to something else entirely. That's a serious violation of the "law of least astonishment", and once again, it forces you to spend time thinking about things that you shouldn't have to think about.

Remember, the "class" keyword changes everything. Yes, I get that TS is a strict superset of JavaScript, and that's awesome. When you're writing valid JavaScript in TypeScript, yes, you need to keep the same behavior. But the minute someone puts in a "class" or an "interface" - everything changes. At that point, nobody expects it to behave like JavaScript, because it's not. It has all sorts of awesome constructs, like static typing, lambdas, type inference, inheritance, polymorphism and other things that you expect from a modern language. You should put a priority on making everything that happens within that class resemble a reasonable, modern language, and steer well clear of JavaScript's mistakes.

Nov 29, 2012 at 5:34 PM
Edited Nov 29, 2012 at 5:36 PM
smithkl42 wrote:

Here's another way to put it. Currently, TypeScript forces you to spend quite a bit of brain cycles (not to mention debugging time) on worrying about things that you shouldn't have to worry about, and that's not good. For instance, you have to worry about what "this" points to - because if you call a class method normally, it points to one thing, whereas if you call the same method from a callback within the same class, it points to something else entirely. The point of a decent language is to let you avoid wasting brain cycles thinking about stuff like that, and spend those same cycles (and debugging time) on things that actually add value to your program.

 

Agreed, and there is work in progress (remember this is still an alpha release) to help make this easier. One of my suggestions concerning "this" pointers is here: http://typescript.codeplex.com/workitem/507 Please, vote. 

The same is true here. If you call a method in the constructor, a field can point to one thing; but if you call the same method later on, that very same field won't just have a different value - it'll be pointing to something else entirely. That's a serious violation of the "law of least astonishment", and once again, it forces you to spend time thinking about things that you shouldn't have to think about.

 

Yep, as above.

Remember, the "class" keyword changes everything. Yes, I get that TS is a strict superset of JavaScript, and that's awesome. When you're writing valid JavaScript in TypeScript, yes, you need to keep the same behavior. But the minute someone puts in a "class" or an "interface" - everything changes. At that point, nobody expects it to behave like JavaScript, because it's not. It has all sorts of awesome constructs, like static typing, lambdas, type inference, inheritance, polymorphism and other things that you expect from a modern language. You should put a priority on making everything that happens within that class resemble a reasonable, modern language, and steer well clear of JavaScript's mistakes.

 

Now here is where you're losing your way. TypeScript is all based on current plans for the upcoming JavaScript "Harmony" (ECMA 5.) So when the "real" JavaScript gets class, module, lambdas etc., are you still going to be confused? For all intents and purposes, TypeScript is JavaScript and don't forget it, literally or figuratively or you'll choke on your own brainfarts :)

Nov 29, 2012 at 5:45 PM
Edited Nov 29, 2012 at 5:47 PM

@Oisin - Good point about TS being a sort of preview of EcmaScript 6. But I'd say the same thing to the EcmaScript committee. Let classes behave like classes, and actually encapsulate things. Letting "this" change (within a class method) depending on who is doing the calling is a serious violation of encapsulation.

Nov 29, 2012 at 5:53 PM
Edited Nov 29, 2012 at 5:54 PM

@smithkl42 - I agree in principle about classes, but it's probably a bit early to say exactly how de-facto es5 classes will differ from idiomatic ones. Right now we're at the mercy of call/apply semantics. At the same time, I am strictly against turning javascript into another boring statically typed language. If it wasn't for its bizarre quirks and "weird" syntax, we wouldn't have such a rich and delightfully wacky ecosystem. I actually like javascript - as a toy to play with, and for small projects - but like everyone else hooked on typescript, I find it a hindrance in a team scenario with large projects.

Nov 29, 2012 at 6:26 PM

I suppose what really needs to happen is for browsers to start supporting something like MSIL or ByteCode, so that we can write our programs in whatever language we want, with whatever quirks we happen to prefer - and then use them all together. I don't suppose that's going to happen anytime soon, however.

Nov 30, 2012 at 2:31 PM

"I suppose what really needs to happen is for browsers to start supporting something like MSIL or ByteCode JavaScript, so that we can write our programs in whatever language we want..."

;-)

 

Dec 1, 2012 at 12:08 AM

I'm all for TypeScript treating JavaScript as an inefficient, human-readable form of MSIL. I just think that the TS team should go all the way, and make sure that they never make a decision based on the assessment that, "This will produce some weird looking JavaScript, so let's not implement this feature." Make TS work how TS should, and treat the generated JS as a black box.

Dec 1, 2012 at 8:59 PM
smithkl42 wrote:

I'm all for TypeScript treating JavaScript as an inefficient, human-readable form of MSIL. I just think that the TS team should go all the way, and make sure that they never make a decision based on the assessment that, "This will produce some weird looking JavaScript, so let's not implement this feature." Make TS work how TS should, and treat the generated JS as a black box.

 

I (and many others) disagree with this sentiment. One of the truly great things that differentiates TS from other javascript generators is that the output is idiomatic and entirely readable. Right now, I am building a very large system that involves quite a lot of javascript and I will have to hand off the entire thing to my client who will maintain it. I am delighted in the knowledge that I don't have to reveal that I used TS to build their system, and nor do I have to go through the all too common rigmarole of having to justify a "new" technology and produce the supporting documentation to justify my need. I don't have to argue with them about the future of TS, nor its supportability. They can just take the JS and run with it, which is likely what they'll do because they are not a Microsoft house.