Closed disshishkov closed 7 years ago
Do you have other solutions?
I would love to see this added to TS too
+1 for code generation purposes (DTOs from a WCF service reference with additional partial class definitions)
+1 for code generation purposes. My use case is: I have React components whose render()
functions are generated externally via react-templates.
With partial
classes I could type-check such functions versus the main class (the Component
). Without partial classes, the only thing I can do is binding the function to the main class and hope for the best.
With next TS 2.0, I was thinking that some code-generation cases might be covered with the this
type feature.
For example in the case I described previously, instead of
partial class MyComponent extends React.Component<any,any> {
render() {
...
}
}
I can write
function render<this extends MyComponent>()
...
}
MyComponent.prototype.render = render;
+1 As a developer I want partial classes so that multiple templates can generate/modify the same class spread out over multiple files.
My comments were specifically in regard to partial classes, not to mixins. The two are fundamentally different. Partial classes are about breaking up physical code, mixins are about breaking up logical behavior into reusable traits enriching other objects at both the type and the value level. I do not think think partial classes are a good idea for TypeScript. Mixins, on the other hand are a good idea, providing increased expressiveness and meshing extremely well with both TypeScript's type system and JavaScript idioms as pointed out by @aleksey-bykov
@wendellm can you think of a clean way to do this given that ES Modules are physical. Partial classes work well and make a lot of sense in a language like C# where modules are logical not physical. From the point of view of the CLR, namespaces don't exist, it's just that class names can have dots in them (source an interview with @ahejlsberg)
+1 I need partial class!
can we extension interface? interface can also have some implemented functions, just like Swift:
interface Rect {
x: number
y: number
}
extension Rect {
area() => this.x * this.y
}
Swift version:
protocol Rect {
var x: Float {get}
var y: Float {get}
}
extension Rect {
func area() -> Float {
return self.x * self.y
}
}
+1
Code generation and partial classes go hand in hand.
Inheritance is a poor solution to what is essentially a glorified #include
directive.
Take Angular 2 for example. It will not read metadata from the parent class, making extension-by-inheritance impractical.
@tsvetomir that's an Angular issue not a TypeScript issue.
Inheritance is a poor solution to what is essentially a glorified #include directive
Yes inheritance is a poor solution, but the problem is using classes in the first place. They have their place but it is very limited. JavaScript classes are pretty weak and inexpressive compared to classes in other languages.
Typescript is not an"Angular" language... Never was
On Jul 23, 2016, at 9:46 PM, Aluan Haddad notifications@github.com<mailto:notifications@github.com> wrote:
@tsvetomirhttps://github.com/tsvetomir that's an Angular issue not a TypeScript issue.
Inheritance is a poor solution to what is essentially a glorified #include directive
Yes inheritance is a poor solution, but the problem is using classes in the first place. They have their place but it is very limited. JavaScript classes are pretty weak and inexpressive compared to classes in other languages.
You are receiving this because you commented. Reply to this email directly, view it on GitHubhttps://github.com/Microsoft/TypeScript/issues/563#issuecomment-234753589, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AJPCIh7n2_0dt00kw-XJv7tc9LB0tPsIks5qYtH4gaJpZM4CcixK.
I don't see the issue as Angular specific in any way. It is a particular case in which partial classes may have helped.
The request originates from past experience with C# and the lack of familiar features. The comparison is inevitable as TypeScript makes no effort to distance itself from .NET. Some form of feature parity is expected by its users.
The request originates from past experience with C# and the lack of familiar features. The comparison is inevitable as TypeScript makes no effort to distance itself from .NET. Some form of feature parity is expected by its users.
@tsvetomir as a long time .NET developer, and as a passionate C# programmer, I fundamentally disagree.
The article you cited is not authoritative, and does not reflect any official material from the TypeScript team.
Furthermore, it directly contradicts the TypeScript design philosophy as stated by @ahejlsberg in numerous talks, interviews, and posts.
Additionally, the cited article's viewpoint reflects a fundamental misunderstanding as to the nature of TypeScript. Ironically, the commonalities that C# does have with TypeScript are actually the same ones it has with JavaScript. These commonalities are not class-based programming, but rather constructs such as closures and higher order functions.
C# or not, we need partial classes which will help TypeScript getting closer to Javascript's prototype features.
@yahiko00 TypeScript already has all of JavaScript's prototype features. It is a superset.
There are several problems with the notion of partial classes in TypeScript, including
The ECMAScript module system is based on importing physical not logical code units. For example, lets say I have
app/my-class-part-1.ts
export partial class MyClass {
firstName = "John";
lastName = "Smith";
}
and app/my-class-part-2.ts
export partial class MyClass {
fullName = this.firstName + ' ' + this.lastName;
}
How can I import this class? Since I cannot import from either module, let us imagine a hypothetical abstraction in TypeScript that allows me to write app/main.ts
import { MyClass } from './my-class';
What would this mean? Even if the TypeScript compiler could infer that app/my-class-part-1.ts has to be loaded before app/my-class-part-2.ts, it cannot make the importing of the individual parts invalid nor can it ensure they are imported in the correct order by an asynchronous module loader such as RequireJS or what will eventually be implemented in browsers.
The whole notion of partial classes is fundamentally at odds with the ECMAScript module system.
Update: It would need to perform arbitrarily complex dependency inference and emit some very odd JavaScript.
TypeScript already has all of JavaScript's prototype features. It is a superset.
I should have expressed myself better. Of course we all know TypeScript is a superset of JavaScript. But I wanted to say that partial classes would help TypeScript classes to get closer to JS prototypes. For the moment, we can just have "static" and non extendable classes, whereas, with a prototype approach, we can extend class-like functions.
Importing partial classes would lead to extends the current ES6 syntax I agree. We could imagine something like this:
import { MyClass } from ['app/my-class-part-1', 'app/my-class-part-2'];
TypeScript classes are based on ECMAScript classes and thusly suffer from the latter's lack of expressiveness. Consider using functions, modules, "namespaces", or plain objects to accomplish what you need.
Functions is what we want to avoid with the notion of class. Modules or namespaces bring simple extensibility and have not the same expresiveness as TypeScript's classes. Also, a namespace or a module cannot be instanciated. They do not serve the same purpose as classes.
@aluanhaddad I see what you mean regarding modules. A possible solution would be to allow only one of the files to export partial class
while the rest contain only partial class
definitions. You import it from the place its exported. From your example:
app/my-class.ts
export partial class MyClass {
firstName = "John";
lastName = "Smith";
}
app/my-class.part.ts
partial class MyClass {
get fullName(): string {
return this.firstName + ' ' + this.lastName;
}
}
app/main.ts
import { MyClass } from './my-class';
What's missing is a syntax to let the compiler know where to locate the parts. This is not an issue with C# where you process all files at once. This might be a special syntax, as the one used for declaration merging.
It's not required for partial classes to allow arbitrary definitions. They can be limited only to methods, properties and constructors, for example. This makes execution order insignificant and allows the compiler to throw an error if a member is duplicated.
I fail to see why Module Augmentation is not valid for these scenarios. It's worked very well for us with Rxjs5.
You simply create your base class, and then with your code generation, generate all the augmented methods you need on top of the base class. It doesn't matter if the generated code is pretty, no humans are supposed to write it.
Is it a perfect replacement for partial classes? I suppose not, but I don't see partial classes happening unless ECMAScript decides JavaScript needs them. The semantics for how partial classes are linked together between external modules alone hurts my head.
@david-driscoll to make it even better, added methods can have a this:
argument declared, for a stronger type-checking.
In the cited example of Module Augmentation:
// observable.ts stays the same
// map.ts
import { Observable } from "./observable";
declare module "./observable" {
interface Observable<T> {
map<U>(f: (x: T) => U): Observable<U>;
}
}
Observable.prototype.map = function (this: Observable, f) {
// here "this" has the shape of the "Observable" class
}
Yeah. Rxjs doesn't quiet use it yet, because it's a 2.0 feature, and 2.0 hasn't been released yet. It's on my hit list later.
I see what you mean regarding modules. A possible solution would be to allow only one of the files to export partial class while the rest contain only partial class definitions. You import it from the place its exported. From your example:
app/my-class.ts
export partial class MyClass { firstName = "John"; lastName = "Smith"; }
app/my-class.part.ts
partial class MyClass { get fullName(): string { return this.firstName + ' ' + this.lastName; } }
I like the syntactic concept but the problem is that you have just introduced a new kind of source file that is consumed as a sort of implicit module, looks like a script, and has to be loaded in a specific manner.
What's missing is a syntax to let the compiler know where to locate the parts. This is not an issue with C# where you process all files at once. This might be a special syntax, as the one used for declaration merging.
Declaration merging works for declarations, not source files.
It's not required for partial classes to allow arbitrary definitions. They can be limited only to methods, properties and constructors, for example. This makes execution order insignificant and allows the compiler to throw an error if a member is duplicated.
All of these are commonly order dependent.
Edit: That is to say that defining them modifies the class.
Probably someone mentioned this already, but another useful case is when you generate part of the class from some code generator, then you wish to manually add some more code directly in your implementation. You don't really wish to do mixing or derived class. It's still one class - just part is generated automatically other part manually.
BTW: if someone needs to generate TS types from C# classes you can check TypeScriptBuilder
Thanks for considering this!
I'm going to try out your TypeScriptBuilder ;)
My suggestion...
What if the compiler didn't try to find all parts? What if I had to import other parts and controlled everything?
/MyClass.partial.ts
export partial class MyClass {
firstName = "John";
lastName = "Smith";
}
MyClass.ts
// v- this would NOT throw an error because the definition is marked as partial
import { MyClass } from "./MyClass.partial";
export class MyClass {
get fullName(): string {
return this.firstName + ' ' + this.lastName;
}
}
Then...
MyClass
I have to import it from MyClass.ts
MyClass.partial.ts
will generate a javascript which extends MyClass.prototype
like showed here before. But it exports a function which receives the prototype to be extended.MyClass.partial.ts
is imported in MyClass
after MyClass
has been defined and it's extend function is called.On a side note... there's nothing preventing me from generating the compiled code directly. But I loose all the greatness of Typescript.
@svallory I think that's definitely the right approach here. Specifically because you say the generated code will export a function which would cause the augmentation, it would work even with asynchronous imports because the functions can be called in the necessary deterministic order.
More generally, and I forget the issue where it was referenced, if assignments to the class prototype affected the shape of the class, there could be many benefits. This would improve mixin libraries, make decorators far more useful, and generally encourage less vertical hierarchies.
It could be tricky for the compiler to track all of this, but it probably could be made to work by being explicit as you suggest.
It would be an enhancement to the type system, with the partial classes use case falling out of that enhancement.
I've did it like the following:
I've did it like the following: file1.ts interface ifoo{ a():void; } class foo implements ifoo{ a() { /*do something*/ } } file2.ts /// <reference path="file1.ts" /> //not necessary interface ifoo{ b():void; } foo.prototype.b =() => { /*do something*/ } file3.ts /// <reference path="file1.ts" /> //not necessary interface ifoo{ c():void; } (<ifoo>foo.prototype).c =() => { /*do something*/ } consumer.ts /// <reference path="file1.ts" /> /// <reference path="file2.ts" /> /// <reference path="file3.ts" /> let f = new foo(); f.a(); f.b(); f.c();
Module Augmentation still solves 90% of the problem here. Given @svallory's examples.
/MyClass.partial.ts
export partial class MyClass {
firstName = "John";
lastName = "Smith";
}
MyClass.ts
// v- this would NOT throw an error because the definition is marked as partial
import { MyClass } from "./MyClass.partial";
export class MyClass {
get fullName(): string {
return this.firstName + ' ' + this.lastName;
}
}
The module augmentation would be something like...
MyClass.ts
export class MyClass {
firstName = 'John';
lastName = 'Smith';
}
MyClass.generated.ts
import { MyClass } from './test';
Object.defineProperty(MyClass.prototype, "fullName", {
get(this:MyClass) {
return this.firstName + ' ' + this.lastName;
}
});
declare module './test' {
interface MyClass {
readonly fullName: string;
}
}
It's a little more verbose than a partial class, but really the partial class would just be syntactic sugar for this implementation.
The only gotchas today you don't have would be:
this: MyClass
syntax.
any
.this
. There may be other rules around that.EDIT: Keep in mind, this is TS 2.0+ syntax.
@david-driscoll I agree. I think that is simpler and I like that it doesn't introduce the additional syntax. As you know, I am opposed to partial classes as a specific language feature, but I do think it would be beneficial if TypeScript tracked assignments to prototypes in general and refined the shape of the object accordingly. I think your approach is the right way to split classes across multiple files.
@wongchichong Your example uses global class names and /// <reference path="..."/>
. I don't think that will scale well, although you could use a namespace to improve the situation.
Code written as modules (that is external modules) is a very different beast because it involves a loader which needs to be aware of dependencies which are order dependent and implicit. That is why @svallory's suggestion is valuable, it requires a very minimal amount of manual wiring which makes the dependencies explicit.
What if someone does not use modules?
@pankleks partial classes are still a bad idea.
To clarify, I mean they are bad idea in language like JavaScript where class declarations are imperative, not declarative. Even if you're using namespaces, you're going to end up breaking your code into multiple files which means you to have to depend on the script tag order of those files for this to work at all.
In a language like C#, partial classes work well but that's because they're fundamentally different from classes in JavaScript.
It's mportant remember that in JavaScript, class declarations are not even hoisted let alone declarative.
Barring ES6, the "class" concept is defined by TS. JS has no notion of a class at all. So its up to TypeScript to decide what a class can be, no?
Barring ES6,
We cannot bar ES6 when discussing TS language features, because one of the TS goals is to follow current/future ES spec.
True that :/
For large systems with code generated classes, i.e. Entities, service layer clients etc, partial classes as per the C# design, provide a very elegant solution when generated code needs to be extended with additional state and behavior. On my present project, where we have ~500 code gen classes, I miss this feature dearly.
I still don't understand why some people are so hostile against a feature that has been proven useful elsewhere, except saying "it is a bad idea".
It seems this hasn't been discussed much on esdiscuss, the only thread I found has 9 posts.
@yahiko00 It is not that I am hostile to the concept. As you say it is very useful in languages such as C#.
As an enthusiastic C# programmer, I find partial classes to be very helpful for certain tasks. However, they do not interfere with the other aspects of the language. They do not break key related abstractions such as namespaces, assemblies, and arbitrarily orderable type declarations.
JavaScript, on the other hand, has a module system that is nascent at best. Classes are a new and, I would argue, as yet not very expressive feature of the language, they need time to grow.
If we take a step back for a moment and consider extension methods, another powerful, and far more useful feature of C#, I would love to see them added to JavaScript but there are fundamental problems with doing so that have yet to be resolved.
When we reach a point where partial classes can be specified without breaking or severely constraining far more fundamental concepts such as modules, I will be all in favor of adding them.
That said, as @SaschaNaz points out, this does need to be addressed in ECMAScript.
Maybe anyone interested can open a new thread on http://esdiscuss.org and post the URL here so that we can continue discussion there 😄
PS: Or on more-GitHub-like ES Discourse. PS2: Or on also-GitHub-like-and-more-active WICG.
I too would love to see some construct that allows for code generation for TS. Partial classes work great for this in C#.
Maybe I am missing something, but it seems to me that the core issue is extending a class with access to 'private' variables from somewhere other than in the constructor, preferably from another file.
Based on the discussion it seems like much of the 'safe' TS innovation has been done. Now we are just waiting to see what the ES governance body does because we don't want to break anything going forward. Totally valid, but a little disheartening because I was hoping for a faster moving train with TS.
A prerequisite is finding a good desugaring:
Although I previously thought partial classes were pretty silly because you could just add to the class directly, I can see the appeal given that doing so will involve making your methods non-enumerable plus fixing the super binding (the latter of which is currently impossible in ES6).
We first need to figure out a good desguaring for classes in general into compositional, imperative primitives, like the old toMethod. But once we have that, adding some more sugar on top like partial classes might be reasonable, depending on how ergonomic---or not---those primitives end up being.
Not sure what this exactly means but probably like this:
class A {
foo() { return "foo" }
}
class B extends A {
}
partial(B, class {
get foo() {
return super.foo(); // should work
}
});
(Cross posted on esdiscuss)
It seems there is a fairly simple way to implement partial class in pure JS (with ES2017 getOwnPropertyDescriptors). ES people may think a syntax sugar will even not needed only to remove this tiny code.
function partial(base, extension) {
 extension.prototype.__proto__ = base.prototype.__proto__; // to enable 'super' reference
 const descriptors = Object.getOwnPropertyDescriptors(extension.prototype);
 delete descriptors.constructor; // must not override constructor
 Object.defineProperties(base.prototype, descriptors);
Â
  return base;
}
Using this on TS requires an interface
duplicate to extend existing class type, of course.
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.
will be: