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.