Elixir歴1か月です
初心者の記事は今しか書けないとおもうので、怖いものなしに書いてみます。
先輩方のご意見など歓迎いたします。
Elixir練習用に、minesweeperを作ってみました。
この中のget_class_string()が力づくな関数なんです。
手続き型の言語の場合 if(xx){str += xxx}
のように追加していけばいいですが、そうはいかない。
この例と類似した問題は、今後もあると思うので、これを例題に、Elixirらしい記述を検討してみます。
初期バージョン
defmodule Tutorial002 do
def get_class_string(board, index) do
cell = elem(board.cells, index)
(["cell"]
++
(if board.clear, do: ["clear"], else: [])
++
(if board.gameover, do: ["gameover"], else: [])
++
(if (board.gameover or board.clear) and index in board.bomb, do: ["bomb"], else: [])
++
if cell == :marked, do: ["flag"], else: (if cell != :nomark, do: ["open"], else: []))
|> Enum.join(" ")
end
end
リファクタリング第一弾
条件にあった場合には、文字を追加。reduceでaccに文字を足しこんでいってみます。
reduceを使えばElixirらしくなるかも。
def get_class_string(board, index) do
cell = elem(board.cells, index)
[
{board.clear, "clear"},
{board.gameover, "gameover"},
{(board.gameover or board.clear) and index in board.bomb, "bomb"},
{cell == :marked, "flag"},
{cell not in [:nomark, :marked], "open"}
]
|> Enum.reduce("cell", fn({condition, str}, acc) ->
if condition, do: acc <> " " <> str, else: acc
end)
end
条件と文字列のペアーを列挙して、それをreduceで処理。
無理してreduceで処理できる形式のリストを作ってる感があります。
リファクタリン第二弾
一つの関数では一つの処理。この原則にのっとって、文字列の結合は、append_str_if関数に追い出してみます。
reduceの場合と構造的には同じですが、この名前の関数を使って、書いてみました。
すんなり書けます。
doctestもつけておきます。
defmodule Tutorial002 do
@moduledoc """
Documentation for `Tutorial002`.
"""
@doc """
iex> Tutorial002.get_class_string(%{
...> clear: false, gameover: false,bomb: [],
...> cells: {:nomark}
...> },0)
"cell"
iex> Tutorial002.get_class_string(%{
...> clear: true, gameover: false,bomb: [],
...> cells: {0}
...> },0)
"cell clear open"
iex> Tutorial002.get_class_string(%{
...> clear: false, gameover: true,bomb: [],
...> cells: {0}
...> },0)
"cell gameover open"
iex> Tutorial002.get_class_string(%{
...> clear: true, gameover: false,bomb: [0],
...> cells: {0}
...> },0)
"cell clear bomb open"
iex> Tutorial002.get_class_string(%{
...> clear: false, gameover: false,bomb: [],
...> cells: {:marked}
...> },0)
"cell flag"
iex> Tutorial002.get_class_string(%{
...> clear: false, gameover: false,bomb: [],
...> cells: {:nomark, :marked}
...> },1)
"cell flag"
"""
def get_class_string(board, index) do
cell = elem(board.cells, index)
"cell"
|> append_str_if(board.clear, "clear")
|> append_str_if(board.gameover, "gameover")
|> append_str_if((board.gameover or board.clear) and index in board.bomb, "bomb")
|> append_str_if(cell == :marked, "flag")
|> append_str_if(cell not in [:nomark, :marked], "open")
end
def append_str_if(acc, true, str) do
acc <> " " <> str
end
def append_str_if(acc, false, _str) do
acc
end
end
|>
使うと、Elixirらしくなるってわけではないけど、この例は、うまくはまった。
append_str_if()のような部品を用意しないといけないのがちょっと不満。もしかしてkernelの関数やライブラリーに助けになるのがあるのかも?
Elixirって楽しい