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

Suggestion: static constructors #265

Closed
omidkrad opened this issue Jul 26, 2014 · 31 comments
Closed

Suggestion: static constructors #265

omidkrad opened this issue Jul 26, 2014 · 31 comments
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds

Comments

@omidkrad
Copy link

A static constructor is a function that is run only once when the class is loaded. It can be used to initialize static class members and maybe as entry point to the application.

Suggested syntax is:

class MyClass {
    static initialized = false;

    static constructor() {
        MyClass.initialized = true;
    }
}

alert(MyClass.initialized);

That would be same as the following:

class MyClass {
    static initialized = false;

    static __ctor = (() => {
        MyClass.initialized = true;
    })();
}

alert(MyClass.initialized);

Except that __ctor does not need to be assigned to the class in the output code, and only needs to be invoked in an anonymous function. Also compiler can check that no more than one static constructor is defined in a class.

Update: Since this generates an Immediately-Invoked Function Expression (IIFE), compiler should make sure to move it to the end of the class definition in the output JavaScript.

TypeScript:

class MyClass {
    static constructor() {
        MyClass.initialized = true;
    }

    static initialized = false;
}

Expected JavaScript output:

var MyClass = (function () {
    function MyClass() {
    }
    MyClass.initialized = false;

    // run last
    (function () {
        MyClass.initialized = true;
    })();
    return MyClass;
})();

Note that body of the static constructor is moved to the bottom and invoked.

Related suggestion in codeplex: Please add static constructors to classes

@basarat
Copy link
Contributor

basarat commented Jul 27, 2014

👍

@ahejlsberg
Copy link
Member

You can already accomplish this with a merged class/module:

class MyClass {
    static initialize() {
        // Initialization
    }
}
module MyClass {
    MyClass.initialize();
}

Or, for that matter, you can just have the call to initialize immediately follow the class declaration:

class MyClass {
    static initialize() {
        // Initialization
    }
}
MyClass.initialize();

@omidkrad
Copy link
Author

Pleasure hearing from Mr. Hejlsberg...

The only limitation I see there is that you can't make it private, whereas in my example you can :)

@knazeri
Copy link

knazeri commented Jul 27, 2014

👍 for class/module merge suggestion

@NoelAbrahams
Copy link

The merged class/module code requires that the developer remember to call MyClass.initialise().

The suggestion I believe is that given the following:

class MyClass {
    static initialized = false;

    static constructor() {
        MyClass.initialized = true;
    }
}

We get JavaScript

var MyClass = (function () {
    function MyClass() {
    }
    MyClass.initialized = false;

    MyClass.__ctor = (function () {
        MyClass.initialized = true;
    })();
    return MyClass;
})();

@omidkrad
Copy link
Author

@NoelAbrahams Yes, good point about having to remember to call it in the merged class/module pattern.

However, @ahejlsberg's code has the benefit that call to the static method is made at the very end when the class is completely loaded. I updated the suggestion above to reflect that. The static constructor syntax should only generate an anonymous function invocation at the end of the JavaScript class output.

@DanielRosenwasser
Copy link
Member

Privacy is a good point here, but it's worth pointing out that you can be a little "evil" to achieve the same goal without having to modify the language. ;)

class C {
    private static initialize(): void {
        // do stuff
        C.initialize = undefined;
    }
}
(<any>C).initialize();

You can even replace C.initialize = undefined with delete C.initialize if you're afraid someone will iterate over your class in a for..of loop.

But yes, here the user is still responsible with calling initialize.

@omidkrad
Copy link
Author

The suggested static constructor syntax remains to be superior IMO and has best of everything: 😌

  • Does not pollute the class structure (even better than private)
  • Does not need the developer to remember to call it
  • Makes sure it is invoked last
  • Makes sure a class does not have multiple static constructors
  • Provides a convention, and
  • It is concise and intuitive

@RyanCavanaugh
Copy link
Member

Closing as Too Complex - we would need a more compelling use case since module merging provides a very close approximation to the desired behavior.

@RyanCavanaugh
Copy link
Member

Anders brought up that we also need to be conservative here so that we don't misalign with the ES6 class proposal (which also lacks static constructors).

@basarat
Copy link
Contributor

basarat commented Jul 29, 2014

we don't misalign with the ES6 class proposal (which also lacks static constructors).

👍

@omidkrad
Copy link
Author

Agreed. Let's keep the pace with ES6.

Though, I think I elaborated a little too much on this feature. I realized, specifically, moving the static constructor block to the end of the class was not necessary to defer its invocation. It is deferred anyway because it is in another function that is invoked when it is completely loaded. To test that:

var MyClass = (function () {
    function MyClass() {
    }
    (function () {
        alert("static ctor invoked");
    })();
    return MyClass;
});// do not invoke yet
alert("class loaded");
// invoke
MyClass();

@aymericbeaumet
Copy link

In case anyone is reaching this issue, you can also do it this way:

class MyClass {

  private static _constructor = (() => {
    console.log('Static constructor');
  })();

}

This has the advantage of being a concise syntax not exposing any unnecessary variable.

@omidkrad
Copy link
Author

@aymericbeaumet That is my preferred solution so far and is also noted in the first post.

@aymericbeaumet
Copy link

Didn't catch you proposed the same solution!

@JoshMcCullough
Copy link

👍 Please do! However, a potential workaround would be:

class MyClass {
    private static isInitialized: boolean = false;

    public static init() {
        if (!MyClass.isInitialized) {
            // Initialize things...
        }

        MyClass.isInitialized = true;
    }
}

MyClass.init();

@nin-jin
Copy link

nin-jin commented May 25, 2015

Developers of TS, please, read about D language, where most problems with OOP are solved. For inspiration.

Why i can not use "this" in "static" context? IMHO, "this" must point to current class (not to superclass).

@danquirk
Copy link
Member

@nin-jin You can use this in static member functions and accessors, just not member initializers.

Note that in any comparison to other languages you need to consider the somewhat unique constraints TypeScript operates with (as a superset of JavaScript and emitting clean, idiomatic JavaScript with minimal or no strange rewriting). Were we designing in a green field with only our own thoughts the language would look very different :)

@felixfbecker
Copy link
Contributor

But what about inheritance?

class A {
  private options: {hello: number}
  static constructor() {
    console.log('called')
    this.options = {hello: 123}
  }
}

class B extends A {
}

I would expect called to be logged twice.

@PascalSenn
Copy link

@felixfbecker Every static property is bind to the class. It cannot be inherited. So the static constructor should also follow this pattern to ensure a equal behavior.

@felixfbecker
Copy link
Contributor

@PascalSenn static properties are inherited in ES6 and TypeScript.

@PascalSenn
Copy link

@felixfbecker Thats new for me. Didnt know that. C# doesnt support it, so i thought Typescript neither.

@felixfbecker
Copy link
Contributor

@PascalSenn you can even do it in ES5:

function SuperClass {}
SuperClass.staticProperty = 123;
function SubClass {}
Object.setPrototypeOf(SubClass, SuperClass);

@mark-norgate
Copy link

mark-norgate commented Sep 14, 2016

@RyanCavanaugh I don't think the merged module solution is anywhere near an approximation to a static constructor. It is still required that you call the static member whether you use merged modules or not. A static constructor would initialise automatically.

@rjamesnw
Copy link

rjamesnw commented Apr 28, 2017

It's too bad generic type references were dropped in static functions. I was using some static constructor style methods on some classes (similar to JoshMcCullough's post above) and now it's all broken. The only workaround was to convert the private static constructor methods to private instance methods and call them instead via the prototype from another static constructor method (or directly after the class). I didn't want to duplicate my class generic signature on my static method as well, that's just getting too silly.

@Alphapage
Copy link

Hello,

I hope Typescript will implement static constructors like this:
sirisian/ecmascript-static-constructor#1

It would solve "function properties" typing problem too: https://stackoverflow.com/questions/12766528/build-a-function-object-with-properties-in-typescript

@rjamesnw
Copy link

rjamesnw commented Oct 27, 2017

@Alphapage TypeScript supports many coming ES6+ changes today, best it can, and targeting such code will output code like this class MyClass { } - which works in non-IE browsers. ;) However, what you are suggesting - to support an invokable 'MyClass()' constructor - is an error since, currently, ES6 introduces a new non-invokable function type (for classes). The minute someone targets ES6+, their code would fail. ;) The spec would need to be changed regarding non-callable class functions in upcoming approval stages at some ECMAScript proposal level before this was even considered.

BTW: There's already a proposal here as well: https://github.com/tc39/ecma262/blob/master/workingdocs/callconstructor.md

@gmurray81
Copy link

I'm not sure of the exact mechanics of some of the tree shakers out there, but won't some of these strategies disrupt the tree shakers? Calling a static method on the Type as a separate action outside of the class's IIFE (in ES5) may be seen as a reference to the type? Or containing some side effect that may not be possible to omit? Presumably being able to wrap some code inside of the class's ES5 iffe would been seen as more atomic with the definition of the type?

@nin-jin
Copy link

nin-jin commented Jan 27, 2018

@gmurray81 tree shaking is partial workaround of autoloaders absence. Better to use bundler that supports autoloading. MAM in example.

@gmurray81
Copy link

@nin-jin even if you could just persuade everyone to use different technology in their projects to avoid a problem (some of us need to write library code for others to consume) there would still be an argument that it should be easy to make the creation/calling of the static cons atomic with the class creation.

It seems like shoving an IIFE on the last defined static property of a type, as suggested above, might be the closest you can come with the syntax as it stands. It's a little ugly, though, comparatively.

@Misaka-0x447f
Copy link

@aymericbeaumet The solution you provided may caused inspector report a 'unused variable' warning which cannot be silenced in webstorm... (I cannot write better one until now. Thinking.)

@microsoft microsoft locked and limited conversation to collaborators Jun 25, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Declined The issue was declined as something which matches the TypeScript vision Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds
Projects
None yet
Development

No branches or pull requests