Общо показвания

октомври 10, 2012

TypeScript (almost a week later or why you should not jump to it yet!)

I have played with the language and guess what... it is not so brilliant as one is lead to believe initially. As with many new products you start to discover the multitude of technical gaffes only after you use it for a while.

First of all I should tell that I rewrote a real world module used in production and proven to work with a) RequireJS and b) Closure tools, the later being fully annotated and all types discovered and identified correctly.

Now, in TypeScript, there are several severe problems for OOP and one of those is evident only if you use chain-able APIs. You know those, where you write almost 300 character lines of methods being invoked one after the other.

Lets see a simple example:

class Base {
  m() { return this; };
}

class Derived1 extends Base {
  m1() {};
}

class Derived2 extends Base {
   m2() {};
}

class Derived3 extends Derived1 {
  m() { return new Derived2(); }
}

Two things to notice: Base has a method called m and it returns the 'this' object, which as we know is special and is always pointing to the context in which the function/method is called. Second: Derived1 class extends Base and introduces a new method m1, which returns nothing (in TypeScript - void, in JavaScript undefined.

Would you like to assume what will the following code do?:

(new Derived1()).m().m1();

Well. it spills out an error: The property 'm1' does not exist on value of type 'Base'.

Analyzing the code is is pretty obvious what is happening: the compiler does not understand what is 'this' when the invocation is encountered. It figures out that the type is Derived1, then searches for method m on it, it does not find one, looks it up in the immediate parent, finds it, checks its return type and ... 'boom', it does not know what is 'this' anymore. Is it possible that Microsoft do not know anything about context in invocation or is it a bug?

Reading the related thread in the codeplex discussion board it is pretty clear to me that the people currently playing with the language have never ever heard of JavaScript compilation, AST and tree shaking and type inference and whole program compilation. It is also pretty obvious that the fragmentation of the JS library world does not help.

Lets try another example:

class Base {
  m() { return this; };
}

class Derived1 extends Base {
  m1() {};
}

class A {
  another(){
    return this;
  }
}

var a = new A();
var b = new Derived1();

a.another.call(b).m1();

As it might magically seems, this example works! How is this possible? Well, it is not magic, if we take a look at the definition of Function.prototype.call it is defined as returning 'any' and this is the correct declaration (because of course it can return anything). Here is the thing: the compiler should still be able to understand what is going on. Lets analyze this a bit: method of class A is called in the context of an instance of class Derived1. Lets look what is the return statement of that method: 'this' - s keyword in JavaScript and TypeScript. So if it is 'this' the return type should be the context in which it was called, right? Why is the compiler not able to determine that and instead rely on the declaration?

I will tell you why: because it was not designed to be able to do such complex tree walking. And it is okay, maybe the authors have different objectives. But what I think we should have is a language/compiler that can do this kind of things! Whole application compilation is what will really assure all your types are correct, not compiling small parts and then hoping that the type system will still work - it will not.

On the mentioned thread there was a proposition to have the _this keyword replace the fact that the compiler is not smart enough to know the type of the context of invocation. It might be true, while I do not agree, it is up to the designers of the language. But imagine this (as they promote the VisualStudio plugin): You have the code above and after you typed (new Derived1()). you get the completion and you select m() and then you get:

(new Derived1()).m().

and at this stage you get no meaningful completion, because the typescript service does not know what is the type of the result anymore. So now tell me, how useful is that?

Of course this is not really a show stopped, people seem to like it a lot, I also like the syntax for the modules and classes, kind of like the fact that you have your type definitions next to the variables, much more elegant than having Java like annotations. Unfortunately the type system reader seem to either be in its infancy or have different objectives than the ones I consider important, which makes the language not so attractive to me. There are things to be fixed, a lots of bugs have been narrowed down in the last week and I am sure the tool will mature, but the advanced features some developers are looking for (for example a robust type system as in Closure tools) are not there. Too bad, I really liked the syntax.

Good luck to Microsoft and I will make sure to check the language in 6 months. Unfortunately it seems the community will be driving the development and knowing the variety of styles and requirement I don't believe something coherent and useful will come out of it in the long run.

Няма коментари: