ヽ(´・肉・`)ノログ

How do we fighting without fighting?

すごいE本をElixirでやる(46)

第15章 レイジ・アゲインスト・ザ・有限ステート・マシーン から

はい.タイトルはレイジ・アゲインスト・ザ・マシーン という有名なバンドが元ネタだろう. 音楽がかっこよく,かつギターのトム・モレロさんが高学歴(ハーバード大学卒業)であることが印象に残っている.

第15章 レイジ・アゲインスト・ザ・有限ステート・マシーン

典型的な Erlang の有限ステートマシンは、 いくつかの関数(の状態)を実行してメッセージ(イベント)を受け取ることにより状態を変遷させるプロセスとして実装されています

15.1 有限ステートマシンとは何か

有限ステートマシン(FSM)は、マシンといっても機械ではなく、有限個のステート(状態)を持ったものです

状態と,その状態を変化させるイベント,イベントによって遷移する次の状態を記述したものが有限ステートマシン.

defmodule CatFSM do
  def start, do: spawn(fn -> dont_give_crap end)

  def event(pid, event) do
    ref = make_ref # won't care for monitors here
    send(pid, {self, ref, event})
    receive do
      {ref, msg} -> {:ok, msg}
    after 5000 ->
      {:error, :timeout}
    end
  end

  def dont_give_crap do
    receive do
      {pid, ref, _msg} -> send(pid, {ref, :meh})
      _ -> :ok
    end
    IO.puts("Switching to 'dont_give_crap' state")
    dont_give_crap
  end
end
Eshell V8.1  (abort with ^G)
Interactive Elixir (1.3.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(2)> import_file("cat_fsm.ex")
{:module, CatFSM,
 <<70, 79, 82, 49, 0, 0, 8, 120, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 232,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:dont_give_crap, 0}}
iex(3)> cat = CatFSM.start
#PID<0.89.0>
iex(4)> CatFSM.event(cat, :pet)
Switching to 'dont_give_crap' state
{:ok, :meh}
iex(5)> CatFSM.event(cat, :love)
Switching to 'dont_give_crap' state
{:ok, :meh}
iex(6)> CatFSM.event(cat, :cherish)
Switching to 'dont_give_crap' state
{:ok, :meh}
iex(7)>

猫が本当に見向きもしない(never give a crap)

確かに見向きもしない.

defmodule DogFSM do
  def start, do: spawn(fn -> bark end)

  def squirrel(pid), do: send(pid, :squirrel)

  def pet(pid), do: send(pid, :pet)

  def bark do
    IO.puts("Dog says: BARK! BARK!")
    receive do
      :pet -> wag_tail
      _ ->
        IO.format("Dog is confused")
        bark
    after 2000 ->
      bark
    end
  end

  def wag_tail do
    IO.puts("Dog wags its tail")
    receive do
      :pet -> sit
      _ ->
        IO.format("Dog is confused")
        wag_tail
    after 30000 ->
      bark
    end
  end

  def sit do
    IO.puts("Dog is sitting. Goooooooooood boy!")
    receive do
      :squirrel -> bark
      _ ->
        IO.puts("Dog is confused")
        sit
    end
  end
end
Eshell V8.1  (abort with ^G)
Interactive Elixir (1.3.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> import_file("dog_fsm.ex")
{:module, DogFSM,
 <<70, 79, 82, 49, 0, 0, 9, 224, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 1, 58,
   131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
   95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, {:sit, 0}}
iex(2)> pid = DogFSM.start
Dog says: BARK! BARK!
#PID<0.88.0>
iex(3)> Dog says: BARK! BARK!
iex(3)> Dog says: BARK! BARK!
iex(3)> Dog says: BARK! BARK!
iex(3)> DogFSM.pet(pid)
DogFSM.pet(pid)
:pet
iex(4)> DogFSM.pet(pid)
Dog is sitting. Goooooooooood boy!
:pet
iex(5)> DogFSM.squirrel(pid)
Dog says: BARK! BARK!
:squirrel
iex(6)> Dog says: BARK! BARK!
iex(6)> DogFSM.pet(pid)
Dog wags its tail
:pet
iex(7)> ## wait 30 seconds
nil
iex(8)> Dog says: BARK! BARK!
iex(8)> Dog says: BARK! BARK!
iex(8)> Dog says: BARK! BARK!
iex(8)> DogFSM.pet(pid)
DogFSM.pet(pid)
:pet
iex(9)> DogFSM.pet(pid)
Dog is sitting. Goooooooooood boy!
:pet
iex(10)>

こちらもうまく動いているようだ. 本と同じ動作になっている.