はじめに
少し昔の記事になりますが、こちらの記事で「関数型プログラミング」について紹介されています。
そこではお題として、「唐揚げ弁当をつまみ食いする」の実装があげられていました。ここにそれを再掲させていただくと、
課題:
唐揚げ弁当がいくつかあるとします。それぞれ唐揚げが複数入っています。
この中から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で実装した記事はこちらです。