I couldn't find a better channel to discuss it... ...
# general-advice
j
I couldn't find a better channel to discuss it... So let me try here... Why does Kotlin don't allow sealed interfaces hierarchy to be defined in one module and have their implementation in a different module? I would like to have the behaviour defined in one place, but different implementations in different modules... I am thinking of writing a ksp processor to define a custom "when" for my interfaces because it makes sense to me to be able to split in different modules behaviour and implementations.
e
how would that work? if you have this in module 1
Copy code
sealed interface Foo
fun foo(foo: Foo): Any = when (foo) {
}
which is compiled first, then module 2
Copy code
class Bar : Foo
which is compiled later, then the code in module 1 is broken
j
The when would know about the interfaces defined in the sealed interface
e
modules are compiled independently. you can only know what's in your module (and its dependencies which were compiled first)
j
And there is nothing wrong with that.
e
so an implementation can't be in another module
I know, when is way more than what I did there
But that method requires knowledge of the interfaces only, not a particular implementation
a
what if your when statement is used as an assignment? It needs to be exhaustive if it's using a sealed interface, or have an 'else` branch if it's open.
e
if you can't guarantee it's closed within a module, you can't use
sealed
j
It needs to be exhaustive for the known interfaces in the sealed interface
Yeah, I know how it works today..
e
I don't understand how you imagine it would work any other way. there simply isn't visibility into future dependent modules, and JVM separate compilation means they could be replaced at runtime anyway
j
Ok man, let's see... The rule for the
when
is that it needs to know "all subclasses/interfaces in the sealed hierarchy", right ? That info is available at any module that can read that sealed interface. The rule applies at the definition of the sealed hierarchy, not at the client.
a
Let's say you have two modules:
library
and
application
(which depends on
library
) In
library
you have a sealed interface, and some function that performs some operation on the sealed interface.
Copy code
// library/src/main/kotlin/lib.kt

sealed interface MyData

interface AlphaData : MyData
interface BetaData : MyData

fun libOperation(data: MyData) {
  val name = when (data) {
    is AlphaData -> "alpha"
    is BetaData  -> "beta"
  }
  println("libOperation got data $name")
}
And then in
application
you're proposing that a new implementation is allowed.
Copy code
// application/src/main/kotlin/app.kt

interface GammaData: MyData
So a similar operation function in
application
can use a when + the sealed interface
Copy code
// application/src/main/kotlin/app.kt

fun appOperation(data: MyData) {
  val name = when (data) {
    is AlphaData -> "alpha"
    is BetaData  -> "beta"
    is GammaData  -> "gamma"
  }
  println("appOperation got data $name")
}
And then let's say that the application's main function calls both the library operation, and the application's operation
Copy code
// application/src/main/kotlin/app.kt

fun main() {
  val data = object : GammaData {}

  libOperation(data)
  appOperation(data)
}
But the library function doesn't know about
GammaData
because it was defined in another module. What should happen? Throw an error? But then the usefulness of the
when
statement is weakened.
j
Nothing would happen as that is not part of the sealed hierarchy for
MyData
. The interfaces that could (should) be allowed to be implemented in another module are the "children" interfaces. Or if the parent interface is allowed to be implemented, it should be by another sealed class/interface.
Independent of how it is today, architecture wise, there is value in having the separation of interfaces and implementations, right ? How do people solve this today ?
a
"Nothing would happen" - this is already achievable by making
MyData
open and adding an
else -> {}
branch on the when statements
Can you say more about what problem you're trying to solve? Can't you define the sealed interface, and all its implementations in one module?
Copy code
sealed interface MyData

interface AlphaData : MyData {
  val string: String
}

interface BetaData : MyData {
  val bool: Boolean
}

interface GammaData : MyData {
  val int: Integer
}
And then you can implement the subtypes in the same module, or another module.
Copy code
data class AlphaDataImpl(override val string: String) : AlphaData
data class BetaDataImpl(override val bool: Boolean) : BetaData
data class GammaDataImpl(override val int: Integer) : GammaData
j
My idea is to have different implementations in different modules... Like polymorphic json models... One impl could be using GSON, other kotlin serialisation
Or having State models that implement Parcelable on Android, but nothing in the JVM
r
Apologies if I've completely failed to understand what it is you are trying to achieve, but this compiles and runs for me: `module1`:
Copy code
package module1

sealed interface MyInterface

interface ChildA : MyInterface
interface ChildB : MyInterface

fun main() {
  val x: MyInterface = TODO()

  val result: Int = when (x) {
    is ChildA -> 1
    is ChildB -> 2
  }
}
`module2`:
Copy code
package module2

import module1.ChildA
import module1.ChildB

class GrandChildA1 : ChildA
class GrandChildA2 : ChildA
class GrandChildB1 : ChildB
class GrandChildB2 : ChildB
Obviously you can't have a direct child of
MyInterface
in
module2
, because then what would
x
be in `module1`'s
main
method?
j
fuuuuu, that really works! I tried something like that before, but I think I missed something, thanks for showing me that!