Assuming this simple interface for our FSM:
type FSM[F[_], I, O] = Kleisli[F, I, O]
where I
is the input and O
is output, we can model it with Cats-effect's Ref as below:
object RefBasedFSM {
def apply[F[_]: FlatMap, I, S, O](
ref: Ref[F, S],
modifyStateOnInput: I => S => (S, F[O])
): FSM[F, I, O] =
Kleisli(input =>
ref
.modify(modifyStateOnInput(input))
.flatten
)
}
-
In the case of our state transitioning being synchronous, that is all there is to it. See this for a concrete example.
-
In the case of the state transition being asynchronous, ie, if there is a database layer, so for example:
type Potentially[S] = Either[Throwable, S]
type StateStore[F[_]] = Kleisli[F, State, Potentially[Unit]]
we can internally introduce this structure:
sealed trait AsyncState[S]
object AsyncState {
case class Value[S](state: S) extends AsyncState[S]
case class Updating[S](asyncState: Deferred[F, Either[Throwable, S]])
extends AsyncState[S]
}
and use an instance of Deferred
to handle the Async part. See here for a concrete example.
The sync impl, simply wants a I => S => (S, F[O])
to work, which is intuitive enough. In the case of Async impl however, as it stands, the usage of deferred is left to the developer and not managed by the code. Would be nice to come up with an abstraction that would manage that for us.