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())
}
Just want to contribute to the discussion with a use case I’m encountering on an Android app, I think someone could relate better with a concrete example.
It’s been a while that Android devs started discussing about how bad and full of constraints the ViewModel class is for their architecture patterns.
In my code, I want to make properties and functions available to my viewmodels to provide state and events and receive UI input, but composing these without having to make an abstract class extending the ViewModel class. Example of an abstract class implementation following:
abstract class VM(initial: State) : ViewModel() {
protected val stateSink = MutableStateFlow(initial)
val state: StateFlow = stateSink.asStateFlow()
protected val eventSink = Channel<Event>()
val events: Flow<Event> = eventSink.receiveAsFlow()
protected abstract fun handle(a: Action)
fun onAction(a: Action) {
handle(a)
}
}
What I find desirable is to not have to extend the ViewModel class but being able to provide stateSink and eventSink to the concrete ViewModels in order to provide the onAction like a public interface, keep the stateSink and eventSink properties protected be accessed by the extensions of the VM trait class only along with the handle function.
The end result would look similar to the following:
trait class VM(initial: State) { // no need to inherit ViewModel
// overridable by classes inheriting the trait
protected val stateSink = MutableStateFlow(initial)
// public val treated like interface val
val state: StateFlow<State> = stateSink.asStateFlow()
// overridable by classes inheriting the trait
protected val eventSink = Channel<Event>()
// public val treated like interface val
val events: Flow<Event> = eventSink.receiveAsFlow()
// override from inheriting class
protected abstract fun handle(a: Action)
fun onAction(a: Action) {
handle(a)
}
}
I don’t know whether to call this trait or mixin, having worked on Dart code it feels more familiar to me to call it Mixin.
There is a convenient way of using this by using a property of the concrete ViewModel to implement this behavior for the container ViewModel class, and it wouldn’t be that hard or ugly to use. But I would like to have this possibility just for the sake of it to be honest It’s just that I don’t like to have multiple dot calls on the same line to call properties of properties of the object I get, it smells bad, even though this is also evitable by passing that object to the UI.
Guess this contribution is just Duck Programming for me at this point, sorry