-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Suggestion: callable class #183
Comments
+1 For this, as most libraries use this convention to omit the new operator I had this in mind static class Foo {
constructor() {
// constructor
}
} and the output would be function Foo() {
if(!(this instanceof Foo)) return new Foo();
// constructor
} |
Vote, could be useful. |
Interesting ability, but I believe this can lead to bad design and confusing API. I like it how it is now, that I have to use the |
and I hate those libraries. |
+Needs Proposal (see https://github.com/Microsoft/TypeScript/wiki/Writing-Good-Design-Proposals) This might be right out due to not aligning with ES6 class syntax. We'd rather not add any more to that than we have to. A potential problem is that you would probably want to be able to call |
Note: comments from Anders on #619 |
I'm not sure I agree. How that is handled is up to the developer. When I call "super()" I always expect to call a base function (always, as would normally happen in most JS implementations). There are ways to work around it, like moving the initialization to another method, or implementing the "instanceof" checks that some have mentioned. I don't see this changing at all. One of the main reasons for this is for object pools for game engines. If there's an available object in such an engine, I never call "super()" (which may create sub objects - and if I did need to, I should be able to just do "super.call(newObj, ....)"), but instead I would call 'oldObj.Reset()' (Construct 2 does something similar). If there isn't any new objects, then I'd simply return a "new ThisType()", which then would call "super()" as normal (as expected). |
+1 for this feature
The shortcut for a new object is not the best example actually. Usually in JS, applying 'call' on a constructor means the 'coerce' operator. In my opinion this is a stronger usecase : class MyCallableClass {
property: string;
constructor() { }
(o: any): MyCallableClass {
if (o instanceof MyCallableClass) return o
if ('toMyCallableClass' in o) return o.toMyCallableClass()
return new MyCallableClass(o);//or throw or return null
}
} |
Just a note: This is being discussed for ES7 - https://gist.github.com/ericelliott/1c6f451b2ed1b634c2f2#file-es7-new-fix-js |
I'm currently missing something like this, but not particularly for a
I haven't found any way to implement this interface in TypeScript without resorting to some hefty |
Just a note 2: ES7 Stage 1 proposal https://github.com/tc39/ecma262/blob/master/workingdocs/callconstructor.md |
I have a concrete problem with https://github.com/peerlibrary/meteor-reactive-field library which allows me to write code like this:
Im trying to write a declaration file for it, which would make complier happy.
but it fails with error messages like:
whenever I try to call |
You could force cast it to a call signature. Something like |
@qbolec in the future, StackOverrflow is a better venue, but what we usually do is separate the instance/static sides into their own interfaces. // Shape of the constructor function.
interface ReactiveFieldStatic {
new <T>(value: T, equals: (a: T, b: T) => boolean): ReactiveField<T>;
}
// Shape of the instance.
interface ReactiveField<T> {
(value?: T): T;
}
// The constructor function itself.
var ReactiveField: ReactiveFieldStatic; |
For the record, callable classes are now an inactive proposal. Check out portions of the discussion here. |
Thank you @DanielRosenwasser - it is very clever solution. I had my mind fixed on the bad assumption that "a var cannot be polymorphic", and totally failed to notice that it is not "var ReactiveField" that has to be polymorphic, but rather the "new" operator. Great idea! |
So the transcript of the discussion claims decorators can solve making classes callable. I'm looking into that and I fail to see how that could work - any examples? |
@rjamesnw With TypeScript's current implementation of decorators, I believe the following should work to make a class both function callable(ctor) {
// Constructs 'ctor' when called *and* when constructed!
return function (...args) {
new ctor(...args);
};
}
@callable
class C {
constructor(...args) {
console.log("Being constructed with: " + JSON.stringify(args));
}
}
C(1, 2, 3, 4);
new C(5, 6, 7, 8); Which gives
However, right now TypeScript can't communicate that both types are callable and constructable. You can also differentiate the behavior by using // Thing that gets passed a function and makes a decorator:
function callableWith(call: (...args: any[]) => any) {
// Thing that actually decorates the class:
return function decorator(construct: new (...args: any[]) => any) {
// Thing that actually replaces the class:
return function replacement (...args) {
if (new.target) {
return new construct(...args);
}
else {
return call(...args);
}
}
}
}
@callableWith((...args) => {
console.warn("Hey, you probably want to use 'new C(...)' instead of 'C(...)'!")
return new C(...args);
})
class C {
constructor(...args) {
console.log("Being constructed with: " + JSON.stringify(args));
}
}
C(1, 2, 3, 4);
new C(5, 6, 7, 8); which gives
|
Some points:
Thanks. Edit: For # 2 it appears VS 2017 is "stuck" at v2.1.5 (https://blogs.msdn.microsoft.com/typescript/2017/03/27/typescripts-new-release-cadence/). Perhaps as well, the Playground is also stuck at an older version, or the option is not enabled. Side note: Regarding the main page: "TypeScript 2.2 is now available. Download our latest version today!" |
@rjamesnw You can try VS2017.2 Preview which includes TS2.2 😄 https://www.visualstudio.com/en-us/news/releasenotes/vs2017-preview-relnotes |
Thanks, installed the preview. Turns out with v2.2 you can also use type intersections for this (was an issue in previous versions, as you couldn't represent both new and non-new signatures well across two different interfaces). At least now I can have an interface from a class, and merge it with a callable signature from another interface, and it will work now. The examples above however still don't work. |
@DanielRosenwasser For the record, |
I mentioned this in another post, but thought I'd put it here also in case someone else comes here. ES6 introduces "specialized functions", and thus any direction towards a callable constructor is a bad idea. Targeting future ES versions will output using the "class" syntax, which will fail if called directly; For example, in Chrome, it generates the error |
May I suggest instead, since callable constructors are not supported, that we can at least compromise with allowing the "new" modifier to allow forcing new static function signatures on derived types? Static functions could be used as factories (such as 'SomeType.create()') and accept different parameters based on type. |
@DanielRosenwasser How might your decorator work for generic classes? For example, I have this case: abstract class Try<T> {
}
class Success<T> extends Try<T> {
constructor(public readonly value: T) {
super();
}
}
// How might I construct `Success<string>` without calling `new`?
const success = Success('successful'); |
Would it be possible to have call signatures just for ambient class declarations? It'd make it possible to accurately model some JavaScript libraries. Using two interfaces gets close but you lose type narrowing on |
bump ... any news? :) |
Nope! |
It's not possible to have callable classes. It is not supported in the spec. Calling a class will cause errors (try it yourself). For this reason it makes sense that this will never be supported. That said, I could agree however that if we could force-enable functions (instead of the new
|
After a few month I facing this issue again. A class should be instantiated and not called as function. If you call the class which checks Hold on ... I still 👍 this issue. But I just thinking loud. export class UUID {
value: string;
constructor() {
this.generate();
}
// ...
}
export const newUUID = (): string => {
return new UUID().toString();
};
const uuidInstance = new UUID();
const uuidString = newUUID(); vs function UUID() {
if (this instanceof UUID) {
// Called as class.
this.generate();
} else {
// Called as function.
return new UUID().toString();
}
} It's a great 'feature' in JavaScript. But maybe not the right for TypeScript. NOTE: How should this work in ES6 and "real" classes? ( |
Agree, just enable it for ambient classes, Flow supports this |
@Domvel This issue is about expressiveness of the language. The A good way to measure the expressiveness of Typescript is that it should allow to reimplement all ECMA spec just in pure TS (= no definitions). At the moment it is quite hard to express in Typescript a class that would behave like String, Date, Number, etc because of the lack of callable classes. |
would emit :
The text was updated successfully, but these errors were encountered: