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
Traits would be able to have field-backed properties, which interface cannot.
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.
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).
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())
}