Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature suggestion: treat prototype assignments like merging type declarations #4038

Closed
ghost opened this issue Jul 27, 2015 · 12 comments
Closed
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript

Comments

@ghost
Copy link

ghost commented Jul 27, 2015

I would like better native support for code of this nature:

function impl1() { // implementation A for myVirtualMethod
    this.x = 1; // this is treated as global object or any
}

function impl2() { // implementation B for myVirtualMethod
    this.y = 1; // no type-safety, compiler doesn't know what 'this' will be bound to.
}

export class Base {
    public myVirtualMethod() {}

    public x: number;
}

export class Derived1 extends Base {
    // can't assign function to myVirtualMethod here to override method
}
Derived1.prototype.myVirtualMethod = impl1; // override method with function

export class Derived2 extends Base {}
Derived2.prototype.myVirtualMethod = impl2; // override method with function

The code above works, but has a couple of drawbacks, which I think are resolvable:
The compiler has no way of knowing what this pointer should be in impl1 and impl2

  • I would like a way to redefine what type this is for functions.
  • It would also be nice if the compiler could detect if this contract is not honored (e.g., the function is invoked without a receiver, or is assigned to a method on a class which does not match the interface)
  • Possibly infer an interface for the receiver of the function if none is declared

Possible syntax:
function impl1() overrides Base.myVirtualMethod {}
function impl1(): this implements Base {}
function impl1() { const this: Base; }

The compiler won't let you define a method by assigning a function, so you must assign to the prototype instead

  • It may be nice to have a way to designate that certain properties are intended to be assigned to the prototype, from inside the class body
  • I would like way to declare a method but define the body through assignment

Possible syntax:
public myVirtualMethod() = impl1; // Parentheses distinguish this from a property assignment.
public myVirtualMethod() = null; // pure virtual method (runtime error to invoke, no compile time check)

Type checking:
impl1(); // Type error: Window does not implement { x: number }
this.y = 1; // Type error: Base does not implement { y: number }
public myVirtualMethod() = impl2; // Type error: Derived1 does not implement { y: number }
impl2.call( new Derived1 ); // Type error: Derived1 does not implement { y: number }

@kitsonk
Copy link
Contributor

kitsonk commented Jul 27, 2015

Sounds like a combination of several dupes: #229 #3694 #3442

@ghost
Copy link
Author

ghost commented Jul 27, 2015

@kitsonk Fair enough for #229 and #3694. #3442 refers to overloading instead of overriding, so I think it's different.

I don't see the second part, "define method by assignment," in any of those.

@kitsonk
Copy link
Contributor

kitsonk commented Jul 27, 2015

Ah, I see, you then are looking for mixins/traits (#311) or some sort of abstract class (#6, #2947). The way you are doing it, by directly modifying the prototype will mean the current scope is always a bit of a challenge.

@ghost
Copy link
Author

ghost commented Jul 27, 2015

I want:

function bar() {}
class Foo {
  public bar() = bar;
}

to have the same output as

function bar() {}
class Foo {}
Foo.prototype.bar = bar;

Along with the benefits of type checking. I think the scope is much smaller than some of the other proposals. No new keywords: it's basically sugar for something that already works. To make type checking easier, there can be a requirement that the value assigned must be a compile-time constant.

The assignment might reference a function, a function or arrow expression, a static method of another class, or null or undefined (thus implementing a "pure virtual" method). It may override a method of a parent class or it may be a new method altogether.

@danquirk
Copy link
Member

The request here is to essentially make directly assigning to .prototype analogous to an actual type declaration as far as type checking is concerned.

@danquirk danquirk added Suggestion An idea for TypeScript Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Jul 28, 2015
@danquirk danquirk changed the title Feature suggestion: method override by function Feature suggestion: treat prototype assignments like merging type declarations Jul 28, 2015
@kitsonk
Copy link
Contributor

kitsonk commented Jul 29, 2015

In theory, if it is reasonably achievable in typescript, it solves some of the issues around "natively" supporting Mixins and Traits (as well as Decorators would modify the type declaration, which they don't currently).

@danquirk do you think a reasonable proposal has a chance?

@danquirk
Copy link
Member

@kitsonk I haven't really chatted with anyone else on the team about this in depth yet so I don't want to make any promises I can't keep :)

At first glance this is definitely a bit outside what we normally do as far as flow control based semantics and such but it does serve a real use that many people would benefit from. I wonder how much ES6 classes will eventually obviate the need for direct prototype assignment in new code (obviously that still leaves a ton of existing JS that could benefit from this). Some might argue this sort of code is on the more dynamic side of the world which we generally try to allow but don't always support incredibly well (ex dynamic assignment/expandos typed as any).

@RyanCavanaugh
Copy link
Member

On the other hand, the work we're doing in the JS intellisense branch to pick up define and require calls (in non-TS code) might set a good precedent for how these things might work in the future.

@kitsonk
Copy link
Contributor

kitsonk commented Jul 30, 2015

I wonder how much ES6 classes will eventually obviate the need for direct prototype assignment in new code (obviously that still leaves a ton of existing JS that could benefit from this).

@danquirk yes, but you might assuming that ES Classes will support the right sort of design patterns. I for one remain unconvinced that ES Classes will head the direction everyone wants to go, and even then, it might take a while to get something agreed.

I know TypeScript is trying to do its best to be "future proof" and not meddle in things that might break in the future, but I suspect having a mechanism of extending the typing of a prototype won't interfere with future standards and get a lot of us who don't believe traditional OO inheritance is the right pattern off your backs. 😉 If we wanted to do composition, or traits, factories, or whatever, we could be the "Wizard of Oz" behind the curtains to ensure that our downstream consumed code was type safe.

@danquirk
Copy link
Member

Right, my concern here re:ES6 was not really future incompatibility and just musing on the general ROI of any work here if we expect people to move to ES6 classes in short order. There's definitely some opportunity here for TypeScript to just infer the right thing from a pattern that people are already commonly using, although as far as things like traits and mixins I'd prefer something a little more explicit/declarative than a mess of prototype assignments magicing up a type somewhere.

@kitsonk
Copy link
Contributor

kitsonk commented Jul 31, 2015

@danquirk I would too, but #311 is closed. 😢 The main reason was, if I understand correctly "we are going to wait for ECMAScript to take the lead..." It is hard to wait for TC39 to try to use and deliver other design patterns.

But if this sort of "modifying the prototype type" were allowed, I, as a middle man, could produce some of this functionality... I will admit @bryanforbes and I have been doing a lot of thinking about composition, factories, traits and mixins for Dojo 2. While union and intersection types will help us out, being able to modify the type of the prototype, would make it challenging for us, but we would be able to deliver a clean API that allowed use of the pattern without breaking TypeScript.

@RyanCavanaugh
Copy link
Member

We've implemented this for JavaScript, but don't see a need to do it for modern TypeScript code. Many better alternatives exist

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants