Recursion 在F中使用不可变记录的状态机#

Recursion 在F中使用不可变记录的状态机#,recursion,f#,immutability,fsm,Recursion,F#,Immutability,Fsm,我对F#中的状态机有如下定义: type MyEvent=Event1 | Event2 | Event3 类型机器房地产= { 过渡:地图> 数据:int //…其他状态信息,如父状态、进入/退出操作等 } 静态成员默认值={Transitions=Map.empty} //简单助手 let on事件结束状态= {state with Transitions=state.Transitions.Add(event,endState)} let with data data state={sta

我对F#中的状态机有如下定义:

type MyEvent=Event1 | Event2 | Event3
类型机器房地产=
{
过渡:地图>
数据:int
//…其他状态信息,如父状态、进入/退出操作等
}
静态成员默认值={Transitions=Map.empty}
//简单助手
let on事件结束状态=
{state with Transitions=state.Transitions.Add(event,endState)}
let with data data state={state with data=data}
其思想是,给定一个状态和一个事件,我将在转换映射中搜索事件键,如果找到,我将返回新状态,否则将返回当前状态。 各州的定义如下:

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
让rec StateA=
默认值
|>关于Event1 StateB
|>数据5
和StateB=
默认值
|>关于Event2 StateC
|>withData-999
和StateC=
默认值
//|>在Event3 StateA//上,这实际上给出了一个运行时错误
|>withData 84
这给了我两个问题: 一个错误FS0031表示StateA是其自身定义的一部分,另一个警告warn40表示将在运行时评估对象的初始化可靠性

我可以通过将所有内容包装在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)
…转换:映射>>。。。
让我们记录一个国家=
lazy(MachineState.Default)
|>在事件1上(b)
和StateB=
lazy(MachineState.Default)
|>关于事件2(c)
和StateC=
lazy(MachineState.Default)
|>关于事件3(a)
这并不能修复警告,感觉有点强迫

这是最好的方法吗?有没有更好的方法来处理不可变的递归结构?或者,更具体地说,实现不可变的HFSM


此fiddle包含一个运行示例:

最好将
状态建模为简单的数据项

类型状态=
|A
|B
|C
类型事件=
|事件1
|事件2
|事件3
类型StateMachine=
{

转换:映射这里的问题是,你想要构造一个不可变的递归值——一个内部某处包含自身引用的对象。这在函数式语言中很难做到,因为对象是不可变的。在某些有限的情况下,F#实际上可以做到这一点

在您的情况下,如果只使用原语值构造函数,即不调用任何函数,则可以使内置的F#递归初始化工作。我必须用普通的
列表
替换转换列表中的
映射
,但这是可行的:

type MyEvent=Event1 | Event2 | Event3
类型机器房地产=

{过渡:('event*MachineStatePerhaps将
State
定义为另一个DU?那么
StateMachine
只是一个转换的集合我喜欢@sdgfsdh的建议,但暂时坚持您当前的设计:您将每个转换独立添加到一个空机器上?这似乎不正确。相反,我认为您需要某种类型的转换折叠,这样你就可以在一台最终机器中积累所有的转换。@sdgfsdh你的意思是有一个专门匹配的结构(状态->事件->状态)?听起来很有趣,但你把状态作为DU是什么意思?每个状态都是DU中的一个条目?严格地说,我没有“状态机器”结构,我只有一个指向其他状态的状态。@fnzr您能描述StateA和StateB之间的区别吗?以及状态之间的转换是否应该是可能的(在您当前的设计中,它只是从默认状态到其他状态的一个转换)@SergeyBerezovskiy状态可能携带任意数据。检查这个fiddle,例如,我看到它正在运行,但是状态真的是简单的数据术语吗?例如,一个状态可能有入口和/或出口函数。我是否有另一个字段(在状态机内部)“ExitFunctions:Map”?这听起来很混乱……F#是一种多范式语言,因此可以将状态建模为具有函数的对象。然而,FP方法将状态保持为简单的数据项,然后对进入和退出的副作用使用单独的函数。与其说是副作用,不如说是副作用(进入/退出对我来说是一个糟糕的选择),但与状态关联的实际数据。在我的例子中,状态还带有树的根,因此当我到达该状态时,我会基于该树进行操作。操作本身没有副作用。能否将关联数据添加到
状态
类型?
...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)