Constructors or static functions in interfaces, or a workaround


#1

I used to program in Java a lot, have been learning Swift for work recently, and now finally got around to tinkering with Kotlin.

One lack I immediately feel coming from Swift is that interfaces can not require certain constructors to exist (or, less relevant but also interesting, static functions/members). For instance, I could do this:

protocol JsonRepresentable {
    init(fromJson: String)
    func asJson(): String
}

Very clean, very neat. Note that nice things are possible now: if I have a T: JsonRepresentable somewhere, I can call T(myJson) and get an object of type T!

Kotlin does not have this feature. I don’t have a good intuition yet which kinds of things are due to language design and which to limitations of Java/JVM. If it’s a language design thing here, the solution is “easy”, so I’ll assume it’s some kind of limitation.

So here is a Kotlin-ish idea for a workaround: Let interfaces specify companion objects and their members.

We can already write down this (yes, I know that Self is obsolete here):

interface JsonRepresentable<Self: JsonRepresentable<Self>> {
    fun toJson(): String
    
    interface Companion<Outer: JsonRepresentable<Outer>> {
        operator fun invoke(json: String): Outer
    }
}

But there does not seem to be a way to require Outer == Self (let alone the implementing type), and that every JsonRepresentable actually have a companion implementing the (suggestively) named interface; all we have here is two independent interfaces JsonRepresentable and JsonRepresentable.Companion.

I would like to propose that something like this be valid code (actually, I’d like there to be a proper meta type Self as well, but that’s a separate issue):

interface JsonRepresentable<Self: JsonRepresentable<Self>> {
    fun toJson(): String

    companion {
        operator fun invoke(json: String): Self
    }
}

The intended semantics for the contract would be:

  • Every implementation of JsonRepresentable must have a companion object.
  • It must implement (operator) method invoke.

That much can (or so I think) be implemented in the Kotlin compiler, using conventions for the first part. The main thing I’m not sure about is whether it’s possible to make type parameters available to inner classes/objects. I don’t see why not, but well.


#2

I would like to use the constructor in interfaces too.
I don’t need the static functions myself, but a constructor would be great!


#3

Yeah, but then you’d passing the static JsonRepresntable object (which is essentially an entirely separate object to any JsonRepresentable instance), wouldn’t you? At that point wouldn’t it be just the same as having two separate interfaces: one for being a Json object and one for being able to make a Json object (which you can feel free to put as the companion object):

interface IJsonRepresentable {
	val json: String
}

interface IJsonCreator {
	fun fromJson( stt: String) : IJsonRepresentable
}

class JsonRepresentable : IJsonRepresentable {
	override val json: String get() = "{}"
	
	companion object : IJsonCreator {
		override fun fromJson(stt: String) = JsonRepresentable()
	}
}

Sure there are design reasons you might always want them attached to the object as a companion, but unless you’re using heavy reflection or something is there a code reason?

You could even do this if you REALLY wanted:

interface IJsonCreator {
	fun fromJson( stt: String) : IJsonRepresentable
}

interface IJsonRepresentable : IJsonCreator {
	val json: String
}

class JsonRepresentable : IJsonRepresentable, IJsonCreator by JsonRepresentable {
	override val json: String get() = "{}"

	companion object : IJsonCreator {
		override fun fromJson(stt: String) = JsonRepresentable()
	}
}

#4

My solution for a similar problem was as follows:

interface MyInterface {
    /**
     * Links to the companion object implementing `MyInterfaceCompanion`
     */
    val myInterfaceCompanion: MyInterfaceCompanion

    val instanceStuff: InstanceStuffType

    val newSpecialStuff: NewSpecialType
        get() = doSomethingWithInstanceAndStaticStuff(
            myInterfaceCompanion.staticStuff, 
            instanceStuff
        )
    )
}

interface MyInterfaceCompanion {
    val staticStuff: StaticStuffType
}

#5

I only have one superclass slot at my disposal; using it for this technical purpose here blocks all modelling uses.

And you still don’t get the companion on the final classes. (Are they even inherited?)


#6

Sure. That’s plain delegation (unless I’m missing something?) and does therefore not bring the syntactic and compatibility advantages that companion objects have.


#7

Constructors and static methods in interfaces sounds like you are mixing 2 concerns:

  • The functionality provided by the interface.
  • The creation (constructors) of and other functionality for the types implementing the interface.

If you need to create interface implementations based on a specific set of parameters, simply use a function type. In your case:

val jsonRepresentableFactory: (String) -> JsonRepresentable = ...

If you need additional functionality besides construction, create another interface:

interface JsonRepresentableType {
    fun create(s: String): JsonRepresentable
    fun otherFunctionality()
}

#8

My problem was to extract out some common code from a class-with-companion-object, and create an interface for that.

Stubs of my actual implementations:

interface CursorDto<T : CursorDto<T>> { }
interface CursorDtoCompanion<T : CursorDto<T>> { }

So I get a reference to the implementing type, and require both be instantiated from the same class. It’s not strictly enforceable, and you need to inherit in both the class and companion, but it’s the cleanest interface I’ve come up with.


#9

I don’t follow. How is initialization not functionality? Or say, the static accessor of a singleton class?
For me, an interface is there for specifying contracts. I see no conceptual reason why constructors and static methods/properties (or, in Kotlin, the existence and contract of the companion object) can’t be part of such a contract.

(Of course, in Java static things are not inherited, which makes an interface with static components a little bothersome to implement.)

One contract I want is: “If this object is of type A, you can create an instance given parameters x, y,z` by calling this here function.” It’s a pain in the butt that this can’t be expressed in Kotlin (or Java).

Of course, there are plenty of way to create such functions (several proposals have been made here but, alas, missing the point), but I can’t specify them resp. their existence (which, again, is the point).


#10

How can you specify (the signature of) the creation function and the functions you want to call on the created objects in 1 interface? Those are 2 separate things: creation and use. So your contract should consist of 2 separate interfaces.

But the first interface forces the use of the second:

interface JsonRepresentableFactory {
    fun init(fromJson: String): JsonRepresentable // Forces use of the 2nd
}

interface JsonRepresentable {
    fun asJson(): String
}

In Swift you can apparently do something like this. Note: I do not know Swift, so I use (pseudo-)Kotlin syntax:

val jsonRepresentableClass: Class<JsonRepresentable> = JsonString::class.java
// This is allowed because of "init(String)"
val jsonRepresentable = jsonRepresentableClass.newInstance("some JSON")
println(val jsonRepresentable.asJson())

But in Kotlin there is no way to specify that a subclass must implement a specific constructor signature, and there is no way to invoke a specific signature without using reflection.


#11

Ah, good point. We’d need a Self meta type if we wanted to do such things for static interface methods such as factories (same as now for non-static methods). For constructors, compiler magic happens anyway, though, so this issue shouldn’t prevent having constructors in interfaces even without Self.

Those are 2 separate things: creation and use.

This I still disagree with; the distinction seems artificial. Sure, constructors and static methods do not have the same receiver as an instance method call, but that’s not a big issue (since the reveiver is unique and known, at runtime at least).

Note that I would be totally okay with split interfaces: what we have now (interface Foo { ... }) for specifying instance receivers, and something else (e.g. static interface Foo { ... } in Java, or interface Foo { companion { ... } } in Kotlin).

But in Kotlin there is no way to specify that a subclass must implement a specific constructor signature

Quite right, and that’s what this discussion is about: extending polymorphism to cover statics/constructors. I think we all agree that what I’m asking for is not possible in Java/Kotlin now, but I’d like it to be (in Kotlin).