通过函数参数的协方差

我试图在斯卡拉实现一个StateMachine,但是我遇到了一个让我非常困惑的类型系统的问题。 在下面的代码中,我需要让守卫函数接受一个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
}

您的转换仅适用于特定类型的状态,但也适用于某种类型的状态机,因此两个类型参数SM 例如,最后,您可能会根据温度而进行转换,温度是StateMachine的一个属性,而不仅仅是国家的属性。

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

相反,你有StateMachine类获得一组转换[S,StateMachine [S]]。 这是有效的,但结果是StateMachine只接受“标准”转换,这不需要机器中的任何特殊功能。 您可以定义需要特殊机器的转换(具有温度),但机器无法接受这些特殊转换,即使它们与其兼容。

然后是你的Hvac机器,它有温度。 您尝试通过它特殊的转换,只能在Hvac机器上运行的转换(访问温度)。 但祖先构造函数的编写只接受标准转换。 编译器拒绝。 它表明如果Transition在M中是协变的,那就没问题。 这是事实,除了Transition不能在M中协变。它需要一台机器作为输入。 一个协变的过渡将意味着如果它可以在一台非常特殊的机器上运行,它也必须能够在一台不那么特别的机器上运行。 不是你想要的。

你需要做的是,StandardMachine类接受特殊的转换,它现在拒绝了,但当然只有与机器兼容的转换(如果你不提供这种保证,编译器会拒绝代码)。 可能更简单的方法是将M型放入机器中,以便可以正确表达约束。

这是一种可能的方法。 首先我们向StateMachine添加一个类型参数

class StateMachine[S, M](

我们需要无处不在地添加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 =>表示每个类的实例都必须是泛型参数M的一个实例。我们强制这是M,所以错误消失。

那么约束M <: StateMachine[S, M ]在Transition来的路上,我们并不需要它,我们简单地将其删除: Transition[S, M] 或者,我们可以在StateMachine上放置相同的约束。

这充分利用了类型系统的问题,因为它说明了这一点,但是隔离机器状态可能会更简单,也就是说,而不是自我type this: M => ,有一些def machineState: M ,并且把这个传给后卫而不是this 。 在这种情况下, Hvac将是一个StateMachine[HvacState, Double] (或温度比双一些更明确的封装),


我的更改摘要:

  • 过渡:去除M上的约束,去除协方差:

    班级转换[S,M](...

  • EpsilonTransition:删除M上的约束

    班级EpsilonTransition [S,M]

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

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

  • turnOffAcc :在复制的代码中缺少一个操作符,并添加<

  • HVac :将自己添加为第二个通用参数: class HVac extends StateMachine[HvacState] 。 另外,一些转换, AcToHeaterHeaterToAc不会出现在您复制的代码中,因此我只是将其删除。

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

    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类型的函数。这里有一个更好的解释,因为您可能正在寻找一个:为什么这个示例没有编译,又如何合作,反对和in)差异工作?

    链接地址: http://www.djcxy.com/p/66207.html

    上一篇: Covariance of a passed function argument

    下一篇: Scala inner types as abstract method arguments