当前位置: 首页 > 知识库问答 >
问题:

传递的函数参数的协方差

戚飞雨
2023-03-14

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

共有1个答案

萧韬
2023-03-14

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

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

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

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

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

class StateMachine[S, M](

我们需要在引用StateMachine的任何地方添加M参数,例如类Transition[S,M<:StateMachine[S,M]]类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。这是一个状态机器[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

这充分利用了有问题的类型系统,但隔离机器状态可能更简单,也就是说,不使用self类型This:M=>,而是使用一些defmachineState:M,并将其传递给守卫,而不是This。在这种情况下,HVAC将是状态机[HvacState,Double] (或者比Double更显式的温度封装),

我得更改摘要:

>

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

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

    EpsilonTransition:删除对M的约束

    类EpsilonTransition[S,M]

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

  •  类似资料:
    • 本文向大家介绍JavaScript函数参数的传递方式详解,包括了JavaScript函数参数的传递方式详解的使用技巧和注意事项,需要的朋友参考一下 JavaScript使用一个变量对象来追踪变量的生存期。基本类型值被直接保存在变量对象内;而引用类型值则作为一个指针保存在变量对象内,该指针指向实际对象在内存中的存储位置。 基本类型值的传递 向参数传递基本类型值时,被传递的值会被复制给一个局部变量(即

    • 本文向大家介绍PHP函数参数传递的方式整理,包括了PHP函数参数传递的方式整理的使用技巧和注意事项,需要的朋友参考一下 在调用函数时,需要向函数传递参数,被传入函数的参数称为实参,而函数定义的参数称为形参。而向函数传递参数的方式有四种,分别是值传递、引用传递、默认参数和可变长度参数。 1. 值传递 值传递是 PHP 中函数的默认传值方式,也称为“拷贝传值”。顾名思义值传递的方式会将实参的值复制一份

    • 看两个例子: a = 1 def fun(a): a = 2 fun(a) print a # 1 a = [] def fun(a): a.append(1) fun(a) print a # [1] 所有的变量都可以理解是内存中一个对象的“引用”,或者,也可以看似c中void*的感觉。 通过id来看引用a的内存地址可以比较理解: a = 1 def fun(a): prin

    • 将函数传递给另一个函数的Scala示例缺少传递的函数(时间段)接受参数(x)的情况。 我怎样才能使上述代码工作? 编辑:我在oncepersecond中添加了一个x,以明确目标是传递整数。

    • 问题内容: 我很好奇Go中是否有可能。我有多种方法的类型。是否可以有一个函数,该函数需要一个方法参数,然后将其称为类型? 这是我想要的一个小例子: Go认为type 有一个称为的方法,而不是用传入的方法名称替换它。 问题答案: 是的,有可能。您有2(3)个选项: 规范:方法表达式 该表达式产生的功能与第一个参数等效,但具有一个显式接收器。它有签名。 在这里,方法接收器是显式的。您只需将方法名称(具

    • 本文向大家介绍浅谈Python中函数的参数传递,包括了浅谈Python中函数的参数传递的使用技巧和注意事项,需要的朋友参考一下 1.普通的参数传递 2.参数个数可选,参数有默认值的传递 参数sep的缺省值是'_' 如果这个参数不给定值就会使用缺省值 如果给定 则使用给定的值 需要注意 如果一个参数是可选参数 那么它后面所有的参数都应该是可选的,另外 可选参数的顺序颠倒依然可以正确的给对应的参数赋值