0

I am investigating Kotlin Sealed Classes.

I wish to force all my Sealed classes to have a standard value of NO_OP.

for example:-

Sealed Class 1

sealed class Operation {
    object NO_OP: Operation()
    class Add(val value: Int) : Operation()
    class Substract(val value: Int) : Operation()
    class Multiply(val value: Int) : Operation()
    class Divide(val value: Int) : Operation()
}

Sealed Class 2

sealed class ScreenState {
    object NO_OP: ScreenState()
    class Error : ScreenState()
    class Loading : ScreenState()
    data class Data(val someData: SomeData) : ScreenState()
}

Is there any approach I can take that will force all sealed classes in separate files to always specify the NO_OP value?

Hector
  • 4,016
  • 21
  • 112
  • 211

2 Answers2

2

If you want force override it as a field for a certain sealed class:

sealed class ScreenState {
    abstract val NO_OP: ScreenState

    class Error : ScreenState() {
       override val NO_OP: ScreenState
          get() = TODO("Your implementation")
    }
}

If you need something like an interface:

sealed class ScreenState : NoOp<ScreenState> {
    override val NO_OP: ScreenState = Error()

    class Error : ScreenState()
}

interface NoOp<T> {
   val NO_OP: T
}
Neo
  • 1,869
  • 1
  • 7
  • 20
2

That seems not possible at the moment in the language. However, we can move the NO_OP out from the sealed hierarchies and use Coproduct from the Arrow Library to define an ad-hoc sealed hierarchy:

import arrow.generic.coproduct2.Coproduct2

sealed class Operation {
    class Add(val value: Int) : Operation()
    class Substract(val value: Int) : Operation()
    class Multiply(val value: Int) : Operation()
    class Divide(val value: Int) : Operation()
}

object NO_OP

typealias Operations = Coproduct2<NO_OP, Operation>

Or we can fix one of the type parameters in Coproduct, and have:

typealias SomeThingWithNoOp<T> = Coproduct<NO_OP, T>

However, this is not ideal as it makes the hierarchy nested. With the top-level being Coproduct, and the nested level being the customized hierarchy. This may be addressed with Arrow-Meta's union type plugin in the near future.


Another way of seeing the problem is treating NO_OP as the sentinel value. So we may encode NO_OP as null or None(From Arrow Option):

sealed class Operation {
    class Add(val value: Int) : Operation()
    class Substract(val value: Int) : Operation()
    class Multiply(val value: Int) : Operation()
    class Divide(val value: Int) : Operation()
}

typealias Operations = Operation?
import arrow.core.Option

sealed class Operation {
    class Add(val value: Int) : Operation()
    class Substract(val value: Int) : Operation()
    class Multiply(val value: Int) : Operation()
    class Divide(val value: Int) : Operation()
}

typealias Operations = Option<Operation>

From my experience, encoding it as nullable may be easier to use, as Kotlin has the nullable type support built-in. While encoding it as Option, Coproduct or defining NO_OP in each of the sealed hierarchies would make it more obvious(especially by using Coproduct or NO_OP in the sealed hierarchy).


Ref:

Alpha Ho
  • 546
  • 4
  • 7