LoginSignup
22
4

More than 1 year has passed since last update.

Elixirらしいif(xx){str += xxx}はこれだ(※個人の感想です)

Posted at

Elixir歴1か月です

初心者の記事は今しか書けないとおもうので、怖いものなしに書いてみます。
先輩方のご意見など歓迎いたします。

Elixir練習用に、minesweeperを作ってみました。
この中のget_class_string()が力づくな関数なんです。

手続き型の言語の場合 if(xx){str += xxx}のように追加していけばいいですが、そうはいかない。
この例と類似した問題は、今後もあると思うので、これを例題に、Elixirらしい記述を検討してみます。

初期バージョン

tutorial.ex
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らしくなるかも。

tutorial1.ex
  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もつけておきます。

tutorial2.ex
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って楽しい

22
4
2

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
22
4