A delegation-based proposal for composition (a.k.a. "Traits")

Kotlin’s support for implementation by delegation of interfaces is great, but I think with it can be brought a little further to allow true composition.

Interface delegation work this way (or at least that is how it seems to me to be designed): you have an interface you probably haven’t designed yourself, and want to add support for it to a class, so instead you write an implementation mini-class and use the by notation in the class header.

Now, imagine, you are instead the interface designer: you want to provide small modular functionality (including both interfaces and their implementors) to others so they can mix and match whatever they need. The problem is that you’ll be reduplicating code a lot: for each of these components you’ll be writing both the interface and the implementor class. Interface by delegation is quite useful when you have to write implementations for interfaces you don’t control, but when you are declaring the interface too, it becomes a nuisance, specially when later you have to refactor.

Therefore, I propose the following:

A new type of declaration: I call it “trait class” but I’m not married with the word. Another alternative could be “component class”. Trait classes could be used like normal classes, but they come with added functionalities. Whenever you define

trait class Foo

this is actually expanded to

interface Foo
class FooImpl

and you can then use it like this:

class Bar : Baz, Foo

which would be expanded to

class Bar : Baz, Foo by FooImpl()

What is the difference to providing the implementation as default functions within the interface?

Two, mainly:

  1. Traits would be able to have field-backed properties, which interface cannot.
  2. Traits would have constructors and could be instantiated as normal classes.

(1) is specially important, since some functionality requires to cache some state, so it’s only natural to tie together some reusable piece of functionality (i.e. methods) and the state it relies on (i.e. fields).

Your proposal is not complete in those cases where Foo needs to invoke the outer object itself. In that case Foo must contain a property allowing access to the outer object.

Could you elaborate? I’m not sure what do you mean by ‘the external object’.

What I mean is the following:

interface MixinContainer

interface Mixin {
   val container: MixinContainer

   fun printState() { println(container) }
}

class Outer: MixinContainer, Mixin by Mixin() {
  override val container: Outer get() = this
}

There are variations on how this could be written and whether you need a separate MixinContainer and whether you make the Mixin named (rather than anonymous).

1 Like

So, it’s been a while, but I think I’ve come up with an improved version of the idea. Let’s showcase the keypoint of the proposed syntax with a dummy example:

// ":" specifies supertypes, while "for" specifies that the implementor of the trait must also be a SomeClass, an AnInterface and an OtherInterface
trait MyTrait: OtherTrait, SomeInterface for  SomeClass, AnInterface, OtherInterface { 
    val id = 1
    val someProp = List<Self> //Self is the type of the class that embeds the trait.
    fun print() { println("$self") //self is a special identifier that points to the instance embedding the trait.
}
//A class that implements
class MyClass: SomeClass, AnInterface, OtherInterface, Trait{
    override val someProp = listOf(MyClass())
}
//then you can access Trait members as if they were class members, or qualified by the trait name
val m = MyClass()
m.id
m.Trait.someProp

This would expand to:

interface MyTrait<Self: SomeClass, AnInterface, OtherInterface>(self: Self): OtherTrait<S>(self), SomeInterface{
    val self: Self
    val id: Int
    val someProp: List<Self>
    fun print() { printLn("$self")
}
class MyTraitImp<S>l: MyTrait<S> {
    override val id = 1
}
class MyClass: SomeClass, AnInterface, OtherInterface, Trait<MyClass>{
    override val self get() = this
    override val someProp = listOf(MyClass())
}