過去コラムZEN Style化①：再帰で書かれたkeyによるロジック切り替え（Strategy／StateパターンをElixirらしくスマートに）

[fukuoka.ex／kokura.ex](https://fukuokaex.connpass.com/)のpiacereです
ご覧いただいて、ありがとうございます :bow:

過去のQiita Elixirコラムを、[「Elixir ZEN Style」](https://qiita.com/zacky1972/items/619f39cc77fbb52b1bbf)で書き換えると、どうなるかのミニコラムです

今回は、@darui_kara さんが書かれた下記コラムでやってみます

[<b>[Elixir]キーワードリストのkeyによって処理を変える一例</b>](https://qiita.com/darui_kara/items/364b75f1cd8d28df6272)
https://qiita.com/darui_kara/items/364b75f1cd8d28df6272

なお、「Elixir ZEN Style」については、下記コラムをご覧ください

[<b>Elixir Zen スタイルプログラミング講座</b>](https://qiita.com/zacky1972/items/619f39cc77fbb52b1bbf)
https://qiita.com/zacky1972/items/619f39cc77fbb52b1bbf

# 本コラムの検証環境

本コラムは、以下環境で検証しています（Windowsで実施していますが、Linuxやmacでも動作する想定です）

- Windows 10
- Elixir 1.10.1 　　　　 ※[<b>最新版のインストール手順はコチラ</b>](https://qiita.com/piacere_ex/items/6714e1440e3f25fb46a1#%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99elixir%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)

# キーワードリストのkeyによってロジックを切り替える

キーワードリストのkey毎に、異なる処理を実行できるような切り替えを、登録済みkeyのリストと、関数のガードで実現しているのが、元コラムの内容です

モジュール内の「@変数」にkeyのリストを登録し、ガード「in」でリストとのマッチングを行います

```elixir
defmodule ArgumentsSample do

  @spec sample(Keyword.t) :: any
  def sample(kw) do
    keys = Keyword.keys(kw)
    values = Keyword.values(kw)

    IO.puts "Start..."
    sample(keys, values)
  end

  @match1 [:hoge, :huge]
  @match2 [:foo, :bar]

  defp sample([key|keys_tail], [value|values_tail]) when key in @match1 do
    IO.puts "@match1 match"
    IO.puts "#{key} = #{value}"
    sample(keys_tail, values_tail)
  end

  defp sample([key|keys_tail], [value|values_tail]) when key in @match2 do
    IO.puts "@match2 match"
    IO.puts "#{key} = #{value}"
    sample(keys_tail, values_tail)
  end

  defp sample(key, value) do
    IO.puts "...End"
  end
end
```

実行結果は、以下の通りです

```
iex> ArgumentsSample.sample hoge: 1, foo: 2, bar: 3, hoge: 4, huge: 5
Start...
@match1 match
hoge = 1
@match2 match
foo = 2
@match2 match
bar = 3
@match1 match
hoge = 4
@match1 match
huge = 5
...End
:ok
```

元のコードが、再帰で実装されているところを、Enum.eachでElixir ZEN Style化します

```elixir
defmodule ArgumentsSample do

  @spec sample( Keyword.t ) :: any
  def sample( kw ) when is_list( kw ) do
    IO.puts "Start..."
    kw |> Enum.each( & sample( &1 ) )
    IO.puts "...End"
  end

  @match1 [ :hoge, :huge ]
  @match2 [ :foo,  :bar  ]

  def sample( { key, value } ) when key in @match1 do
    IO.puts "@match1 match"
    IO.puts "#{ key } = #{ value }"
  end
  def sample( { key, value } ) when key in @match2 do
    IO.puts "@match2 match"
    IO.puts "#{ key } = #{ value }"
  end
end
```

実行結果は変わらず、同じ結果が出力されます

# 書き換えのポイント

- 再帰によるフロー制御を無くすことで、各sampleからフロー制御を除去でき、リスト分割も除去できました
- この結果、keysとvaluesを事前に取り出す中間処理が不要になりました
- 最終メッセージ出力を、再帰の終端（≒空リスト）として、最後のsampleでやっていたが、メイン側に移せました

# 効果

- コード行数が30%削減できました
- 制御フローと処理の分割で見通しが良くなりました

# このテクニックの使いどころ

いわゆる「Strategyパターン」や「Stateパターン」のような、入力や状態によって、ハンドラをスイッチするようなものであれば、このテクニックによって、非常に保守性が高く、見通しの良いコードとなります（いちいちクラスを作るウザさもElixirなので当然ありません）

キーワードリストに入力や状態を並べ、ハンドラを呼ぶkey毎にグルーピングを行えばOKです

なお、各リストにkeyが単数しか無い場合は、下記のように、関数パターンマッチとして直接書けば良いため、inによるガードも、「@変数」の登録も、不要になります

```elixir
…
  def sample( { :hoge, value } ) do
    IO.puts "@match1 match"
    IO.puts "#{ key } = #{ value }"
  end
  def sample( { :foo, value } ) do
    IO.puts "@match2 match"
    IO.puts "#{ key } = #{ value }"
  end
…
```

# ちなみに…

ガードの「in」の指定が、「@変数」で無いと、エラーになります

これは不思議な仕様のように見えますが、ガードに書ける構文には、割と制約がある（動的な関数や自己定義関数はまず使えない）ので、致し方ありません

恐らく、「@変数」は、コンパイル時に定数であるため、この制約に引っかからないのでしょう

# p.s.「いいね」よろしくお願いします

ページ左上の ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/4d515047-cc48-382e-c2b1-3ad0cc50dbbf.png) や ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/a4e3da58-70a3-4197-95a2-6a6906650d01.png) のクリックを、どうぞよろしくお願いします:bow:
ここの数字が増えると、書き手としては「ウケている」という感覚が得られ、連載を更に進化させていくモチベーションになりますので、もっとElixirネタを見たいというあなた、私達と一緒に盛り上げてください！:tada: