Skip to content

Instantly share code, notes, and snippets.

@BugInMyHEAD
Created August 22, 2025 17:36
Show Gist options
  • Select an option

  • Save BugInMyHEAD/a85aa7599eef1de9bae89ff5ba5fe7ef to your computer and use it in GitHub Desktop.

Select an option

Save BugInMyHEAD/a85aa7599eef1de9bae89ff5ba5fe7ef to your computer and use it in GitHub Desktop.
GenericTransState
package com.buginmyhead.chatter.fundamental.architecture
/**
* Trans-state is a coinage to indicate transition and state together in the state machine concept.
*
* Example for [GenericTransState], [GenericTransition], [GenericTransition.Disposal]:
* ```kotlin
* sealed interface Event
* sealed interface TransState : GenericTransState<TransState, State, Event>
* sealed interface State : TransState
*
* data class Transition(
* override val state: State,
* val sideEffect1: Any,
* val sideEffect2: Any,
* ) : TransState, GenericTransition<TransState, State, Event> {
*
* data object Disposal : Event, GenericTransition.Disposal
*
* }
* ```
*
* @param TS trans-state: the runtime type is either [GenericTransState] or [GenericTransition].
* @param S state
* @param E event
*/
@Suppress("UNCHECKED_CAST")
interface GenericTransState<TS : GenericTransState<TS, S, E>, S : TS, E> {
fun transitByEvent(event: E): TS
companion object {
/**
* @return
* [GenericTransition.state] if the receiver is [GenericTransition],
* or the receiver itself otherwise.
*/
@JvmStatic
val <TS : GenericTransState<TS, S, E>, S : TS, E> TS.state: S get() =
(if (this is GenericTransition<*, *, *>) state else this) as S
}
}
/**
* Transition in the state machine concept.
*
* Read the documentation of [GenericTransState] for an example.
*
* @param S state
* @param E event
*/
interface GenericTransition<TS : GenericTransState<TS, S, E>, S : TS, E> : GenericTransState<TS, S, E> {
val state: S
/**
* @return [state] for [Disposal] event, or `[transitByEvent]` of [state] for other events.
*/
override fun transitByEvent(event: E): TS =
if (event is Disposal) state else state.transitByEvent(event)
/**
* Implement with the custom event interface.
* The meaning of this event is to reset side effect state to default
* by calling [transitByEvent].
*
* Read the documentation of [GenericTransState] for an example.
*/
interface Disposal
}
package com.buginmyhead.chatter.fundamental.architecture
import com.buginmyhead.chatter.fundamental.architecture.GenericTransState.Companion.state
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
internal class GenericTransStateTest : FreeSpec({
"state of State should be itself" {
val state = StateA
state.state shouldBe state
}
"state of Transition should be state inside Transition" {
val state = StateA
val transition = Transition(
state = state,
)
transition.state shouldBe state
}
"transitByEvent of Transition with ordinary event works on state inside Transition" {
val state = StateA
val transition = Transition(
state = state,
)
transition.transitByEvent(EventA) shouldBe StateA.transitByEvent(EventA)
}
"transitByEvent of Transition with Disposal event returns state inside Transition" {
val state = StateA
val transition = Transition(
state = state,
)
transition.transitByEvent(Transition.Disposal) shouldBe state
}
})
private sealed interface Event
private sealed interface TransState : GenericTransState<TransState, State, Event>
private sealed interface State : TransState
private data object EventA : Event
private data object StateA : State {
override fun transitByEvent(event: Event): TransState = Transition(
state = StateB
)
}
private data object StateB : State {
override fun transitByEvent(event: Event): TransState = Transition(
state = StateA
)
}
private data class Transition(
override val state: State
) : TransState, GenericTransition<TransState, State, Event> {
data object Disposal : Event, GenericTransition.Disposal
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment