2012-11-18 141 views
3

我試圖在斯卡拉實現一個StateMachine,但是我遇到了一個讓我非常困惑的類型系統的問題。在下面的代碼中,我需要讓後衛函數接受StateMachine的期望子類的參數。不幸的是,由於函數N的參數是逆變的,所以我不確定如何解決這個問題。通過函數參數的協方差

 
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

什麼原因特徵過渡是用+ M參數聲明的? –

+2

你爲什麼要'M'協變?只是好奇。 –

+0

M是協變的(+ M),因爲如果我不把它標記爲第三行到代碼snipplet的結尾,HVac類擴展StateMachine [HVacState] ...將顯示所有轉換的錯誤我傳遞給StateMachine構造函數。每個錯誤都說明,雖然我們有參數HVac的轉換,但預計會出現StateMachine [HVacState]的轉換。 –

回答

3

您的轉換僅適用於某種類型的狀態,而且對於某種類型的狀態機,所以兩個類型參數SM。例如,最後,您可能會根據溫度而進行轉換,溫度是StateMachine的一個屬性,而不僅僅是國家的屬性。

不知何故,狀態機應該只有與它兼容的轉換。在沒有溫度的狀態機上,不應允許需要訪問溫度的轉換。類型系統將執行該操作。但是,您的代碼沒有爲此提供任何規定。

相反,你有類StateMachine獲得一組轉換[S,StateMachine [S]]。這是有效的,但結果是StateMachine只接受「標準」轉換,這不需要機器中的任何特殊功能。您可以定義需要特殊機器的轉換(具有溫度),但機器無法接受這些特殊轉換,即使它們與其兼容。

然後來你的Hvac機器,它有溫度。您嘗試通過它特殊的轉換,只能在Hvac機器上運行的轉換(訪問溫度)。但祖先構造函數的編寫只接受標準轉換。編譯器拒絕。它表明如果Transition在M中是協變的,那就沒問題。這是事實,除了Transition不能在M中協變。它需要一臺機器作爲輸入。一個協變的過渡將意味着如果它可以在一臺非常特殊的機器上運行,它也必須能夠在一臺不那麼特別的機器上運行。不是你想要的。

您需要做的是讓StandardMachine接受特殊的轉換,它現在拒絕了,但當然只有與機器兼容的轉換(如果您不提供這種保證,編譯器會拒絕代碼) 。 也許更簡單的方法是將M型放入機器中,以便可以正確表達約束條件。

這是一種可能的方法。首先,我們添加一個類型參數的statemachine

class StateMachine[S, M](

我們需要添加參數m到處的StateMachine被引用,例如 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, MTransition進來的路上,我們並不需要它,我們簡單地將其刪除:Transition[S, M]。或者,我們可以在StateMachine上放置相同的約束。

這充分利用的問題類型系統,因爲它指出,但它可能是更簡單的隔離機器狀態,這是不是具有自我type this: M =>,有一些def machineState: M,並傳遞到後衛而不是this。在這種情況下,Hvac將是一個StateMachine[HvacState, Double](或溫度超過一倍的一些更明確的封裝),


我變動摘要:

  • 轉型:刪除M上的約束,消除協方差:

    class Transition [S,M](...

  • EpsilonTransition:M上

    除去約束

    類EpsilonTransition [S,M]

  • StateMachine:添加類型參數M,使用M作爲參數的轉換,並設置M自我類型:

    類的StateMachine [S,M](轉換:將[過渡[S,M]],initialStates:將[S]){這樣的:M =>

  • turnOffAcc:運營商在複製的代碼丟失,添加<

  • HVac:增加自身作爲第二個泛型參數:class HVac extends StateMachine[HvacState]。另外,部分轉換AcToHeaterHeaterToAc未顯示在您複製的代碼中,因此我只是將其刪除。
+0

我是我實際上期待你的建議工作,但我現在只看到一個錯誤(HVAC類擴展到StateMachine [HvacState,HVac](Set(autoFan,turnOffAc,AcToHeater,HeaterToAc,HeaterToFan),Set(heater))))...編譯器指出每個轉換都沒有正確的類型...說明過渡有參數HVAC在StateMachine預計。這似乎表明我需要協變。但是,+ M還是不是:同樣的錯誤。 –

+0

爲我編譯好。我添加了我對代碼所做更改的列表。 –

+0

首先,請原諒我的拙劣的複製粘貼工作,所以在粘貼過程中必須放棄上述元素,下次我會仔細檢查。 –

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的函數。這裏有一個更好的解釋,因爲您可能正在尋找一個:Why doesn't the example compile, aka how does (co-, contra-, and in-) variance work?

+0

有趣的想法。實際上,我還詢問了另一個StackOverflow,他建議我在轉換時保留+ M,而不是MM <:M ...我使用MM>:M.因爲如果Transition在M中不是協變的,那麼HVac擴展StateMachine無法編譯。因此,似乎Transition *必須在M中是協變的。但是,對於MM>:M ... guard.get(stateMachine)無法編譯。有些東西根本不對我想要做的事情,這是令人沮喪的,因爲這肯定是一個明智的設計。 –

+1

是的,我越看你想做什麼,我不認爲這會奏效。如果你開始用StateMachine [S]取代M,你可能會開始明白我的意思。令人沮喪。 – Noah

+0

事實上,特別是考慮我想要做的事情似乎並不複雜,但我已經遇到了類型系統的限制。 –