petitviolet blog

    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