Description
I'm trying to convert a JavaScript functional-style "class" from JS to strongly-typed TS making minimal changes to the code other than adding type annotations. I do not want to use the class
keyword (this is an experiment to ease a refactoring scenario, not newly-written code). I also want the code to compile correctly using --noImplicitAny
.
The following code works as expected and compiles cleanly (in 1.4), however I am "cheating" by using the ClassFunction
type which is an alias for any
.
interface FunctionalPoint {
new (x: number, y: number) : FunctionalPoint;
x: number;
y: number;
isOrigin(): boolean;
}
type ClassFunction = any;
var FunctionalPoint: FunctionalPoint = <ClassFunction>function (x: number, y: number) {
var self: FunctionalPoint = this;
self.x = x;
self.y = y;
self.isOrigin = () => {
return self.x === 0 && self.y === 0;
}
};
var point1 = new FunctionalPoint(1, 1);
var point2 = new FunctionalPoint(0, 0);
var result = `point 1 is origin: ${point1.isOrigin() }, point 2 is origin: ${point2.isOrigin() }.`;
alert(result);
If the type assertion of <ClassFunction>
on the function is removed, I get this error:
Type '(x: number, y: number) => void' is not assignable to type 'FunctionalPoint'.
Property 'x' is missing in type '(x: number, y: number) => void'.
Is there any way to do this without resorting to using any
or refactoring to use classes? I read through the spec and 4.11 seems to indicate that if there is an apparent construct signature (which I believe the interface FunctionalPoint has), then "the result type of the function call becomes the result type of the operation" - which I believe should be FunctionalPoint, no?
Please forgive me if I am being dense on this. Thanks very much.
Activity
danquirk commentedon Mar 11, 2015
Why do you need
ClassFunction
instead of the cast toany
? An explicit cast still satisfies your requirement of compiling without errors undernoImplicitAny
.nycdotnet commentedon Mar 11, 2015
Thanks, Dan. Of course, using
<any>
and<ClassFunction>
are the same in terms of the type system. Using<ClassFunction>
has the practical benefit of letting me easily identify where I've done this particular hack via "Find all references" . Is there any way to do this without the cast toany
(or type aliases ofany
)?danquirk commentedon Mar 11, 2015
Ah, gotcha.
First, you probably intend to have a different shape for your class here. An interface with a construct signature generally should only have static members in it as it would be describing the constructor function. Like with your code there you can now legally do
new point1(1,2)
which is probably not what you intended. Instead you want:That said, there's still not really a better solution here than what you've done. The compiler only allows
new
on functions that return void and thennew
will return anany
which will trigger your implicit any warnings. You want to cast the function toFunctionalPointConstructor
but we don't allow that assignment, requiring a cast toFunction
orany
(or some alias of those as you did).It's possible we should add a new exception to the assignability rules specifically for this case (assigning void returning functions to construct signatures), as we essentially already have this special case of how void returning functions are newable to handle this pattern but don't allow you to follow through to stronger typing.
nycdotnet commentedon Mar 12, 2015
Hi Dan - thanks very much for this excellent explanation. I hadn't considered that side-effect (regarding being able to call new on an instance), and I should have thought to cast down to
<Function>
instead of<any>
.I do think that it would be nice to be able to close the loop in the type system in this manner. I was following the style used by examples on the Knockout JS tutorial site for this experiment. For example,
ReservationsViewModel
on this page: http://learn.knockoutjs.com/#/?tutorial=collectionsBecause these are samples from a popular library, I expect there is a lot of JS out there that looks just like this and it would be helpful to have a way to completely type it without needing to do unnecessary refactoring to proper TS classes or needing to do a non-obvious double-cast. Plus this would be a nice bone for those who are simply against classes and prefer the JS functional style (though most of those folks don't care for
new
either...)Thanks.
danquirk commentedon Mar 12, 2015
Yep, makes sense to me. I logged a new issue to capture that language change suggestion in a more concise form. I'll just close this one as by design and it can serve as an additional reference for that one.
nycdotnet commentedon Mar 12, 2015
Much appreciated.