14
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

唐揚げ弁当とプログラミング (Elixir編)

14
Last updated at Posted at 2018-05-20

はじめに

少し昔の記事になりますが、こちらの記事で「関数型プログラミング」について紹介されています。

そこではお題として、「唐揚げ弁当をつまみ食いする」の実装があげられていました。ここにそれを再掲させていただくと、

課題:
唐揚げ弁当がいくつかあるとします。それぞれ唐揚げが複数入っています。
この中からx個の唐揚げをつまみ食いするプログラムを作りましょう。
つまみ食いはバレないようにするために、
その時点で最も唐揚げ数が多いお弁当から取るという仕様にします。

となります。

そこで、関数型プログラミング言語である Elixir で作ってみました。

コード

とりあえず、元記事では、オブジェクト指向プログラミングではBentooをクラスに、関数型プログラミングではデータタイプ的なJsonで取り扱っていました。そこで、Elixirでは何で扱うのが良いのか考えてみました。多分、普通に考えたら Elixirでも連想配列を使うのが良いのでしょう。
とはいえ、後々のプログラムの展開を考えると、Bentooクラスにはいろいろと新機能が追加されるかもしれません。だとすると、単純な連想配列とかではなく、応用の効くアクターモデルとして作った方が良いのではと考えて、次のように実装してみました。

defmodule Bentoo do
  use GenServer

  def init({dish,num}) do
    {:ok, {dish,num}}
  end

  def handle_call(:show, _, {dish, num}) do
    IO.puts "#{dish}#{inspect num}個"
    {:reply, nil, {dish, num}}
  end

  def handle_call(:data, _, state) do
    {:reply, state, state}
  end
end

初期化を除けば、表示と値参照の2つしかメッセージハンドラのない簡単な作りです。Bentooクラスに相当します。

そして、Bentooモジュールの配列を作ります。

    {:ok, pid1} = GenServer.start_link(Bentoo,{"唐揚げ",10})
    {:ok, pid2} = GenServer.start_link(Bentoo,{"唐揚げ",8})
    {:ok, pid3} = GenServer.start_link(Bentoo,{"唐揚げ",6})
    order = [pid1, pid2, pid3]

あとは、これを「つまみ食い」するたびに、変化させていけば良い訳なので、元記事の関数型プログラミングの方法のように、どれが一番唐揚げのたくさん入っている弁当かを調べるために

  def selectMaxBentoo(order) do
    order |> Enum.reduce(nil,
      fn(x,acc) ->
        if acc == nil do x else
          {_, a} = GenServer.call(x, :data)
          {_, b} = GenServer.call(acc, :data)
          if (a > b) do x else acc end
        end
      end)
  end

と、実際につまみ食いをするために

  def tsumamigui(order,pid) do
    order |> Enum.map(
      fn(x) ->
        if x == pid do
          {xdish,xnum} = GenServer.call(x,:data)
          GenServer.stop(x)
          {:ok, xpid} = GenServer.start_link(Bentoo,{xdish,xnum-1})
          xpid
        else
          x
        end
      end)
  end

を定義しました。

あとは、これをループで回せば良いので、

    1..5 |> Enum.reduce(order,
      fn(_,order) ->
        pid = selectMaxBentoo(order)
        order2 = tsumamigui(order,pid)
        IO.puts "------"
        for item <- order2 do
          GenServer.call(item, :show)
        end
        order2
      end)

こんな感じです。

コードの全体は、

defmodule Bentoo do
  use GenServer

  def init({dish,num}) do
    {:ok, {dish,num}}
  end

  def handle_call(:show, _, {dish, num}) do
    IO.puts "#{dish}#{inspect num}個"
    {:reply, nil, {dish, num}}
  end

  def handle_call(:data, _, state) do
    {:reply, state, state}
  end
end

defmodule Main do

  def selectMaxBentoo(order) do
    order |> Enum.reduce(nil,
      fn(x,acc) ->
        if acc == nil do x else
          {_, a} = GenServer.call(x, :data)
          {_, b} = GenServer.call(acc, :data)
          if (a > b) do x else acc end
        end
      end)
  end

  def tsumamigui(order,pid) do
    order |> Enum.map(
      fn(x) ->
        if x == pid do
          {xdish,xnum} = GenServer.call(x,:data)
          GenServer.stop(x)
          {:ok, xpid} = GenServer.start_link(Bentoo,{xdish,xnum-1})
          xpid
        else
          x
        end
      end)
  end

  def run do
    {:ok, pid1} = GenServer.start_link(Bentoo,{"唐揚げ",10})
    {:ok, pid2} = GenServer.start_link(Bentoo,{"唐揚げ",8})
    {:ok, pid3} = GenServer.start_link(Bentoo,{"唐揚げ",6})

    order = [pid1, pid2, pid3]

    1..5 |> Enum.reduce(order,
      fn(_,order) ->
        pid = selectMaxBentoo(order)
        order2 = tsumamigui(order,pid)
        IO.puts "------"
        for item <- order2 do
          GenServer.call(item, :show)
        end
        order2
      end)
  end

end

Main.run()

その結果は

------
唐揚げ9個
唐揚げ8個
唐揚げ6個
------
唐揚げ8個
唐揚げ8個
唐揚げ6個
------
唐揚げ7個
唐揚げ8個
唐揚げ6個
------
唐揚げ7個
唐揚げ7個
唐揚げ6個
------
唐揚げ6個
唐揚げ7個
唐揚げ6個

となりました。

感想

2010年代に登場した、いわゆる次世代言語(SwiftやKotlinなども含む)はあまり触ったことがなかったので、名前だけは知っていたElixirで初めてプログラミングしてみました。アクターモデル(とErlang)というのは、私がオブジェクト指向プログラミングを最初に学んだ時期にも紹介されていて、オブジェクト指向プログラミングの王道であるカプセル化とメッセージパッシングを持っていたので、もしかしたら、現在の主流であるオブジェクト指向プログラミング(クラスベースの)の代わりに広まっていた可能性もあった興味深い言語だと思っています。
現在のオブジェクト指向プログラミングでも、マルチスレッドのプログラミングは難しいですし、一方、並列処理や非同期処理の注目度が高まってきた昨今、Elixirへの期待も高まっていくかもしれないですね。

 また、比較のために、手続き指向型言語であるPascalで実装したこちらも良かったら見て下さい。
Prologで実装した記事はこちらです。

14
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?