microsoft / TypeScript

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

Partial classes #563

Closed disshishkov closed 7 years ago

disshishkov commented 10 years ago

Add support of partial classes. Mixins is not the same, because it's run-time realization. Need compile realization, where partial classes will be combine into one before converting typescript to javascript.

//FileA.ts
partial class ClassA
{      
    constructor(public name: string) {}
    public sayHello(): string { return "hi!"; }
}

//FileB.ts
partial class ClassA
{
   public sayBye(): string { return "by!"; }
}

will be:

partial class ClassA
{      
    constructor(public name: string) {}
    public sayHello(): string { return "hi!"; }
    public sayBye(): string { return "by!"; }
}
LeandroBarral commented 9 years ago

+1

iKBAHT commented 9 years ago

+1

TonyValenti commented 8 years ago

+1 for code generation. I have been getting into angularjs and webapi and I want to make a tool that essentially creates automatic JS definitions from my c# objects and services. I want to then be able to extend these classes without having to edit the "scaffolded" JS definitions. I've see that a few other people are requesting this and it seems like we have similar use cases.

s-KaiNet commented 8 years ago

+1 for code generation

dziedrius commented 8 years ago

+1 for code generation, angular has lots of boilerplate code, we're already doing lots of generation of it, but with partial classes we would be able to do much more.

GrantErickson commented 8 years ago

+1 yet again for code generation. Trying to extend classes in another file is messy at best.

RandScullard commented 8 years ago

@RyanCavanaugh, what is needed to get this out of "+1" mode and moving in a productive direction?

falsandtru commented 8 years ago

-1 a following syntax is better for mixins: #2919

ghost commented 8 years ago

Not sure why can't we have both partial classes and the mixins; two totally unrelated features.

Vaccano commented 8 years ago

+1

This would be nice when code is generated, but you don't want to modify it to add things to the generated code.

shivajiraot commented 8 years ago

+1

andy-hanson commented 8 years ago

I have a proposal that should satisfy both of these use cases:

  1. You want to write methods of your own class in different places.
  2. You want to add new methods to someone else's class, e.g. a builtin.

For (2), normally you would just use the :: operator to get infix syntax.

But if you want an old class to be part of a new interface, or if you want to dynamically dispatch, you will need to actually modify the prototype.

interface ITimes {
    times(n: number): number
}

Array implements ITimes {
    times(n: number): number {
        return this.length * n
    }
}

// The compiler should check that all methods of ITimes and IOther are implemented.
Number implements ITimes, IOther {
    times(n: number): number {
        return this * n
    }

    other(): void {}
}

// The old types should typecheck with the new interface.
const x: ITimes = true ? [] : 0
x.times(3)

// The interface list can be empty. This essentially gives you partial classes.
MyClass implements {
    anotherMethod(): void {}
}

(In the example I use string method names, but since these are builtin types, using symbols would be better once those can be typechecked.)

An improvement that this has over true partial classes is that the class has a single definition, and other places merely extend it. This means there's a clear place to import the class from and makes translation to JS easier.

ludydoo commented 8 years ago

:+1: I would love to see partial classes too.

What is the status of this feature?

This use case for example :

I have a Math package that define a class Set, with methods Set#add, 'Set#remove'

// math.ts
export partial class Set {
  add(){}
  remove(){}
}

I have an optional, heavy, Relational package that define classes Relation and Tuple.

This Relational package would also add a Set#product method.

// relational.ts
///<reference path="./../math/tsd.d.ts"/>
partial class Set {
  product(){}
}

export class Relation(){}
export class Tuple(){}

Partial classes would allow me to compose classes in a more flexible way. If I don't want to use the heavy relational package, I can still use the basic Set functionality. Other partial classes will only come in and add extra functionality to the class.

I'm scratching my head to do something that will have a nice API without the use of partials in this case.

All I could come with is some repository pattern.

// Without partials

// base

export class Base {
    static registerOperation(opName, method){
        this.prototype[opName] = method;
    }
    operation(name, ...args){
        return this[name].apply(this, args);
    }
}

// math.ts
import {Base} from './base';
class Set extends Base{
    // Define the methods here
    add(){}
    remove(){}
}

// Or here
Set.registerOperation("add", function(...){});
Set.registerOperation("remove", function(...){});

// relational.ts
import {Set} from './../math/math';

Set.registerOperation("product", function(...){});

// app.ts

import {Set} from 'math';

var set = new Set();
set.add // compiler error
set.remove // compiler error
set.product // compiler error

// have to use something like
set.operation("add", args);
// or
(<any>set).add(arg);
mehrandvd commented 8 years ago

+1 I'm using t4 to generate classes and proxies over WebApi. I think auto-generation is using vastly in typescript and when it comes to auto-generation, partial classes is necessary!

Artazor commented 8 years ago

It would be great for splitting aspects of the same class especially in React +1

Elephant-Vessel commented 8 years ago

+1 Code generation and "Just enough separation" in the case of pure reusable data objects with client-specific attributes for like validation that would be neat to have defined on the class but maintained in a separate piece of source code.

areijngoudt commented 8 years ago

+1 for Code generation of partial classes and handcode additional members for these classes in separate files.

a8775 commented 8 years ago

+1

iBasit commented 8 years ago

+1 I have large base of methods in our js api service, it would be best to split them into files, which makes it easy to understand each method.

api service like fb (facebook) or gq (google analytics), where you are given one global class or object, which you can use through out your development.

chinookproject commented 8 years ago

+1

RenaudGelai commented 8 years ago

This feature would be a big plus for us too.

We developped a middleware where all types of clients can connect to, and communicate with our servers through it.

We generate some of the client code based on what is exposed by the servers.

So far, everything was ok. But now, we'd like to be able to transfer heterogeneous collections of objects (but with a same base class). The objects transfered are part of the code generated based on the server API.

To be able to use the power of inheritance, abstract methods are the key in this case. But our developpers won't add methods to generated code, i presume everyone knows why ;)

As far as i understood (i'm a C# developper), the solution proposed here (https://github.com/Microsoft/TypeScript/issues/9) would not allow us to do that.

So, partial classes would be perfect for us. We would generate the class as partial, and if developpers need to add logic or methods, they would just have to write an other part wherever they want.

wupaz commented 8 years ago

+1

scosc commented 8 years ago

+1 very helpful for code generation

david-driscoll commented 8 years ago

I wonder if module augmentation basically negates the need for this as a specific feature.

If you have a generated class then you should be able to do something like...

import {MyClass} from "./MyClass.generated"

MyClass.prototype.partialMethod1 = function() {
  return true;
}
MyClass.prototype.partialMethod2 = function(abc: string) {
  this.doSomething(abc);
}

declare module './MyClass.generated' {
  interface MyClass {
    partialMethod1(): boolean;
    partialMethod2(abc: string): void;
  }
}
Elephant-Vessel commented 8 years ago

@disshishkov That looks like a good way to implement it under the cover, but I'd still like proper first-class support in the language without the need to manually modify prototypes and maintaining that interface.

areijngoudt commented 8 years ago

I agree with Elephant-Vessel. The manually crafted part of the partial class should be as loosly coupled (at design time in TS) as possible form the generated part.

GrantErickson commented 8 years ago

The suggestion by @david-driscoll works for many cases. However, as far as I can tell, prototypes don't have access to internal variables. This limits the usefulness of this approach in code generation contexts where you want to generate the core parts of your classes and then augment them with custom code.

Vasim-DigitalNexus commented 8 years ago

+1

spion commented 8 years ago

@david-driscoll that looks like an excellent way to desugar this feature. (would need to add the ability to access to private/protected properties). Wonder what would happen with multiple partial class declaration? What would the mutual visibility of those be?

chrizy commented 8 years ago

++ 1 Lack of partial is really hurting me when trying to extend my auto generated models..

RyanCavanaugh commented 8 years ago

I think the only thing in scope here is basically what @david-driscoll proposed -- you could declare new methods (which would be put on the prototype) and new non-initialized properties (which have no codegen), but not new initialized properties (because these have side effects on the constructor codegen).

zpdDG4gta8XKpMCd commented 8 years ago

downvoted, pardon my french, partial classes are a stupid attempt to chunk your god classes, which are inevitable in OOP, into multiple files so that they are easier to manage (although still being god classes)

with OOP there is no future for you people (been there, know what i am talking about)

come to FP, we have cookies and a way to write a program without a single class and s**t like this

RyanCavanaugh commented 8 years ago

So we already basically allow this anyway through interface A { method(): void } A.prototype.method = function() { };; codifying this into a sugar for exactly that makes sense.

One thing to bikeshed is the keyword partial. A brief historical vignette: In C#, partial class was originally going to be extension class (which you put on all but one declaration), but there wasn't a clear distinction about which declarations would be the extension and which declaration would be the non-extension declaration. Instead you have the modifier partial that you must place on all classes.

This is not the case in TypeScript; rather the opposite. Here, only one class (let's call it the "primary declaration") may have constructors / initialized member fields; the primary declaration doesn't get any new modifier. Every other declaration (let's call them "extension declarations") may only have statics, methods, and non-initialized fields, and you'll need to have some modifier on them to indicate that they're not primary.

The current best guess is

class Foo {
  x = 6; // only legal to initialize here
  constructor() { } // only legal to have constructor here
  someMethod() { }
}

// vvvvvvvvv thoughts?
   extension class Foo {
     someOtherMethod() {
     }
   }

:sparkles: :bike: :house: :sparkles:

kitsonk commented 8 years ago

Bikeshedding or not, this is sounding an awful like #311 which was walked away from because of likely interfering with future ECMAScript standards. Why is this worth considering, but proper mixin support isn't?

Elephant-Vessel commented 8 years ago

Using the keyword extension conflicts with extension methods? Or is this going to negate extension methods completely?

RyanCavanaugh commented 8 years ago

@kitsonk I think the mixin proposal has a lot more open questions. The partial class proposal here is just a codification of things that are already allowed in TypeScript, just with some syntactic sugar (this feature could literally be done as a syntactic remapping to things we already have).

@Elephant-Vessel I don't think extension methods are happening if partial classes happen. Classes are the only place where extension methods make sense given our constraints, and the proposed ES7+ bind operator is a better way to shoehorn in extension-like syntax.

jfrank14 commented 8 years ago

@RyanCavanaugh: to me, it seems a bit constraining to require that one class is primary and the others not. If you want to partition your code so that one file contains all the methods of a class and the other contains all the properties, neither seems more obviously primary, and why should you be forced to choose? Isn't it cleaner to have partials where they're all equal in importance, and merged by the compiler?

giancarloa commented 8 years ago

@RyanCavanaugh ... I would do it exactly the same way c# does it... it works great for code generation... which i believe was the primary problem the feature meant to solve in c#... although my memory is fuzzy... never had any issues, it's been around for years, and is battle tested...

Elephant-Vessel commented 8 years ago

If partial/extended classes won't be able to have initialized fields, I think it would be extra neat to support something like partial methods that we know from C#, basically a lightweight contract specific for partial classes. So like:

public partial class MyGeneratedClass
 {
     partial void Initialize();
     public constructor()
     {
          //...
         this.Initialize();
    }
 }

 public partial class MyGeneratedClass
 {
     partial void Initialize()  { //... }
 }

The need to be able to partialize/extend third-party classes could be quite easily(?) solved by making the partial/extends generally optional but required for use of partial methods.

I think that this could bring some neat expressive and operative power to the concept that we want here.

zpdDG4gta8XKpMCd commented 8 years ago

Those who's looking for mixins.

Consider the object initializes proposal by means of which (and flow analysis) the mixins can be done elegantly, naturally, idiomatically right as far as JavaScript goes:

function enableBeingPositioned<a>(
   // hypothetical syntax
   something: a /* <-- before type */ => a & { x: number; y: number; } /* <-- after type */
): void { 
   something.x = 0;
   something.y = 0;
}
let value = {};
value.x; // <-- should not typecheck
enableBeingPositioned(value);
value.x; // <-- should typecheck
ghost commented 8 years ago

Languages such as Java don't offer partial classes/structs. It is a niche feature of C#. Imitating C# "partial class/struct" concepts to full extent is the best way to go here. In pre-compilation step, the first thing the TS compiler could do is to stitch the partial code blocks in memory and then move on to the regular compilation pipeline route. This will be more than enough to qualify for v1.0.0-preview1.0000 of Partial Classes feature in TypeScript. Anything beyond what C# is offering is something that can be introduced in later versions when the feature will evolve and outlive its trial/preview. The corner cases and the similar kind of obscure stuff can be discussed separately one at a time and we can worry about the fit&finish later... IMO.

hdachev commented 8 years ago

I also agree doing both partial and extension classes exactly the way c# does it is the best way to go in terms of syntax and semantics, both work extremely well for their respective use cases. Partial classes improve the usability of generated code. Extension methods improve the usability of static methods that operate on interfaces, specified generics and third party types. Both are really cool, but please remember they are not the same feature, I'm saying this here because of the proposed syntax above.

Extension methods would allow one to do this (sry about lack of formatting, typing on a phone):

extend MyType[] { somethingSpecificToMyType() { ... } }

extend WeirdThirdPartyDatastructure { sameHere() { ... } }

That among other things like being able to do use libs like underscore without the cruft, which basically means reaping the syntactic benefits of extending built-in types without actually poluting them.

So brief - lets not treat the two as one and the same, they are both very cool but not the same thing. I'd personally love to have both in our language.

hdachev commented 8 years ago

By the way, as a side note, I imagine many people cringe at the idea of code generation and are ready to explain to everyone that it has no place in javascript because there are more idiomatic ways to achieve the same. There are two ways in which code generation is of great help in some projects, such as the one I'm working on right now.

As another side node for the benefit of anyone wondering what partial classes have to do with codegen, the value of keeping the generated parts of a class into a separate file comes from source control requirements - the generated stuff is usually a rapidly changing build artifact and you don't want it committed in your repo.

Elephant-Vessel commented 8 years ago

I like the idea of implementing this accordingly to a well known and tried model of this concept, just like what C# has. 'Imitation' has some negative connotations to it, but it has significant value if done intelligently. On the other hand, the 'intelligent'-part of that is to be aware of different circumstances and limitations, so I won't protest if partials for TypeScript does not exactly look like partials for C#.

ghost commented 8 years ago

@Elephant-Vessel, I agree that it needn't be a ditto copy of C# partials, the general design can be laid out first crafted around TypeScript / JavaScript flavor while taking maximum inspiration from C# partials. My suggestion is to go with the 'registering partial keyword' and 'stitching partials' as a first step, and ship it as an experimental/preview feature (so consumer don't start depending on it in production code right off the bat). Later, based on the community response, evolve the feature until it is prod-ready and RTM'd. If we worry about all kinds of tricky gotchas and scenarios before hand, then most probably it will delay the matter further.

rsparrow commented 8 years ago

+1 - For extending and maintaining generated objects.

franferns commented 8 years ago

+1 for maintaining generated code

falsandtru commented 8 years ago

I created polyfill and divided my large class.

Define a class as an interface.

export class C {
  constructor() {
  }
}
export interface C {
  m(): void;
}

Implement class members.

export default class extends C {
  m(): void {
  }
}

Merge implementations.

import {C} from './core';
import m from './member/m';

compose(C, m);
export {C}
import {assign} from './assign';
import {concat} from './concat';

export function compose<T extends new (...args: any[]) => any>(target: T, ...sources: T[]): T {
  return concat([target], sources)
    .reduce((b, d) => {
      void assign(b.prototype, d.prototype);
      for (const p in d) if (d.hasOwnProperty(p)) b[p] = d[p];
      return b;
    });
}

https://github.com/falsandtru/spica/commit/a6ff30da5319db5f25f703a29da48fc0f7dbe2fe

aluanhaddad commented 8 years ago

I think this is a terrible idea for one specific reason: it will make the already complicated rules for global, ambient, external, namespaced, and brittle declaration order dependent inheritance problems significantly worse. This is not C# and name resolution and member declarations is very different.

Perhaps use an object or namespace instead of a class, a la the revealing module pattern if you really need to do this. Otherwise your class is probably just too large.

aluanhaddad commented 8 years ago

Also decorators can be used to implement associations between generated code and hand written code.