Skip to content

Suggestion: Syntax for constructor signature #4775

Closed

Description

@omidkrad

This is a valid signature:

    subscribe<T>(event: { new(...any): T; }, callback: (message: T) => void): void;
                        ^^^^^^^^^^^^^^^^^^^

I would like to suggest to support typeof T to be equivalent of { new(...any): T; } above.

    subscribe<T>(event: typeof T, callback: (message: T) => void): void;
                        ^^^^^^^^

which in my opinion is much more expressive. Notice that if we used typeof SomeClass compiler would accept it. So why not also accept typeof T?

Here's an example:

declare class EventAggregator
{
    subscribe<T>(event: typeof T, callback: (message: T) => void): void;
//  subscribe<T>(event: { new(...any): T; }, callback: (message: T) => void): void;
    publish<T>(event: T): void;
}

// class with zero argument constructor
class SomeMessage {
    constructor() {}
    data: string;
}

// class with argument constructor
class SomeMessage2 {
    constructor(public data: string) {}
}

var ea = new EventAggregator();
// CASE 1: okay - no error
ea.subscribe(SomeMessage, message => {
    console.log(message.data);
});

// CASE 2: okay - no error
ea.subscribe(SomeMessage2, message => {
    console.log(message.data);
});

// CASE 3: errors out - good, that is expected. I want the method to
// take only classes (types) and not instances of classes
ea.subscribe(new SomeMessage(), message => {
    console.log(message.data);
});

ea.publish(new SomeMessage());
Workarounds that don't work really

Using generic type of T extends Function or Function instead of T breaks type inference of T

subscribe<T extends Function>(event: T, callback: (message: T) => void): void;
          ^^^^^^^^^^^^^^^^^^
subscribe<T>(event: Function, callback: (message: T) => void): void;
                    ^^^^^^^^

Activity

  1. changed the title [-]Suggestion: Syntax sugar for constructor signature[/-] [+]Suggestion: Syntax for constructor signature[/+] on Sep 14, 2015
  2. kitsonk commented on Sep 14, 2015

    @kitsonk
    Contributor

    What about constructors that have a defined argument signature?

  3. omidkrad commented on Sep 14, 2015

    @omidkrad
    Author

    Kitson Kelly (@kitsonk) in that case you can use exact constructor signature like { new(a: string, b: string): T }.

  4. kitsonk commented on Sep 14, 2015

    @kitsonk
    Contributor

    Then that would seem "auto-magic" to me and surprising... "if you have this one signature you can use this one keyword in these scenarios, but if you have anything else..."

  5. ahejlsberg commented on Sep 14, 2015

    @ahejlsberg
    Member

    The typeof operator in a type position is used to obtain the type of a variable. I think it would be really odd for it to also be notation for a constructor function type. Plus it would be a breaking change:

    var x: number;
    function foo<x>(b: typeof x) { }

    I would suggest defining a generic type that represents a constructor:

    interface Constructor<T> {
        new (...args: any[]): T;
    }
    
    declare class EventAggregator
    {
        subscribe<T>(event: Constructor<T>, callback: (message: T) => void): void;
        publish<T>(event: Constructor<T>): void;
    }
  6. omidkrad commented on Sep 14, 2015

    @omidkrad
    Author

    Kitson Kelly (@kitsonk) that was the point of my suggestion, to define type of constructor with arbitrary arguments.

    Anders Hejlsberg (@ahejlsberg) the Constructor interface you suggested is what I needed. Thank you! I noticed I had suggested this before in issue #204 but didn't come up with a solution I liked.

    However, I wonder what typeof ClassX means below? Isn't it denoting the constructor type of ClassX? If the compiler is accepting typeof ClassX then why it's not complaining on foo(ClassY)?

    class ClassX {}
    class ClassY {}
    function foo<x>(b: typeof ClassX) { }
    foo(ClassX);
    foo(ClassY);
  7. ahejlsberg commented on Sep 14, 2015

    @ahejlsberg
    Member

    However, I wonder what typeof ClassX means below? Isn't it denoting the constructor type of ClassX? If the compiler is accepting typeof ClassX then why it's not complaining on foo(ClassY)?

    You have no members in ClassX and ClassY so they are structurally equivalent. Add a member to each and you'll get an error:

    class ClassX { x; }
    class ClassY { y; }
    function foo<x>(b: typeof ClassX) { }
    foo(ClassX);
    foo(ClassY);  // Error
  8. omidkrad commented on Sep 14, 2015

    @omidkrad
    Author

    OK, that makes sense. Now what makes typeof ClassX valid below but not typeof T?

    class ClassX { x; }
    class ClassY { y; }
    function foo(b: typeof ClassX, c: ClassX) { }
    foo(ClassX, new ClassX);
    function bar<T>(b: typeof T, c: T) { } // Error
    bar(ClassY, new ClassY);
  9. ahejlsberg commented on Sep 14, 2015

    @ahejlsberg
    Member

    typeof ClassX is valid because ClassX denotes a named value (i.e. the constructor function). It also denotes a type, but that's irrelevant here.

    typeof T is an error because T doesn't denote a named value.

  10. ahejlsberg commented on Sep 14, 2015

    @ahejlsberg
    Member

    An easy rule of thumb is: When typeof is used in a type position, the argument must be a valid expression and the operator obtains the type of that expression.

  11. added
    Too ComplexAn issue which adding support for may be too complex for the value it adds
    on Sep 14, 2015
  12. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    SuggestionAn idea for TypeScriptToo ComplexAn issue which adding support for may be too complex for the value it adds

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions