Object as sealed class subclass. Opinion needed

I am surprised at how often i can see the following:

sealed class State {
    class StateA(val dep : Dependency) : State()
    object StateB : State()
}

Stateless child of sealed class defined as object. There is obvious reason to do it - reduce count of allocations for StateB. While it stateless - everything is normal… or not? StateB instance will live forever (ok, enum in Java works similar but in Java we have no choice to describe set of variants) while StateA instances will be collected.

Use case:
Now State should store Date:

sealed class State(var date : Date) {
    class StateA(val dep : Dependency, date : Date) : State(date)
    object StateB : State(Date())
}

StateA has Date passed from outside, and StateB has initial Date.

class StateConsumerA {
    fun applyState(state: State) {
            //.. state.date mutation
    }
}

class StateConsumerB {
    fun applyState(state: State) {
            //… state.date reading
    }
}

And while state is instance of StateA - everything is fine. But when state is instance of StateB - mutation in StateConsumerA will affecting state in StateConsumerB.

Singleton behaviour can be unexpected in some cases of using sealed class and can be error-prone. While you try reduce count of allocations of StateB using object signature actually you trade it off with keeping forever in memory StateB instance.

So how do you think, is there particular reason to restrict defining sealed class subclasses as object?

The problem in this case is the mutating property, not the inner object. There is a reason why people prefer immutable classes, and that mutable singletons aren’t a great idea isn’t exactly new.

There is no reason to restrict inheritance because of this.

1 Like

No, I found it pretty useful, ie:

sealed class Option
data class Some<T:Any>(val value:T):Option()
object None:Option()
1 Like

Fair about mutable singleton… but again - what the advantage of using object here? You getting error-prone code, trying to be memory efficient but storing your object in memory all time and not allow it to be garbage-collected… I would like that compiler protect me from that.

What are you trying to achieve here using object ? What if your Option using only at one place? You will keep reference at None during application lifetime, isn’t it? I’m agree that it’s mostly not about Kotlin design, but sealed class often representing entities that suppose to die young.

are you struggling to recover 4-bytes in a JVM application?

3 Likes

That is probably just what you’re used to do and not a general requirement. There is no problem retaining generally useful immutable singletons (like for example an empty List or the equivalent of Optional.absent()) in memory and there is nothing wrong with using sealed classes for that.

Again, your code is error prone, because you create mutable objects, not because of objects inside sealed classes.

I would have no problem with Kotlin enforcing immutability on objects, apart from not being backwards compatible and hard to enforce. But that would make much more sense than needlessly restricting objects inside sealed classes.

1 Like

I disagree. Mutable singletons might often be bad, but they are extremely useful as long as they are used properly, e.g Random.Default.

1 Like

I consider Random.Default to be effectively immutable as it has no state you can set. Ideally its methods would call ThreadLocalRandom.current() and thus you wouldn’t need to save state inside the singleton.

Similar to Clock.systemUTC() in Java which also returns differing values but is immutable itself.

1 Like

Obviously yes

Totally agree. But i’m sure that it’s most often use case for sealed class. And in this use case Kotlin allows me to shoot out my leg easily - it’s major point

It is more than 4 bytes, I believe 16+ bytes? :sweat_smile:

Concern is if we start encouraging object immutable classes and object utils classes etc; then at some point codebase can have thousands of object singletons.

More than memory I think it is slippery slope that developer can add member variable which will add to memory leak, and issues around this global state.