3

我曾尝试在 Scala 中实现 StateMachine,但是我遇到了类型系统的问题,这让我很困惑。在下面的代码中,我需要让守卫函数接受一个预期的 StateMachine 子类的参数。不幸的是,由于FunctionN参数的类型参数是逆变的,我不知道如何解决这个问题。

 
类转换[S,+M <:StateMachine[S]](开始:S,结束:S,var guard:Option[M => Boolean]){
// 上面的编译器错误:^^ 协变类型 M 出现在方法保护的 type => Option[M => Boolean] 的逆变位置 ^^
  val startState = 开始
  val endState = 结束

  def willFollow(stateMachine: M, withGuard: Boolean) =
  // 上面的编译器错误:^^ 协变类型 M 出现在值 stateMachine 的类型 M 的逆变位置 ^^
    if (!withGuard && guard == None) true;
    否则(withGuard && guard.get(stateMachine))
}

类 EpsilonTransition[S, M <: StateMachine[S]](start: S,end :S) extends Transition[S, M](start, end, None)

类StateMachine[S](转换:Set[Transition[S,StateMachine[S]]],initialStates:Set[S]){
    私人 val stateDrains = transitions.groupBy(_.startState);
    私有 var activeStates = initialStates

    默认行为() = {
      var entryStates = Set[S]()
      var exitStates = Set[S]()

      stateDrains.foreach {排水 =>  
        val (exitState, transitionsOut) = drain

        // 跟随非 epsilon 转换
        transitionsOut.filter(_.willFollow(this, true)).foreach {transition =>
          exitStates += transition.startState
          entryStates += transition.endState
        }
      }

      // 对于所有退出状态,我们将状态映射到一组转换,并且所有集合都“扁平化”为一个大的转换集
      // 然后它将被那些没有保护的过滤器(epsilon 转换)。生成的过滤转换列表
      // 所有都包含我们将映射到的 endStates。所有这些结束状态都附加到当前的入口状态集。
      entryStates = entryStates ++ (exitStates.flatMap(stateDrains(_)).filter(_.willFollow(this, false)).map(_.endState))

      // 只排除我们没有重新进入的退出状态
      // 然后包括新进入的状态
      activeStates = ((activeStates -- (exitStates -- entryStates)) ++ entryStates)
    }

    覆盖 def toString = activeStates.toString
}

对象 HvacState 扩展枚举 {
     类型 HvacState = 值
     val 空调、加热器、风扇 = 值
}
导入 HvacState._

对象 HvacTransitions {
    val autoFan = new EpsilonTransition[HvacState, HVac](空调, 风扇)
    val turnOffAc = new Transition[HvacState, HVac](空调, 风扇, Some(_.temperature 75))
    val HeaterToFan = new Transition[HvacState,Hvac](加热器、风扇、一些(_.temperature > 50))
}
导入 HvacTransitions._

HVac 类扩展 StateMachine[HvacState](Set(autoFan, turnOffAc, AcToHeater, HeaterToAc, HeaterToFan), Set(heater)) {
  变量温度 = 40
}
4

2 回答 2

3

您的转换仅适用于某种类型的状态,但也适用于某种类型的状态机,因此两个类型参数SM. 例如,最后,您的转换可能取决于温度,这是 StateMachine 的属性,而不仅仅是 State 的属性。

不知何故,状态机应该只有与之兼容的转换。在没有温度的状态机上不应允许需要访问温度的转换。类型系统将强制执行。但是,您的代码对此没有任何规定。

相反,您让 StateMachine 类获得一组 Transition[S,StateMachine[S]]。这行得通,但结果是 StateMachine 只接受“标准”转换,这不需要机器提供任何特殊的东西。您可以定义需要特殊机器(带温度)的转换,但机器无法接受这些特殊转换,即使它们与它兼容。

然后是你的 Hvac 机器,它有温度。您尝试通过它特殊的转换,只能在 Hvac 机器上运行的转换(访问温度)。但是祖先构造函数被编写为仅接受标准转换。编译器拒绝了。这表明如果 Transition 在 M 中是协变的,那就没问题了。这是真的,除了Transition 在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。我们使用 self 类型来做到这一点:

class StateMachine[S, M](
   transitions: Set[Transition[S, M]], 
   initialStates: Set[S]) { this: M => 
 // body of the class

}

this: M => 声明类的每个实例都必须是泛型参数 M 的实例。我们将 this 强制为 M,因此错误消失了。

然后约束M <: StateMachine[S, M] inTransition进来了,我们不需要它,我们只需删除它 : Transition[S, M]。或者,我们可以对 StateMachine 施加相同的约束。

这充分利用了存在问题的类型系统,但隔离机器状态可能更简单,也就是说,不是拥有 self type this: M =>,而是拥有一些def machineState: M,并将其传递给守卫而不是this. 在这种情况下,Hvac将是一个StateMachine[HvacState, Double] (或比 Double 更明确的温度封装),


我的更改摘要:

  • 过渡:移除对 M 的约束,移除协方差:

    类转换[S, M](...

  • EpsilonTransition : 移除对 M 的约束

    类 EpsilonTransition[S, M]

  • StateMachine:添加类型参数MM用作转换的参数,并设置M为自我类型:

    class StateMachine[S, M](transitions: Set[Transition[S, M]], initialStates: Set[S]) { this: M =>

  • turnOffAcc:您复制的代码中缺少一个运算符,添加<

  • HVac : added itself as second generic parameter : class HVac extends StateMachine[HvacState]. Also, some of the transitions, AcToHeater and HeaterToAc do not appear in the code you copied, so I just removed them.
于 2012-11-19T02:00:57.277 回答
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 的函数。这里有一个更好的解释,因为您可能正在寻找一个:为什么示例不编译,也就是如何编译(co -, contra-, and in-) 方差有效吗?

于 2012-11-19T00:17:58.743 回答