A way to enforce static properties in sub-classes

This is very confusing. The implementation of AccessibleName by Git does not say anything about the actual instances of Git. Instead it says something about another object that is related to Git.

What do you think happens when you statically access a field? An instance is created. True, it is only an instance of Class, but there is an instance. What would the extra overhead of creating an instance of that loaded class be? 1% in CPU and memory? I am pretty sure that you won’t be able to show that creating an instance of a class after that class has been loaded, is a bottleneck.

Well, what I think should happen is that of course no instance is created. I’d be fine with limiting this feature only to primitive types so one could also fulfill the static interface’s requirement by writing something like

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

to make name a compile-time constant. I hope you’re not going to tell me that accessing Git.name in this case will also create an instance of Git. If that was the case, something is really wrong with the compiler.

I would like to see how you would take advantage of this feature in a polymorphic way.

Can you show a usage example that would not work when your Git class is defined this way:

class Git {
    companion object {
        const val name = "Git"
    }
}
1 Like

No, it won’t create an instance of Git, but it will create:

  • The class of the companion object of Git.
  • The companion object of Git.

Creating the companion object class is way more expensive than creating the companion object itself. And I think there won’t be much performance difference between looking up the value using reflection without instantiating the companion object and just asking the instantiated companion object.

As there is no performance difference, I would keep it simple and just let the companion object of an implementation implement the descriptor interface. This has the added benefit that the code is easier to understand (this companion object is used as a descriptor) and navigate (which descriptor implementations are there):

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

Pseudocode to load a VCS descriptor:

val co = getObject("${vcsName}.companion object")
if (co is VcsDescriptor) {
    register(co.name, co)
} else {
    reportDescriptorError()
}

I wish there was a way to do this as well, I wanted to be able to enforce that subtypes all had a static factory creation method.

Why not just create those factory methods without enforcing it? What would be the disadvantage?

1 Like

The hope was to be able to invoke it generically, having just some reified type and being able to invoke a static method on it or access some static property.

Which kinda comes back to my earlier question of how this is supposed to work. Those functions would still be static (or part of the companion which can only be accessed statically) so you could only access them using reified types.
I don’t see that the benefit of this is big enough to add a new system to the language which requires a new kind of Type.
Also how would this work in interop with Java or Js. I’m not an expert on the later but I can say that there is no way of enforcing this type of restraint on java.
So this would be a feature that would only work with reified generics would be used rarely and would break interop with both target platforms (jvm and js). Not sure it’s worth all the trouble, even if I underestimate the number of usecases.


Don’t get me wrong. There are a few problems where this would be nice, so I get why this would be great. But then this was never a showstopper for me.

2 Likes

You can easily make the companion object implement a “named” interface. and then use that for whatever you want. The only “missing” thing is that it might be useful to be able to have automatic access (with dynamic dispatch) to the companion object. That however is tricky with Java classes, or with inheritance of companion objects (if A extends B, that does not now mean that A.companion extends B.companion)

If this is not possible, how can did Android implement and enforce in some classes, like Parcelable?

Parcelable required a static variable (i.e. companion object) called CREATOR. If one try to extend Parcelable without the CREATOR companion object, it won’t allow that person to build the app

I don’t know how exactly Android does this, but note Android is not just a library. It is an SDK, you build the application with Android build tools, so they can verify whatever they want in your application. You can write compiler plugins to check things like that, but then users of your library have to use your plugin.

1 Like