3

私は Scala で StateMachine を実装しようとしましたが、型システムで問題が発生し、かなり困惑しました。以下のコードでは、ガード関数が StateMachine の予想されるサブクラスの引数を受け入れるようにする必要があります。残念ながら、FunctionN引数の型パラメーターは反変であるため、これをどのようにやってのけるかはわかりません。

 
class Transition[S, +M <: StateMachine[S]](start: S, end :S, var guard: Option[M => Boolean]) {
// 行の上のコンパイラ エラー : ^^ 共変型 M がメソッド ガードの型 => オプション[M => Boolean] の反変位置で発生します ^^
  val startState = 開始
  val endState = 終了

  def willFollow(stateMachine: M, withGuard : Boolean) =
  // 上記のコンパイラ エラー : ^^ 共変の型 M が値 stateMachine の型 M の反変の位置にあります ^^
    if (!withGuard && ガード == なし) 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);
    プライベート var activeStates = initialStates

    デフ行為()= {
      var entryStates = Set[S]()
      var exitStates = Set[S]()

      stateDrains.foreach {ドレイン =>  
        val (exitState, transitionsOut) = ドレーン

        // 非イプシロン遷移に従います
        transitionsOut.filter(_.willFollow(this, true)).foreach {transition =>
          exitStates += transition.startState
          entryStates += transition.endState
        }
      }

      // すべての終了状態について、状態を遷移のセットにマップし、すべてのセットを 1 つの大きな遷移セットに「フラット化」します
      // 次に、ガードを持たないもの (イプシロン遷移) でフィルタリングします。トランジションのフィルタリングされた結果のリスト
      // すべてにマップ先の endStates が含まれています。これらの終了状態はすべて、現在の開始状態のセットに追加されます。
      entryStates = entryStates ++ (exitStates.flatMap(stateDrains(_)).filter(_.willFollow(this, false)).map(_.endState))

      // 再入力していない終了状態のみを除外します
      // そして、新しく入力された状態を含めます
      activeStates = ((activeStates -- (exitStates -- entryStates)) ++ entryStates)
    }

    オーバーライド定義 toString = activeStates.toString
}

object HvacState extends Enumeration {
     タイプ HvacState = 値
     val エアコン、ヒーター、ファン = 値
}
HvacState をインポートします。_

オブジェクト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](ヒーター, ファン, Some(_.temperature > 50))
}
インポート HvacTransitions._

class HVac extends StateMachine[HvacState](Set(autoFan, turnOffAc, AcToHeater, HeaterToAc, HeaterToFan), Set(heater)) {
  var 温度 = 40
}
4

2 に答える 2

3

遷移は特定のタイプの状態に対してのみ機能しますが、特定のタイプのステート マシンに対しても機能するため、2 つのタイプ パラメータSM. たとえば、最後に、State だけでなく StateMachine のプロパティである温度に依存する遷移があります。

どういうわけか、ステート マシンには、それと互換性のある遷移のみが含まれている必要があります。温度へのアクセスを必要とする遷移は、温度のないステート マシンでは許可されません。型システムはそれを強制します。ただし、コードはそのための準備をしていません。

代わりに、クラス StateMachine が Transition[S, StateMachine[S]] のセットを取得します。これは機能しますが、その結果、StateMachine は「標準」遷移のみを受け入れ、マシンから特別なものを必要としなくなります。特殊なマシン (温度) を必要とする遷移を定義できますが、互換性がある場合でも、マシンがそれらの特別な遷移を受け入れるものはありません。

次に、温度を持つ空調機が来ます。特別なトランジション、Hvac マシン (温度にアクセスする) でのみ動作するトランジションを渡そうとします。ただし、祖先コンストラクターは、標準遷移のみを受け入れるように記述されています。コンパイラはそれを拒否します。Transition が M で共変であれば問題ないことが示唆されます。Transition が M で共変でないことを除いて、これは真です。入力としてマシンを取ります。共変遷移とは、非常に特殊なマシンで動作できる場合、それほど特殊でないマシンでも動作できる必要があることを意味します。あなたが望むものではありません。

あなたがする必要があるのは、クラス StandardMachine が特別なトランジションを受け入れるようにすることです。これは現在拒否されていますが、もちろん、マシンと互換性のあるトランジションのみです (この保証を与えないと、コンパイラはコードを拒否します)。おそらくこれを行う簡単な方法は、制約を適切に表現できるように、型 M もマシンに入れることです。

ここに可能な方法があります。まず、型パラメーターを StateMachine に追加します。

class StateMachine[S, M](

class Transition[S, M <: StateMachine[S, M]]たとえば、StateMachine が参照されるすべての場所に 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 のインスタンスでなければならないことを示しています。これを強制的に M にすることで、エラーは消えます。

次に、制約M <: StateMachine[S, M] がTransition邪魔になります。それは必要ないので、単純に削除します : Transition[S, M]。または、StateMachine に同じ制約を適用することもできます。

これは、述べられているように問題のある型システムを最大限に活用しますが、マシンの状態を分離する、つまり、 self を持つ代わりに、type this: M =>いくつかの を持ちdef machineState: M、それを の代わりにガードに渡す方が簡単かもしれませんthis。この場合、Hvac(StateMachine[HvacState, Double] または、Double よりも明示的な温度のカプセル化)、


私の変更の概要:

  • 移行: M の制約を削除し、共分散を削除します。

    class Transition[S, M](...

  • EpsilonTransition : M の制約を削除

    クラス EpsilonTransition[S, M]

  • StateMachine: 型パラメーターを追加し、遷移のパラメーターとしてM使用し、自己型として設定します。MM

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

  • turnOffAcc: コピーして追加したコードに演算子がありません<

  • HVac: 自身を 2 番目のジェネリック パラメーターとして追加しました : class HVac extends StateMachine[HvacState]。また、トランジションの一部は、コピーしたコードには表示さAcToHeaterHeaterToAcないため、削除しました。
于 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 よりも小さい値を取る必要があります。おそらく探しているので、より良い説明を次に示します。 -、contra-、および in-) 分散作業?

于 2012-11-19T00:17:58.743 に答える