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


#1

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()

#2

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


#3

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).


#4

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.


#5

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


#6

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).