首页 文章

传递函数参数的协方差

提问于
浏览
3

我已经尝试在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
}

2 回答

  • 3

    你需要做这样的事情(它为我编译):

    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的任何东西,因为它更好的解释,因为你可能正在寻找一个:Why doesn't the example compile, aka how does (co-, contra-, and in-) variance work?

  • 1

    您的转换仅适用于某种类型的状态,但也适用于某种类型的状态机,因此两种类型参数 SM . 例如,最后,您的转换可能取决于温度,这是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
    

    }

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

    那么 Transition 中的约束 M <: StateMachine[S, M ]就会出现问题,我们不需要它,我们只需删除它: Transition[S, M] . 或者,我们可以对StateMachine设置相同的约束 .

    这充分利用了类型系统的问题,但是它可能更容易隔离机器状态,也就是说,而不是让自己 def machineState: M ,而不是 def machineState: M ,并将其传递给警卫而不是 this . 在这种情况下, Hvac 将是 StateMachine[HvacState, Double] (或者更温和的封装温度比Double),


    我的更改摘要:

    • 转换:删除M上的约束,删除协方差:

    class Transition [S,M](...

    • EpsilonTransition:删除对M的约束

    class EpsilonTransition [S,M]

    • StateMachine :添加类型参数 M ,使用 M 作为转换参数,并将 M 设置为自身类型:

    class StateMachine [S,M](过渡:设置[Transition [S,M]],initialStates:Set [S]){this:M =>

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

    • HVac :将自身添加为第二个通用参数: class HVac extends StateMachine[HvacState] . 此外,某些转换, AcToHeaterHeaterToAc 不会出现在您复制的代码中,所以我只是将它们删除了 .

相关问题