I'm reading <https://avwie.github.io/compositional...
# getting-started
d
I'm reading https://avwie.github.io/compositional-patterns-in-kotlin-part-1-delegation, namely a note about a possible default implementation of an interface:
Copy code
interface Health {
    var health: Int
    val isDead: Boolean get() = health <= 0
    
    fun damage(amount: Int) {
        health -= amount
    }
}
It has the clear drawback that
health
can be mutated directly. I wondered whether it couldn't be fixed somehow, e.g. something like
Copy code
interface Health {
    protected var healthInt: Int
    val health: Int get() = healthInt
    val isDead: Boolean get() = health <= 0
    
    fun damage(amount: Int) {
        healthInt -= amount
    }
}
For some reason which I don't yet understand,
protected
is not allowed in interfaces. Is there possible a different strategy how to keep the default implementations while hiding the direct
health
setter?
s
I think that article itself does a good job of describing the alternatives. The section immediately after the one you quoted describes using delegation to achieve something like mixins, which is the approach I think I would take.
Protected members on interfaces just weren't part of the original idea of what interfaces were for, but in theory they could be added in future: https://youtrack.jetbrains.com/issue/KT-22408
d
Thanks for the link! Re the original question: I used an example from the article just as an inspiration for the question, i.e. I'm asking if in general, something like that (default impl + hiding setter) is possible even now. What I find a little bit worrying in the explicit implementation approach
Copy code
class HealthImpl(initialHealth: Int): Health {
    override var health: Int = initialHealth
        private set

    override val isDead: Boolean get() = health <= 0

    override fun damage(amount: Int) {
        health -= amount
    }
}
is that once I have an instance of
Health
, e.g.
Copy code
val orc = Orc.new(position = 5.0 to 0.0,  damage = 30)
then
orc.health
is val but it can be changed, possibly in a different thread. Perhaps I'm just misunderstanding the message that val communicates. When I see a val in the code I would like to think immutable but that clearly isn't the case here.
s
The
private set
part ensures that it can only be modified by methods inside
HealthImpl
👍
But in general you're right:
val
can rarely really be relied on to mean "immutable", so it can definitely be misleading. The
HealthImpl
could just as easily expose a way for anyone and everyone to mutate the underlying value, if it wanted to.
d
> The
HealthImpl
could just as easily expose a way for anyone and everyone to mutate the underlying value, if it wanted to Yeah, so why not to use the default interface implementation with the public setter, then?
I mean, aren't we doing more damage by pretending that
orc.health
is val while it actually can be mutated (even if not by ourselves)?
s
It's pretty normal (and useful) to have properties which are publicly read-only, but can be changed internally. That's what
val
properties are for. They're not supposed to be immutable.
The
val
properties in data classes (or more specifically, final properties defined without a setter and within the current module) are a special case, where the compiler will guarantee they don't change, which does make things more confusing 😞
But in general, a
val
can change whenever it wants and nobody should be surprised by that
Maybe it would be useful to have a new modifier that makes a val immutable though 🤔
d
Thinking about it more, most of the public properties in my classes actually are in data classes, so that's probably the reason why I think
val=immutable
. But I'm not yet using the delegation model so it's good to know that once I start I should change my thinking paradigm.
Maybe it would be useful to have a new modifier that makes a val immutable though
Most probably yes because AFAIK there's no general way how to declare a property really immutable...
👍 1
s
I think some really good examples of a
val
being read-only but not immutable are
Collection.size
and
String.length
. They demonstrate the usefulness pretty well.
👍 1
It looks like immutability was discussed but declined in the past: https://youtrack.jetbrains.com/issue/KT-50085
👀 2
d
Btw regarding
Collection.isEmpty
: for some reason I would rather like to have this as method. Perhaps I tend to view these boolean methods/props on a different complexity level than the other props like size etc. But that's only my gut feeling 🙂
s
I just checked, and in fact it is a function 😄 so I edited my earlier message
🤭 1
The style guide mentions this, but it's not clear cut: https://kotlinlang.org/docs/coding-conventions.html#functions-vs-properties
a
Hey, my blog 😁 . Val doesn’t imply immutability in my case but I understand the confusion! The ‘old’ Java convention specifies a getter in this case, but in Kotlin this isn’t a convention. The Val communicates interior mutability. But… I agree it isn’t that clear.
👍 1