我正在构建HFSM并使用记录来跟踪状态:
type State<'a> =
{
CurrentNodeId : int
Data : 'a
}
当当前节点有另一个HFSM作为其中一部分时,我也需要跟踪该状态。所以我想做这样的事情
type State<'a> =
{
CurrentNodeId : int
Data : 'a
SubState : State<_> list
}
因为我并不十分关心SubState list
的类型,但错误的是:
此声明中不允许使用匿名类型变量
是否有不同的,更惯用的F#方式来实现这一目标,还是我必须提供不同的解决方案?
答案 0 :(得分:2)
Tomas是正确的,在F#中没有超级干净的方式来做这件事,但我认为可以比他的方法做得更好。基本的想法是你想要一个这样的类型:
type State<'a> = {
CurrentNodeId : int
Data : 'a
SubState : ∃'x. State<'x> list
}
除了F#不直接支持存在类型。事实证明,根据通用类型(F#支持)对存在类型进行编码是一种相当标准的方法:
∃'x.T<'x> ≡ ∀'z.(∀'x.T<'x> -> 'z) -> 'z
不幸的是,这实际上需要两种额外的类型,因为每种通用量化在唯一类型上被编码为单个通用方法:
type State<'a> = {
CurrentNodeId : int
Data : 'a
SubStates : SomeStateList
}
and SomeStateList =
abstract Apply : StateListApplication<'z> -> 'z
and StateListApplication<'z> =
abstract Apply : State<'x> list -> 'z
请注意,与Tomas的解决方案相比,这里有一个额外的类型,但好处是您不必为所有使用特定State
的>(Tomas&#39;编码基本上将SomeStateList
类型内联到State
,并将类型参数'z
提升为State
类型过程)。
现在我们希望能够将某些任意类型的状态列表打包为SomeStateList
:
let pack states = { new SomeStateList with member __.Apply a = a.Apply states }
我们可以演示如何使用这些类型与Tomas的递归depth
函数类似的定义。我们希望我们可以写
let rec depth = 1 + s.SubStates |> List.map depth |> List.fold max 0
但我们需要添加一些额外的语法来处理创建和应用我们的泛型类型(尽管如果你眯眼,核心逻辑仍然很明显):
// Full type annotation necessary here to get inner use to typecheck
let rec depth<'a> (s:State<'a>) : int =
1 + s.SubStates.Apply {
new StateListApplication<_> with
member __.Apply l = l |> List.map depth |> List.fold max 0
}
创建图表并应用该功能非常简洁:
depth {
CurrentNodeId = 1
Data = "test"
SubStates = pack [{ CurrentNodeId = 2
Data = 1uy
SubStates = pack []}]
}
答案 1 :(得分:0)
这在F#中确实没有很好的解决方案。在实践中,我可能只是将状态保持为obj
并将其拆分为I期望的类型,或者我将使用捕获可能情况的简单区分联合来存储状态。
有一种非常复杂的方法,可能会编码你想要的,但会使你的代码看起来非常可怕(我玩这个,我不认为我可以做得很好)。我们的想法是使用单个Invoke
方法存储接口,该方法是通用的,并且将使用存储在子代中的类型的type参数调用:
type InvokeWithState<'R> =
abstract Invoke<'T> : State<'T, 'R> list -> 'R
and State<'T, 'R> =
{ CurrentNodeId : int
Data : 'T
SubStates : InvokeWithState<'R> -> 'R }
因此,如果你想对State<'T, 'R>
进行一些操作,你可以编写InvokeWithState<'R>
界面,与孩子们做点什么。包含字符串的根状态和包含数字的子状态的简单HFSM如下所示:
let hfsm =
{ CurrentNodeId = 1
Data = "root"
SubStates = fun op ->
op.Invoke
[ { CurrentNodeId = 2
Data = 42
SubStates = fun op -> op.Invoke [] } ] }
我们的想法是SubStates
函数将调用Invoke
操作并为其赋值State<int, 'R>
('R
类型表示我们正在运行的操作的结果)
使用这些东西也非常难看 - 因为普通的F#递归函数不能用另一个类型参数作为参数调用自己。但你可以写这样的东西(计算FSM的深度):
type HfsmOp<'R> =
abstract Invoke<'T> : State<'T,'R> -> 'R
let rec depth =
{ new HfsmOp<int> with
member x.Invoke(state) =
let childDepth =
{ new InvokeWithState<int> with
member x.Invoke<'T>(states:State<'T, int> list) : int =
if List.isEmpty states then 0
else states |> List.map (fun s -> depth.Invoke<'T> s) |> List.max }
|> state.SubStates
childDepth + 1}
depth.Invoke hfsm
这可能正在做你想要的(一般),但它看起来很可怕,我不会真的建议这样做。我们在one place in Deedle中使用类似的技巧,但范围非常有限,因此它不会使整个代码库变得难看。但在大多数情况下,我会选择一个受歧视的工会或obj
。