microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.95k stars 12.48k forks source link

Extending intersection types #10549

Closed Neftedollar closed 8 years ago

Neftedollar commented 8 years ago

Add support of extending intersection types

Example

class Person1 {
 foo() {
  retorn "person1";
 }
}

class Person2 {
 bar() {
  retorn "person2";
 }
}

class Person extends Person1 & Person2 { 
 foobar() {
  retorn "person3";
 }
}

// or 
// type Persons = Person1 & Person2
// class Person extend Persons {}

var person = new Person();
person.foobar() //person3
person.bar() //person2
person.foo() //person1

Use case

For example, I have a class AngularComponent

export class AngularComponent  {
    someLogic(){ 
         /* .. */
    }
    public goBack(){
        window.history.back();
    }
    public goForward(){
        window.history.forward();
    }
} 

This component use history API for navigation. This is not only component who use this logic, so I dont repeat myself and create HistoryNavigationClass

export class HistoryNavigationClass  {
    public goBack(){
        window.history.back();
    }
    public goForward(){
        window.history.forward();
    }
} 

but some of my components does not use goForward() method, so I split HistoryNavigationClass into two separated classes HistoryBackWalker and HistoryForwardWalker

export class HistoryBackWalker  {
    public goBack(){
        window.history.back();
    }
} 
export class HistoryForwardWalker  {
   public goForward(){
        window.history.forward();
    }
}

and then I try to extend AngularComponent from intersection of HistoryBackWalker and HistoryForwardWalker type.

//type HistoryWalker = HistoryBackWalker & HistoryForwardWalker;
export class AngularComponent  extends HistoryBackWalker & HistoryForwardWalker  { // or   extends HistoryWalker
    someLogic(){ 
         /* .. */
    }
} 

but I can't.

With JavaScript

I can do this:

var Person1 = function(){};
Person1.prototype.foo = function(){return "person1";};

var Person2 = function(){};
Person2.prototype.bar = function(){return "person2";};

var Person = function(){};
Person.prototype.foobar = function(){return "person3";};

Person.prototype = Object.assign(Person.prototype,Person1.prototype,Person2.prototype);

var person = new Person();
person.foo();  // "person1"
person.bar();  //"person2"
person.foobar(); // "person3"
aravindarun commented 8 years ago

Typescript does not support extending intersections or multiple inheritance, for that matter. Can't you work around this problem by using interfaces and explicit type constraints? Shouldn't something like this work for you?

export interface IHistoryBackWalker  {
    goBack: () => void;
} 

export interface IHistoryForwardWalker  {
   goForward: () => void;
}

export abstract class HistoryWalker implements IHistoryBackWalker, IHistoryForwardWalker {

    public goBack() {
        window.history.back();
    }

    public goForward() {
        window.history.forward();
    }
}

export class AngularComponent extends HistoryWalker {     
} 

(new AngularComponent() as IHistoryBackWalker).goBack();
felixfbecker commented 8 years ago

Multiple inheritance will never work in TypeScript because it doesn't work in JavaScript. The prototype chain is a chain, not a tree. What you are looking for is language-level mixin support, there are open issues for this

kitsonk commented 8 years ago

Multiple inheritance will never work in TypeScript because it doesn't work in JavaScript.

Well, it could, if JavaScript supported it, so championing with TC39/ES Discuss would be the way to do that.

@Neftedollar while you can extend the prototype that way, you are breaking the prototype chain for Person, in ES6, this is valid:

class Person1 {
 foo() {
  return "person1";
 }
}

class Person extends Person1 { 
 foobar() {
  return "person3";
 }
}

console.log(Person.prototype.__proto__ === Person1.prototype); // true

You could of course change your example so that the prototype chain works for the first of whatever inheritance, but then you have a mixin problem where if the ancestor class changes at some point post the mixin, it would only be reflected in some class descendents, where the ancestor happened to be in the first "slot".

I suspect the TypeScript core team would need to have a compelling reason to break compatibility with ES6.

Also what would you propose to emit if targeting ES6 with TypeScript?

You are also avoiding the challenges that come with multiple inheritance, like the Diamond Problem and what sort of mitigation strategy should be used. There are many and in my opinion most of them cause surprises and confusion. Multiple inheritance is a can of worms, which is likely why ECMAScript has even avoided considering it.

mhegazy commented 8 years ago

Also related: https://github.com/Microsoft/TypeScript/issues/311 and https://github.com/Microsoft/TypeScript/issues/2919