18

I'm looking for a way to implement an abstract static method in TypeScript.

Here an example:

abstract class A {
  abstract static b (): any // error
}

class B extends A {
  static b () {}
}

This is allowed with public, private and protected methods but not with a static method. TypeScript returns the following error:

'static' modifier cannot be used with 'abstract' modifier.ts(1243)

Is there any workaround?

8
  • 2
    Why do you even need an abstract static method? What does that mean from design perspective? Related: Why can't static methods be abstract in Java? Commented Jun 9, 2022 at 9:24
  • Does this answer your question? Why can't static methods be abstract in Java? Commented Jun 9, 2022 at 9:37
  • 4
    Java and JavaScript are completely different languages. In JavaScript, classes are first class objects just like instances and so it's perfectly reasonable for them to have required members. Instead of comparing this to java and its limitations, consider Scala Commented Jun 9, 2022 at 9:38
  • 1
    It would mean that subclasses have to Implement such a method. It's true that there's no way to express this in the language but it's not so strange to desire such functionality if you're committed to using classes. Personally, I think abstract should be avoided in all typescript contexts but that's a separate argument. Commented Jun 9, 2022 at 9:53
  • 9
    @VLAZ from a design perspective, it would mean the parent class can mandate that child classes implement the static method or they get an angry red squiggle in their editor and compile errors. I like to make abstract base classes that are extended for various uses. I like to use abstract methods to force those classes to implement something so it works consistently. Sometimes those things should be static. If I have to depend on the junior dev to implement an interface, there is no guarantee it will be done. Commented Mar 8, 2023 at 1:16

4 Answers 4

16

Another way to handle this type of scenarios, could be to introduce a runtime check. The idea is to provide a default implementation that obviously won't work, and rely on runtime errors to throw error if the derived class hasn't overridden the method.

abstract class A {
  static myStaticMethod(): any {
    throw new Error('Method not implemented! Use derived class');
  }
}

class B extends A {}

B.myStaticMethod(); // error: Method not implemented...

That can hint you to override the static method.

class B extends A {
  static override myStaticMethod(): any {
    console.log('yay!');
  }
}

B.myStaticMethod(); // yay!
Sign up to request clarification or add additional context in comments.

Comments

7

I think the reasoning is generally as follows:

  • Abstract methods are placeholders that a concrete subclass is supposed to implement.

  • The abstract base class will have a concrete method which uses this abstract placeholder, and thus requires it to be implemented; without this, abstract methods make little sense at all. E.g.:

    abstract class Foo {
        bar() {
            return this.baz() + 1;
        }
    
        abstract baz(): int;
    }
    
  • This can only be called on a concrete instance, e.g.:

    function (foo: Foo) {
        let val = foo.bar();
        ...
    }
    

    The result will be different here depending on what concrete subclass of Foo you get, but it's all guaranteed to work.

Now, what would this look like with static methods?

abstract class Foo {
    static bar() {
        return this.baz() + 1;
    }

    abstract static baz(): int;
}

function (F: typeof Foo) {
    let val = F.bar();
    ...
}

This looks somewhat logical and like it should work, shouldn't it? But:

  • If you write this, there's basically no difference to passing instances of classes, except for the awkward typeof typing. So, why?
  • If you never instantiate the class, what's the point of a class?
  • If your static method does instantiate an object, if it acts as an alternative constructor—which is entirely legitimate—then calling F.bar() to get a new instance, you'd expect to get an instance of F, but you might be getting an instance of some subclass of it. And that's arguably not desired.

To rephrase the chain of argument here:

  • If you're going to use static methods at all, they should act as alternative constructors and return an instance, otherwise they have little business being part of the class.
  • Alternative constructors should return an instance of the class that you called them on. Calling F.bar() and not getting an instance of exactly F is… weird?
  • If you're going to call static alternative constructors on classes, you're going to want to call them on specific classes and not variables as shown above, because otherwise you really won't know what you're getting (see point above).
  • Therefore, there's no real use case for an abstract static method, either as direct alternative constructor nor as helper function for one, since that would lead to a violation of one of the above points one way or another.

As far as I see, this is pretty much the classical thinking that lead to abstract static methods not being a thing. Certain technical limitations in Java may have contributed to that and/or lead to said technical limitations in the first place.

In Javascript one can argue that this makes less sense, since even classes are objects and it would be entirely feasible to use them as described above, and in certain situations it may even make sense. But here we are.

7 Comments

"Certain technical limitations in Java may have contributed to that and/or lead to said technical limitations in the first place" Not only Java: abstract static methods are not available in C#? nor in C++. To an extent that's probably a design limitation but I think you also do a good argument for why they probably didn't bother making abstract static classes valid. The use-case for such a feature is questionable.
Ugh typo. I missed the edit window: "abstract static classes" -> "abstract static methods"
I beg to differ: In javascript, we have a prototype chain of the instances of a class, as well as the class constructors themselves, which means that in fact, typescript supporting abstract static members won't break anything, and actually seems perfectly logical, and would help in a few obscure cases, where type-unsafe operations (the thing typescript is trying to eliminate) are currently needed
abstract static baz(): int; does not work for me
@Gonzalo You really need to read. This entire thread is explaining why it doesn't work.
|
2

One solution I came up with to have a static method and to make sure it's implemented via the type check system was to declare a static method that calls an abstract method on the prototype.

abstract class Foo {
  static get Bar() {
    return this.prototype.bar;
  }

  abstract bar();
}

You could even provide a custom this arg to the non-static version to remind that it won't have this defined yet:

abstract bar(this?: Foo | undefined);

Comments

1

I solved it the following way:

interface Selectors<N> {
    selectName(node: N): string | undefined;
}

const selectors: Selectors<object> = {
    selectName(node: object) {
        return "my name is node";
    },
};

This is obviously not the same as an abstract static class, but depending on what you are going to do with it, this might be a good solution!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.