blog.petitviolet.net

Actorで作る有限オートマトン

2016-08-24

QiitaScalaAkkaactor

有限オートマトン?

Finite State Machine(FSM) ざっくり言うとイベントを受け取って状態遷移するステートマシン。 詳しくはWikipedia参照

FSM を Actor で実装する

まさにそのためのakka.actor.FSMがあるので、これを使って実装する。

FSM に必要な型を定義する

必要となるのは以下の 3 つの型

  • State

    • FSM の状態
  • Data

    • FSM が内部的に持つ情報
  • Event

    • FSM の状態遷移をキックするもの

今回は信号を FSM で実装してみる。

State.scala
sealed trait SignalState
case object Red extends SignalState
case object Green extends SignalState
case object Yellow extends SignalState
Data.scala
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")
}
Event.scala
sealed trait SignalEvent
case object ChangeSignal extends SignalEvent
case object RetainSignal extends SignalEvent

FSM を実装する

先ほど用意した State/Data/Event を利用して FSM を実装する。 FSM 自体の実装は DSL だけで完結するため非常に楽。

SignalActor.scala
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で、gotostayを返せば良い。 今回のサンプルでは破棄しているが、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 をおぼえてでも使う価値はあるかも知れない。

from: https://qiita.com/petitviolet/items/9a6899a14a5219b43c5a