I have the following definiition for a State Machine in F#:
type MyEvent = Event1 | Event2 | Event3
type MachineState<'event when 'event:comparison> =
{
Transitions: Map<'event, MachineState<'event>>
Data: int
//...other State stuff, like parent state, entry/exit actions etc
}
static member Default = {Transitions=Map.empty}
//simple helpers
let on event endState state =
{state with Transitions = state.Transitions.Add(event, endState)}
let withData data state = {state with Data = data}
The idea is, given a State and an Event, I will search for the Event key in the transitions map and if found, I will return the new State, otherwise will return the current. The states are defined like this:
let rec StateA =
MachineState<_>.Default
|> on Event1 StateB
|> withData 5
and StateB =
MachineState<_>.Default
|> on Event2 StateC
|> withData -999
and StateC =
MachineState<_>.Default
//|> on Event3 StateA //This actually gives a runtime error
|> withData 84
This gives me two problems: One error, FS0031, saying that StateA is part of its own definition and one warning, warn40, saying the objects will be evaluated for initialization-soundness at runtime.
I can fix the error by wrapping everything in lazy:
...Transitions: Map<'event, Lazy<MachineState<'event>>>...
let rec StateA =
lazy (MachineState<_>.Default
|> on Event1 StateB)
and StateB =
lazy (MachineState<_>.Default
|> on Event2 StateC)
and StateC =
lazy (MachineState<_>.Default
|> on Event3 StateA)
This doesnt fix the warning and feels kinda forced.
Is this the best way of going about this? Is there a better way to handle immutable, recursive structures? Or, more specifically, implementing a immutable HFSM?
This fiddle contains a running example: https://dotnetfiddle.net/TjjeBz