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.

1 Like

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

Interfaces extending other interfaces is quite common practice in business app models, particularly to reduce copy-pasing and reuse parts of the models for read version, write version, API model, SQL database model, noSQL database model, messaging model, protobuf model, …

Imagine a data object bearing 40 properties, and for various frameworks, you need to annotate them differently, or tweak them in various incompatible ways. Also, parts of the object can be reused (e.g. composition in NoSQL, like, a person and the address). So then, the interfaces capture the business semantics of the model, or parts thereof; you may then pass the interface(s) around the app, while the underlying object may be backed by any of the models.

Same goes with CDI, where this “smell” is usually hidden by frameworks like Spring and causes errors at runtime. Or, you can do things in a type safe way, which is what I am trying to come up with.

Note: The class intended to consume such compound interface is in another module than the module with the smaller interfaces.
Also: This feature is intended to be for data classes properties, not really “functionality”. This intended use does not change much language-wise, if it’s supposed to be general enough. But may hint about what I mean by it and feel less as a smelly code.

Thanks for the suggestions, I will explore whether they are viable.

Then it would stop compiling, because it would no more qualify the condition of being solely made of other interfaces.

I think this is not at all what I propose, you probably misunderstood.
The proposal is that the compiler will ensure at the compile time that you are passing the “right thing”, which fully complies to the declared interface; instead of, e.g., passing some reference, and checking at runtime, or fiddling with proxies, which is what the IoC / CDI frameworks internally do. I wanted to keep the comparison to IoC out of scope as it may stir up unrelated topics, so let’s take it just as a vague context of the motivation.

I’m not sure if you got me right. There is nothing wrong in extending interfaces from other interfaces. I meant creating interfaces with the sole purpose of “gathering” other interfaces. It sounds like a good idea initially and in some rare cases it really is, but at least from my experience in most cases it causes more harm than good in a long a run. It causes problems like you have above - we have an object that implements both HasFoo and HasBar and this is exactly what a method needs to do what it has to do, but we can’t easily call it, because it “artificially” requires a more restricting HasFooAndBar type.

In my head class implementing HasFoo and HasBar is a different thing than class implementing the HasFooAndBar. In the second case the class explicitly said it knows the type and it represents this type. In the first case we may accidentally become a type we never wanted to be.

Of course, there is always a room for some syntactic sugar. Language could provide a utility similar to my asFooBar above and this way the developer could explicitly choose to convert to the “compound” type. Implicit conversion is not a good idea in my opinion.