有限オートマトン?
Finite State Machine(FSM)
ざっくり言うとイベントを受け取って状態遷移するステートマシン。
詳しくはWikipedia参照
FSMをActorで実装する
まさにそのためのakka.actor.FSM
があるので、これを使って実装する。
FSMに必要な型を定義する
必要となるのは以下の3つの型
-
State
- FSMの状態
-
Data
- FSMが内部的に持つ情報
-
Event
- FSMの状態遷移をキックするもの
今回は信号をFSMで実装してみる。
sealed trait SignalState
case object Red extends SignalState
case object Green extends SignalState
case object Yellow extends SignalState
sealed trait SignalData
object SignalData {
sealed case class SignalColor(value: String) extends SignalData
val RedData = SignalColor("red")
val YellowData = SignalColor("yellow")
val GreenData = SignalColor("green")
}
sealed trait SignalEvent
case object ChangeSignal extends SignalEvent
case object RetainSignal extends SignalEvent
FSMを実装する
先ほど用意したState/Data/Eventを利用してFSMを実装する。
FSM自体の実装はDSLだけで完結するため非常に楽。
class SignalChangeFSMActor extends Actor with FSM[SignalState, SignalData] {
// 初期状態
startWith(Red, RedData)
// Eventを受け取った時の状態遷移
when(Green) {
case Event(ChangeSignal, _) => goto(Yellow) using YellowData
case Event(RetainSignal, _) => stay
}
when(Yellow) {
case Event(ChangeSignal, _) => goto(Red) using RedData
case Event(RetainSignal, _) => stay
}
when(Red) {
case Event(ChangeSignal, _) => goto(Green) using GreenData
case Event(RetainSignal, _) => stay
}
// 状態遷移中の処理
onTransition {
case Green -> Yellow =>
println(s"WARN! green -> yellow: $stateData")
case Yellow -> Red =>
println(s"CAUTION! yellow -> red: $stateData")
case Red -> Green =>
println(s"OK! red -> green: $stateData")
}
// Actorが終了する際の処理
onTermination {
case StopEvent(_, _, _) =>
println("Shutting down FSM...")
}
// 初期化
initialize()
}
FSMの根幹となる状態遷移は、when
の引数に対するPartialFunction
で、goto
かstay
を返せば良い。
今回のサンプルでは破棄しているが、Event
の第二引数は現在の状態を表すstateData
と同じものが入ってくるらしい。
FSMなActorを使う
通常のActorと同様に生成してメッセージを送信すれば良い。
object ApplicationMain extends App {
val system = ActorSystem("MyActorSystem")
val signalActor = system.actorOf(Props[SignalChangeFSMActor], "fsm-signal")
signalActor ! ChangeSignal
signalActor ! RetainSignal
signalActor ! ChangeSignal
signalActor ! RetainSignal
signalActor ! ChangeSignal
signalActor ! RetainSignal
signalActor ! ChangeSignal
signalActor ! RetainSignal
Thread.sleep(3000)
system.terminate()
}
実行すると以下のように出力される。
OK! red -> green: SignalColor(red)
WARN! green -> yellow: SignalColor(green)
CAUTION! yellow -> red: SignalColor(yellow)
OK! red -> green: SignalColor(red)
Shutting down FSM...
所感
Actorの場合、context.become
とかcontext.unbecome
で状態を切り替えることが可能なため、わざわざakka.actor.FSM
を使わなくても同じようなことは実現できる。
akka.actor.FSM
ならDSLで状態遷移とイベントハンドラを宣言的に記述できるので、やや状態遷移が複雑なものなどはDSLをおぼえてでも使う価値はあるかも知れない。