typeof T as argument

Topics: General
Oct 15, 2013 at 12:27 PM
I don't understand why you can't do:
get<T>(type: typeof T): T;
You can do this in java
<T> get(Class<T>: clazz): T
So why is typescript designed to have such limitation?
Oct 15, 2013 at 3:01 PM
I think you're misunderstanding what the typeof operator does.

This is perfectly legal TypeScript code:
class Foo<T> {
     private _contents: T;

     static unwrap<T>(holder: Foo<T>): T {
           return holder._contents;
     }
}
See this for more details on typeof.

Are you specifically trying to use generics with the getter syntax? That doesn't seem to be supported currently.
Oct 15, 2013 at 3:55 PM
It has nothing to do with getters, its normal method just named get, sorry for confusion. I know that generics work in typescript. I don't understand whats the point of your code snippet

As in original post I am using typeof T as Class<T> in java (in C# there is really no equivalent but its like Type). Am I using it wrong?

Since code above doesn't work i use this code:
get<T extends MyClass>(type: typeof MyClass): T;
var a = get<MySubClass>(MySubClass);
and I have to write "<MySubClass>" everywhere, because I can't pass T as argument and type can't be infered. So question is Why can't i replace typeof MyClass with typeof T?
Oct 15, 2013 at 4:10 PM
Ah ok, I think I understand now. It looks like you want return type covariance on generic parameters. From a quick check in the playground, it looks like TypeScript supports return type covariance, but as you've found apparently not this scenario with generics. I'd open an issue on the tracker if you want people to vote on this type of support.
Oct 15, 2013 at 4:22 PM
Well I've created an issue
https://typescript.codeplex.com/workitem/1786
But I didn't realize I can't even make typeof T parameter, and as it was closed as by design so I wonder if there is something that prevents it from working like in java that I'm missing.
Coordinator
Oct 15, 2013 at 4:45 PM
Oct 15, 2013 at 4:54 PM
Edited Oct 15, 2013 at 4:54 PM
Yes I have, and if all my types inheriting from MyClass had parameterless constructor I might have used your approach. Ok I could maybe do something like new (...any[]) => T; And that could work, but I thought that typeof could be nicer replacement for hacks like this.

Is my understanding of what typeof (as method argument) is wrong? Is it something different then Class<T> in Java?
Coordinator
Oct 15, 2013 at 5:06 PM
When used in a type position, typeof produces the type of an expression. It doesn't do anything else.

I think the thing to realize is that in TypeScript, the constructor function is the class object. Modeling this isn't a hack, it's reality! In JavaScript, there isn't a one-to-one mapping of constructor functions to types like there is in Java or C#. You could easily have an external library where new Foo() and new Bar() both returned objects of type Qua, so you can't just take an object of type Qua and go back to Foo/Bar.

Indeed, this works fine:
class NoArgs {
    private n;
    constructor() { }
}
class SomeArgs {
    private m;
    constructor(n: string, y: number) { }
}

function foo<T>(type: new(...args: any[]) => T): T {
    if(type === NoArgs) { /* ... */ }
}

var a = foo(NoArgs); // a: NoArgs
var b = foo(SomeArgs); // b: SomeArgs
Oct 15, 2013 at 5:57 PM
Edited Oct 15, 2013 at 6:28 PM
the constructor function is the class object.
I understand this.


But I dont understand whats the difference between
foo<T>(type: typeof MyClass) 
and
foo<T>(type: new() =>MyClass)
  • I understand that the difference is that second statement takes only one constructor, but the first one takes "any" constructor (I realize that there can be only one constructor per class)
Whats the reason behind having "typeof MyClass"? Is it not that you can pass MyClass regardless of what kind of constructor the class has?
And if thats the reason, why can't we have "typeof T" for the same reason?
Developer
Oct 15, 2013 at 7:38 PM
One difference is that the constructor function type includes the entire static side of the class. So in your first definition for foo if MyClass had any static members then they would be accessible from the argument named type within foo. In your second definition of foo you are allowing a more general set of constructor functions and not requiring a particular set of static members.

Either way it doesn't affect why 'typeof T' is not valid. T is not a value, it is only a type, and one which doesn't exist at runtime. What would the compiler emit to make this work? Consider this similar case without generics which is also invalid:
interface I {
    new (): MyClass;
}

function f(x: typeof I) { // error
    var r = new x();
}
The interface doesn't exist in the emitted JavaScript, so what could the compiler emit here that would work and make sense?
Oct 15, 2013 at 8:08 PM
Edited Oct 15, 2013 at 8:12 PM
I don't understand how is this different from java.



I guess i want generated code to look like it does in this case
interface I {
    new (): MyClass;
}

function f(x: () => I) { // ok
    var r = new x();
}

//into js

function f(x) {
   ...
}
So I don't get why I can't do this either.


Its only tool for compile time "safety", i dont want anything special at runtime.


Yes T is not a value, thats why i cant do
foo<T>() {
   new T();
}
Because this information is lost at runtime, but when i pass the type its no longer just a type its some kind of value.


I really don't get what I am missing here.
Coordinator
Oct 15, 2013 at 8:45 PM
The reasoning for why typeof exists is fully explained in the linked post. I'm unclear on what the question is here. Are you asking why typeof T isn't a shorthand for { new(...args: any[]): T; } ?
Oct 15, 2013 at 8:50 PM
Edited Oct 15, 2013 at 9:10 PM
I guess I am asking why can't this:
foo<T>(type: typeof T) {
}
be translated into
function foo(type) {
}
And why can't there be all static checks done by compiler that has type information. For not passing stuff that is not T, and infering T from that parameter.

In the linked post, you explain why you can't have foo<T>() and access T inside such method, which is obvious. And its obviously not possible in java either.
Coordinator
Oct 15, 2013 at 9:29 PM
You can already do all those things without using typeof.
Oct 15, 2013 at 9:40 PM
Edited Oct 15, 2013 at 9:44 PM
And if I want to do:
class MyClass {
   static stuff() {}
}

foo<T extends MyClass>(type: typeof T) {
   type.stuff();
}
So typeof is ONLY for having access to static methods?


And if I want to store a "class reference" I should use the constructor syntax? Isnt that kind of unintuitive?
Coordinator
Oct 17, 2013 at 6:30 PM
For what you're trying to do, think of T as a structural type rather than a particular name. If you want to pass in something that invokes .stuff, you would need to pass the static piece of MyClass:
class MyClass {
   static stuff() {}
}

interface Stuffable {
   stuff();
}

foo<T extends Stuffable>(t: T) {
   t.stuff();
}

foo(MyClass);
You don't use typeof to try to get at the types passed into generic functions. You can't see those, and that's intentional. You can only see the structure that the function sets provides in the constraint on the generic.
Oct 18, 2013 at 12:00 PM
Edited Oct 18, 2013 at 12:08 PM
If you want to pass in something that invokes .stuff, you would need to pass the static piece of MyClass:
I thought that thats what typeof is for (when used as variable type).


I would hope that there is a way that you can force user to pass just MyClass and not new MyClass() too. Because there is if I wouldn't use generics, since without genrics I can use typeof exactly as I want to. It just doesn't work with typeof T, and I still don't understand the reason why not.

Maybe I am just too used to normal languages where new MyClass() doesn't behave as MyClass in any situation. And where you can't call static method using object instance, but I guess thats the cost of being in javascript world.
You don't use typeof to try to get at the types passed into generic functions. You can't see those, and that's intentional.
I don't think I understand what you are saying. There is typeof T that is used as declaring type of variable. And there is typeof operator that does runtime stuff - you can use T there since it's not runtime, I get that and I've never talked about that.
You can only see the structure that the function sets provides in the constraint on the generic.
Yeah, I know, thats why in my example I used T extends.
Oct 30, 2013 at 3:09 PM
Kikaimaru wrote:
I don't understand why you can't do:
get<T>(type: typeof T): T;
You should be able to do :
get<T>(type: T): T;
'T' is not an instance of a type, it IS a type. You don't need to dereference it with "typeof".
Oct 31, 2013 at 8:34 AM
Edited Oct 31, 2013 at 8:43 AM
Because I don't want to pass parameter of type T, but a parameter holding type T.
class Person {
}

class Student extends Person {
}

foo<T extends Person>(type: typeof T) {}


foo(new Student()); // Passing value that is __of__ type T
foo(Student); // Passing type that is type T
Obviously your example will work even with passing just "Student" (even though I don't know why, I dont think that type Student is of type Student), but I don't want to be able to pass new Student()


As I've said I want same logic that java provides with Class<T>
Coordinator
Nov 11, 2013 at 5:45 PM
Kikaimaru,

TypeScript doesn't have the equivalent of a templating system where you can actually reference the types being used to instantiate the generic. Generics are autonomous, and can only see what the constraints say is visible.

The 'typeof' argument is only used to get the type of an expression and can't be used against types or type variables.

This is because type checking is modular, allowing you to type-check code in isolation. This becomes important when creating and working with .d.ts files, where we needed to produce correct types before we've ever seen how they were used.
Nov 26, 2013 at 12:52 PM
Edited Nov 26, 2013 at 1:09 PM
RyanCavanaugh wrote:
Indeed, this works fine:
class NoArgs {
    private n;
    constructor() { }
}
class SomeArgs {
    private m;
    constructor(n: string, y: number) { }
}

function foo<T>(type: new(...args: any[]) => T): T {
    if(type === NoArgs) { /* ... */ }
}

var a = foo(NoArgs); // a: NoArgs
var b = foo(SomeArgs); // b: SomeArgs
So this code no longer works with 0.9.5 beta. Since SomeArgs constructor would have to have signature (n?: string, y?: number)

Is there currently a workaround to use instead of typeof T in 0.9.5?