我已尝试在Scala中实现StateMachine,但是我遇到了类型系统的问题,让我感到困惑。在下面的代码中,我需要让 guard 函数接受StateMachine的预期子类的参数。不幸的是,由于 FunctionN 参数的类型参数是逆变的,我不知道如何解决这个问题。
class Transition[S, +M <: StateMachine[S]](start: S, end :S, var guard: Option[M => Boolean]) { // COMPILER ERROR ABOVE LINE : ^^ covariant type M occurs in contravariant position in type => Option[M => Boolean] of method guard ^^ val startState = start val endState = end def willFollow(stateMachine: M, withGuard : Boolean) = // COMPILER ERROR ABOVE : ^^ covariant type M occurs in contravariant position in type M of value stateMachine ^^ if (!withGuard && guard == None) true; else (withGuard && guard.get(stateMachine)) } class EpsilonTransition[S, M <: StateMachine[S]](start: S,end :S) extends Transition[S, M](start, end, None) class StateMachine[S](transitions: Set[Transition[S, StateMachine[S]]], initialStates: Set[S]) { private val stateDrains = transitions.groupBy(_.startState); private var activeStates = initialStates def act() = { var entryStates = Set[S]() var exitStates = Set[S]() stateDrains.foreach {drain => val (exitState, transitionsOut) = drain // Follow non-epsilon transitions transitionsOut.filter(_.willFollow(this, true)).foreach {transition => exitStates += transition.startState entryStates += transition.endState } } // For all exit states we map the state to a set of transitions, and all sets are "flattened" into one big set of transitions // which will then filter by those which do not have a guard (epsilon transitions). The resulting filtered list of transitions // all contain endStates that we will map to. All of those end states are appended to the current set of entry states. entryStates = entryStates ++ (exitStates.flatMap(stateDrains(_)).filter(_.willFollow(this, false)).map(_.endState)) // Exclude only exit states which we have not re-entered // and then include newly entered states activeStates = ((activeStates -- (exitStates -- entryStates)) ++ entryStates) } override def toString = activeStates.toString } object HvacState extends Enumeration { type HvacState = Value val aircon, heater, fan = Value } import HvacState._ object HvacTransitions { val autoFan = new EpsilonTransition[HvacState, HVac](aircon, fan) val turnOffAc = new Transition[HvacState, HVac](aircon, fan, Some(_.temperature 75)) val HeaterToFan = new Transition[HvacState,HVac](heater, fan, Some(_.temperature > 50)) } import HvacTransitions._ class HVac extends StateMachine[HvacState](Set(autoFan, turnOffAc, AcToHeater, HeaterToAc, HeaterToFan), Set(heater)) { var temperature = 40 }
答案 0 :(得分:3)
您的转换仅适用于某种类型的状态,但也适用于某种类型的状态机,因此两种类型参数S
和M
。例如,最后,您的转换可能取决于温度,这是StateMachine的属性,而不仅仅是State。
不知何故,状态机应该只有与之兼容的转换。在没有温度的状态机上,不允许需要访问温度的转换。类型系统将强制执行该操作。但是,您的代码没有为此做出任何规定。
相反,你有一个StateMachine类得到一个转换集[S,StateMachine [S]]。这是有效的,但结果是StateMachine只接受“标准”转换,不需要机器上的任何特殊转换。您可以定义需要特殊机器(带温度)的转换,但即使它们与它兼容,机器也无法接受这些特殊转换。
然后是你的Hvac机器,它有温度。你试图给它传递特殊的过渡,转换只能在Hvac机器上运行(访问温度)。但是祖先构造函数被编写为仅接受标准转换。编译器拒绝这一点。它表明,如果过渡在M中是协变的,那就没问题了。这是真的,除了过渡在M中不能协变。它需要一台机器作为输入。协变过渡意味着如果它可以在非常特殊的机器上运行,它也必须能够在不太特殊的机器上运行。不是你想要的。
你需要做的是让StandardMachine类接受特殊的转换,它现在拒绝,但当然只有与机器兼容的转换(如果你不提供这种保证,编译器将拒绝代码)。 可能更简单的方法是将类型M放在机器中,以便您可以正确地表达约束。
这是一种可行的方法。首先,我们向StateMachine
添加一个类型参数class StateMachine[S, M](
例如,我们需要在引用StateMachine的任何地方添加M参数
class Transition[S, M <: StateMachine[S, M]]
或class Hvac extends StateMachine[HvacState, Hvac]
当然,构造函数参数变为
class StateMachine[S,M](transitions: Set[Transition[S, M]]], ...
这里我们说明机器可以进行过渡。除了我们没有。每次我们将this
机器转移到转换时,它仍然不会编译,例如:
transitionsOut.filter(_.willFollow(this, true)).foreach {transition =>
^^
type mismatch; found : StateMachine.this.type (with underlying type StateMachine[S,M]) required: M
好吧,我们介绍了类型M,但我们没有将一些M传递给机器,我们正在传递this
。这是一个StateMachine [S,M],它不需要是M.我们当然希望M成为机器的类型,但不一定是这种情况。我们不愿意声明StateMachine [S,M]必须是M.我们用自我类型来做:
class StateMachine[S, M](
transitions: Set[Transition[S, M]],
initialStates: Set[S]) { this: M =>
// body of the class
}
这:M =&gt;声明该类的每个实例都必须是泛型参数M的实例。我们强制它为M,因此错误消失。
然后M <: StateMachine[S, M
中的约束Transition
]挡住了我们,我们不需要它,我们只是删除它:Transition[S, M]
。或者,我们可以在StateMachine上添加相同的约束。
这充分利用了类型系统的问题,但是它可能更容易隔离机器状态,也就是说,而不是让自己type this: M =>
有一些{{1} },并将其传递给警卫而不是def machineState: M
。在这种情况下,this
将是Hvac
(或者更温和地封装温度而不是Double),
我的更改摘要:
转换:删除M上的约束,删除协方差:
类转换[S,M](...
EpsilonTransition:删除M
上的约束类EpsilonTransition [S,M]
StateMachine[HvacState, Double]
:添加类型参数StateMachine
,使用M
作为转换的参数,并将M
设置为自我类型:
class StateMachine [S,M](转换:设置[Transition [S,M]],initialStates:Set [S]){this:M =&gt;
M
:您复制的代码中缺少运算符,添加了turnOffAcc
<
:将自己添加为第二个通用参数:HVac
。此外,某些转换class HVac extends StateMachine[HvacState]
和AcToHeater
没有出现在您复制的代码中,因此我只删除了它们。 答案 1 :(得分:1)
你需要做这样的事情(它为我编译):
class Transition[S, M <: StateMachine[S]](start: S, end: S, var guard: Option[M => Boolean]) {
val startState = start
val endState = end
def willFollow[MM <: M](stateMachine: MM, withGuard: Boolean) =
if (!withGuard && guard == None) true
else (withGuard && guard.get(stateMachine))
}
基本上,Option[M => Boolean]
将采用任何取M或更大的函数并转到布尔值。例如,Any => Boolean
可行。这是逆变的。但是,您的willFollow
方法需要采用小于M的任何方法,因为它适用于至少类型为M的函数。这是一个更好的解释,因为您可能正在寻找一个:Why doesn't the example compile, aka how does (co-, contra-, and in-) variance work?