A way to enforce static properties in sub-classes

For some time now I’ve missed a specific feature from various programming languages, and I wonder whether there is a way to do this in Kotlin. Suppose the following classes:

class VCS {}

class Git: VCS() {
    companion object {
        const val NAME = "git"
    }
}

What I’d like to enforce is that all sub-classes of VCS implement a compile-time constant / static property called NAME. A concept of something like a “static interface” would be useful here. Or less elegant, a way to access NAME of all sub-clsses of VCS within VCS so it fails to compile if any of the sub-classes do not implement that static properly.

Is there a way in Kotlin to achieve this?

2 Likes

Generally, no. But your particular case is just asking for a singleton. Make VCS an interface and add a name field to it. Then Git could be a singleton that inherits the interface and forced to implement the constant. It won’t be compile time, but it will be created only once.

1 Like

Right, but my point here really is that I want to access Git’s NAME without actually creating a Git instance (not even a singleton one).

So, would be adding such a “static interface” paradigm something to consider for the language design?

1 Like

I can see your point. I seldom have similar problems, but I can’t see a case where such thing could not be avoided. I usually use an annotation with parameter instead of interface.

I’ve hit cases like this a lot as well. Here’s how I generally handle it:

interface VcsType {  val name: String }
interface Vcs {
    val type: VcsType
}

class Git : Vcs {
    override val type = Companion

    companion object : VcsType {
        override val name: String = "Git"
    }
}
1 Like

Thanks for the suggestion, but this is not “fool-proof” as it also does not enforce name to be available at compile time without a Git instance being created, as one could also implement this as

class Git : Vcs {
    override val type: VcsType

    init {
        type = object :  VcsType {
            override val name: String = "Git"
        }
    }
}
2 Likes

If you are accessing the companion object directly, unless you’re using reflection then the enforcement is the api of the object itself.
In other words, if you have the code Git.name, that is enforced because it wouldn’t compile without name being a member of Git.

Not sure what you’re trying to avoid by not creating singleton instances.
In your example, I would have used a sealed class:

sealed class VCS {
    abstract val name: String
}
object Git: VCS // Error: Object 'Git' is not abstract and does not implement abstract base class member public abstract val name: String defined in VCS

Fixed:

object Git: VCS() {
    override val name = "Git"
}

Also, what languages support this feature?

This is usually what I do.
If you also wanted to do constructor-like instance creation from that object,

val myGit = Git()

you could add an abstract invoke method to the VCS class (although you may have to do something like recursive generics if you wanted that constructor to return a more specific type than VCS)

1 Like

Not sure what you’re trying to avoid by not creating singleton instances.

I’m trying to avoid singletons (or more specifically, the singleton design pattern) because they are bad.

Also, what languages support this feature?

While not directly supported, you can enforce something like a “static interface” with meta-template programming in C++.

The link you provided is just a personal opinion, and a highly dubious one, I would say. My personal opinion is that Singleton isn’t inherently bad, when you don’t have to implement it by yourself.
In fact, a lot of features you’re using in Kotlin are based on Singletons.

And I’m pretty sure that something like meta template programming is a feature that Kotlin doesn’t need.

2 Likes

Yes, but one that is shared / upvoted by over a thousand StackOverflow users, which is a very high score if you know the platform. Also, I just just picked one, but you can find similar answers, articles, blog posts etc. all over the Internet, so it’s barely “just a personal opinion”.

Anyway, this is becoming off-topic.

Yes, it is, but I still want to add something.
The post you linked gives many good reasons why singletons are dangerous. That does not mean however that there are problems for which they are a good solution. Every pattern has it’s up and downsides. The singleton pattern if not used carefully has many downsides, that’s why you see people call it bad. On the other hand, Jetbrains reserved a keyword for that pattern, which IMO is a high praise and saves me the time to go into the upsides :wink:.

Whether singletons are a good solution for your problem. Not sure, depends what other properties and methods you need in your Git class and on the rest of your project.

2 Likes

I would also note that some of the arguments why Singletons are bad probably also apply to “static interfaces” or “compile-time constants”.

I agree that Singletons are bad for some cases. But In those cases any static or compile-time solution is bad.

It depends on the use-case.

Take for example Java/Kotlin enums. They can be very bad also, for very similar reasons to why Singletons are bad. They even are implemented as Singleton when used in a particular way. Yes, enums can indeed be a very big pain in the ass when misused. But they can also be a blessing.

2 Likes

I have done this identical thing (When I say Identical, I mean VCS/Git and static naming). You do, infact, have a static name already built in. The class name!

Git::class.simpleName!

Sure, but that really was just a simply example. I’m really interested in a generic solution where the static field can also be forced to be e.g. a numeric priority.

There is no fool-proof way in any JVM language I am aware of. You can basically apply quack-typing at runtime and exclude anything that doesn’t comply but that’s it. I have been working on the JVM since '99 - Your only/best bet is to make some assumptions and work with existing infra (like .class.simpleName or toString)

That’s also my experience. Which makes it a nice feature candidate for Kotlin to implement!

I’m not quite sure what this feature should accomplish. Do you just want to enforce that each subclass of VCS has some fields in the companion or do you also need a new way of accessing them.
Accessing a companion member is still static dispatch and there is no way of making this dynamic (AFAIK).
How would you want to use this? Is this supposed to work with generics? I guess reified types would work, other than that I don’t see a use case.

1 Like

Basically yes. But practically I do not care whether such “statically enforced” fields are part of a companion, or whether some totally new language feature would be used for that. Just like with abstract fields I do want to force subclasses to implement / provide the field, but in contrast to abstract fields the field must be accessible statically, i.e. without and instance of the surrounding class.

Edit: Let me provide a fictional example of how it could work. Suppose we had a new keyword called static interface. Then something like

static interface AccessibleName {
    val name: String
}

class Git : AccessibleName {
    override val name = "Git"
}

would not compile as name is not abstract and the interface is static. The Git class would need to be changed to

class Git : AccessibleName {
    companion object {
        override val name = "Git"
    }
}

to make the code compile.