Feature: Allow compound interfaces

I have a proposal which I think would fit nicely to Kotlin without threatening the language design consistency.

For interfaces consisting only of other interfaces, where such interface is expected, Kotlin could allow using a type which implements all these interfaces.

interface HasFoo { val foo: Foo }
interface HasBar { val bar: Bar }

interface HasFooAndBar: HasFoo, HasBar

class ProvidesFooBarBaz (
  override val foo: Foo,
  override val bar: Bar,
  val baz: Baz,
)
   : HasFoo, HasBar

val appContext = ProvidesFooBarBaz(...)

class ComponentNeedsFooBar (
    val appContext: HasFooAndBar,
)

So far so good, Kotlin can do this. Here comes the trick:

val component = ComponentNeedsFooBar(
    appContext = appContext
)

This would not go, since appContext does not implement HasFooAndBar, despite implementing all that HasFooAndBar is.

So again: For interfaces consisting only of other interfaces, Kotlin could allow using a type which implements all these interfaces. This of course would be extended transitively to a tree of such “compound” interfaces, i.e. the check would pass if the used type transitively implements the nested interfaces. This check would not pass for interfaces which have anything besides extending other interfaces.

It is a very limited case of duck typing. I did not want to put “duck typing” to the title, because that has been discussed as a general idea; but here, it is relatively simple mechanism.

It would be very useful for a compile-time, type-safe dependency injection, at least.
As discussed here:
https://youtrack.jetbrains.com/issue/KT-13108/Denotable-union-and-intersection-types#focus=Comments-27-10988052.0-0

I was experimenting for a while with various features like delegates, inline functions, as, but did not figure out any alternative that would be as elegant as these compound interfaces.

WDYT? Useful or not? Can you think of other use cases? Is there something such coming in near future Kotlin? Thanks.

My two cents:

  1. Yeah, duck typing was my first thought. While I see how this could be convenient, for me it doesn’t fit the Kotlin nicely. Kotlin is a strongly typed language build on top of the classic OOP. I can definitely see intersection types going into it, but such compound interfaces seems to contradict the classic OOP.
  2. It would be tricky to compile to JVM bytecode as JVM doesn’t support this. Kotlin would have to make some hacks that wouldn’t be ideal for the Java interop.
  3. Taking your specific example into consideration, why not to receive both objects separately? If designing any kind of API, it is much more flexible to receive: foo: Foo, bar: Bar than: fooBar: FooBar. Even if the caller have fooBar, they can still call the first variant by passing the same object twice. If the caller have foo and bar separately, they won’t be able to call the second function easily.
  4. To some degree we can mitigate the problem by using delegation:
fun main() {
    val obj = MyClass()
    consumeFooBar(object : FooBar, Foo by obj, Bar by obj {})
}

interface Foo { fun foo() }
interface Bar { fun bar() }
interface FooBar : Foo, Bar

class MyClass : Foo, Bar {
    override fun foo() = println("foo")
    override fun bar() = println("bar")
}

fun consumeFooBar(fooBar: FooBar) {
    fooBar.foo()
    fooBar.bar()
}

Of course, we can have a function like FooBar.compound(Foo, Bar): FooBar to make it even simpler.

Or even:

consumeFooBar(MyClass().asFooBar())

fun <T> T.asFooBar(): FooBar where T : Foo, T : Bar = object : FooBar, Foo by this, Bar by this {}
2 Likes

What’s the practical use case for this? I can see how it makes sense, but why would you need this? Both “why have an interface that does nothing but implement multiple other interfaces”, and “why have duck typing?” I think you could solve the problem by making ComponentNeedsFooBar use generics. ComponentNeedsFooBar<T : HasFoo, HasBar>(val appContext: T) (Though tbf, I don’t know if you can do that on class generics, or just function generics)

1 Like

I agree. For me personally an interface with a sole purpose of extending other interfaces, is a serious code smell. It could be justified in some rare cases, but generally, using types like this introduces more problems than it solves.

Ok, but what if we need to provide a functionality of multiple types by a single object? My answer is the same as always - composition over inheritance:

data class FooBar(val foo: Foo, val bar: Bar)

Hmmm, I don’t think I would actually want this behavior. Here is why.

If I want a use site to ask for something that’s a Foo and a Bar, I’ll always write : Foo, Bar. If I ever define an interface that’s both, then it is something different in meaning, if not in implementation. Maybe it will gain additional members in a future patch or something ? Or, I do not want to be able to pass something that implements everything without declaring that it is the right thing (e.g. encoding numerical units in the type system where both Meters and Degrees are Doubles but you don’t want to be able to pass meters to something that takes degrees). Either way, I don’t want to be able to pass an object that implements everything but is not the right compound type.

The case where I think this could come in handy is if you want a shorthand. Say you have three interfaces and a lot of usage of them together and you want to write this more succinctly. Say you have HasWheels and HasSteering and HasEngine and you make a Car shorthand because in your code that’s the only thing that has these three things. In this case I would not want to declare Car as interface Car : HasWheels, HasSteering, HasEngine (unless I plan for Car to gain stuff on top of these three in the future). I’d want typealias. I’d be more interested in a proposal that implements this with typealiases, and it has the extra bonus that today typealias can’t take multiple interfaces so you can’t change the meaning of existing code.

Finally on a side note I would like to point out that this doesn’t look at all like a limited case of duck typing to me. It looks like a limited case of structural typing. Structural typing can be an amazing tool but it’s also pretty difficult to implement on the JVM and pretty difficult to design in a way that can’t easily shoot you in the foot. Anyway, I expect the Kotlin designers have considered and discussed structural typing too and there was a conscious decision not to do it in Kotlin ?

1 Like