Skip to content

Instantly share code, notes, and snippets.

@BugInMyHEAD
Created August 22, 2025 17:36
Show Gist options
  • Save BugInMyHEAD/a85aa7599eef1de9bae89ff5ba5fe7ef to your computer and use it in GitHub Desktop.
Save BugInMyHEAD/a85aa7599eef1de9bae89ff5ba5fe7ef to your computer and use it in GitHub Desktop.

Revisions

  1. BugInMyHEAD created this gist Aug 22, 2025.
    75 changes: 75 additions & 0 deletions GenericTransState.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,75 @@
    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

    }
    68 changes: 68 additions & 0 deletions GenericTransStateTest.kt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,68 @@
    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

    }