Elixirの復習

# はじめに

この記事は [Elixir Advent Calendar 2016](http://qiita.com/advent-calendar/2016/elixir) - Qiita の 23 日目の記事です。
###### Advent Calender初参加です。文章書くの大変辛いです。

さて、わたしはElixirを書くことが多く、よくどうすればElixirっぽく書けるのかなと思い色々なライブラリーを読んでは発見、読んでは発見の繰り返しをしている時期がありました。

そんな試行錯誤の中見つけたよくやる書き方、Elixirの機能の小ネタなどを私の復習も兼ねてご紹介できればと思っています。紹介する中にはコードの良し悪しで悪い部分があるかもしれません。そちらの評価はみなさんにしてもらえれば幸いです(すみません)。

- Map & Structs
- Keyword lists
- Anonymous functions
- Kernel in pipeline
- Comprehension
- Domain Specific Language(DSL)
- Built-in Protocol
- Linter

以下、一つずつ紹介していきます。

# Map & Structs

まず基本的なところから。

Elixirは関数型言語なので状態をModule内で動的に保持、扱う事ができません。[^1]

ですのでよく状態をコード内で引き回して扱ったりします[^2] 、こんな風に。


- 全ての関数にPattern MatchingもGuardも全くない状態

```elixir
defmodule Response do
  defstruct [:response, :results, :records]

  def wrap(response) do
    %__MODULE__{response: response}
  end

  def results(st) do
    ...

    %{st | results: results}
  end

  def records(st) do
    ...

    %{st | records: records}
  end
end

response = %Response{response: [%{}, %{} ...]}

response
|> Response.results
|> Response.records
```

これだと何でも通しちゃって問題がある場合もあるので、少し書き換えます。

- `results`, `records` は `%Response{}` のみ通します

```elixir
defmodule Response do
  defstruct [:response, :results, :records]

  def wrap(response) do
    %__MODULE__{response: response}
  end

  def results(%__MODULE__{} = st) do
    ...

    %{st | results: results}
  end

  def records(%__MODULE__{} = st) do
    ...

    %{st | records: records}
  end
end
```

もう少し柔軟に、`struct` を通したい場合は下記のように書けます

- structを生成すると暗黙的に`__struct__`がstructのfieldに追加されます
- `__struct__`には`Response` Moduleが入ります

```elixir
defmodule Response do
  defstruct [:response, :results, :records]

  def wrap(response) do
    %__MODULE__{response: response}
  end

  def results(%{__struct__: _} = st) do
    ...

    %{st | results: results}
  end

  def records(%{__struct__: _} = st) do
    ...

    %{st | records: records}
  end
end
```

もっと柔軟に、`struct`, `map` を通したい場合は

- Keyword listsなどは弾かれる
- results, records fieldが無いと弾かれる

```elixir
defmodule Response do
  defstruct [:response, :results, :records]

  def wrap(response) do
    %__MODULE__{response: response}
  end

  def results(%{results: _results} = st) do
    ...

    %{st | results: results}
  end

  def records(%{records: _records} = st) do
    ...

    %{st | records: records}
  end
end
```

色々応用がききますね


# Keyword lists

こちらも基本です。 以下の例では`key1`が取りたいです。

しかしKeyword listsは要素全体がみられるのでこれではエラーが出ます

```elixir
def do_something([key1: key1]), do: key1

resource = [key1: 1, key2: 2, key3: 3]
IO.inspect AdventCalender.do_something(resource)
# (FunctionClauseError) no function clause matching in AdventCalender.do_something/1
```

要素全てを定義するとエラーが出ませんが、面倒くさくてもう無理です

```elixir
def do_something([key1: key1]), do: key1
def do_something([key1: key1, key2: _]), do: key1
def do_something([key1: key1, key2: _, key3: ]), do: key1

resource = [key1: 1, key2: 2, key3: 3]
IO.inspect AdventCalender.do_something(resource)
# 1
```

リストのPattern Matchingですっきりします

```elixir
def do_something([{:key1, key1}| _]), do: key1
```

また、key2, key3が取りたいとき再帰をして値を取り出す方法も考えられると思いますが、あまり無理せずおとなしく `Keyword.get/2` などを使用して値だけを渡すか、Keyword listsを使用しないという選択肢が必要なのかもしれません。

# Anonymous functions

無名関数は色々な方法で定義することが出来ます。

### Partial Application of Function, Name/Arity

関数の部分適用, Name/Arity記法を使用すると簡略な形で無名関数を定義できます。

また関数はcapture operatorの`&`で束縛できます。

```elixir
iex(1)> fun1 = &Map.get/2       # Name/Arity
&Map.get/2

iex(2)> fun2 = &Map.get &1, &2  # Partial Application
&Map.get/2

iex(3)> fun3 = & &1[&2]
&Access.get/2

iex(4)> fun1 === fun2
true

iex(5)> fun2 === fun3
false

iex(6)> fun1.(%{key1: 1}, :key1)
1
```

この性質を利用すると


この形の定義が

```elixir
iex(1)> Enum.map 0..5, fn num -> is_integer num end
[true, true, true, true, true, true]
```

こうなって
(Partial Application)

```elixir
iex(2)> Enum.map 0..5, &is_integer &1
[true, true, true, true, true, true]
```

こうにもなります
(Name/Arity)

```elixir
iex(3)> Enum.map 0..5, &is_integer/1
[true, true, true, true, true, true]
```


### 応用すると

あまり意味のない例ですが

```elixir
defmodule Selector do
  def selector(enumerable) do
    Enum.random enumerable
  end

  def choice(enumerable, selector \\ &selector/1) do
    selector.(enumerable)
  end
end

iex(1)> Selector.choice 0..10
3

iex(2)> Selector.choice [0, 0, 1], &Enum.uniq/1
[0, 1]
```

このようにデフォルトではRandom Choice, 引数にselector関数を渡すことによって振る舞いを変更できる関数になりました

### case文の省略

無名関数のPattern Matchingを使用するとcase文を省略できます。

これが

```elixir
Enum.map enumable, fn element ->
  case element do
    {left, right} ->
      "#{[left, right]}"
    elm when is_atom(elm) ->
      "#{elm}"
    elm when is_number(elm) ->
      "#{elm}"
    num ->
      num
  end
end
```

こうなります

```elixir
Enum.map enumable, fn
  {left, right} ->
    "#{[left, right]}"
  elm when is_atom(elm) ->
    "#{elm}"
  elm when is_number(elm) ->
    "#{elm}"
  num ->
    num
end
```
スッキリしました


# Kernel in pipeline

Elixirで普段使用するデフォルトのMacro,FunctionはKernel Module内で定義されています。それらは使用時に自動的に我々の環境にImportされます。(if, unlessなど)

ですのでKernel Module経由でそれらの呼び出しをすることも可能です。

```elixir
iex(1)> Kernel.<>("My", "View")
"MyView"

iex(2)> "My" <> "View"
"MyView"

iex(3)> Kernel.in(1, [10])
false

iex(4)> 1 in [10]
false
```

このKernel Moduleを利用するとPipeline処理で一貫した書き方が出来るようになります。

これはあまり良い例が思いつかないですが。。

これが

```elixir
chose =
  1..100_00
  |> Enum.reduce(&+/2)

IO.inspect chose + 100_00
# 50015000
```

こうなります

```elixir
1..100_00
|> Enum.reduce(&+/2)
|> Kernel.+(100_00)

```
変数にassignする必要がなくなりました。素晴らしいほど小ネタです。


# Comprehension

基本的なことですがループ処理・フィルタリング処理・マッピング処理を同時にできる内包表記はとてもパワフルです。

たとえばあるModuleから任意の引数の数をもつ関数を抽出したい場合、内包表記を使わないでド直球に実装したらこんな感じになるでしょうか

```elixir
def extract_function(modules, arity) do
  Enum.reduce modules, [], fn module, acc ->
    functions =
      Enum.map(module.__info__(:functions), fn
        {function, ^arity} -> function
        _                  -> nil
      end)

    acc ++ Enum.filter_map(functions, & !!&1, & {module, &1})
  end
end

IO.inspect My.extract_function([Enum, Map], 4)
# [{Enum, :chunk}, {Map, :update}]
```

内包表記を使うと

```elixir
modules = [Enum, Map]
arity = 4

for module <- modules,
    {function, ^arity} <- module.__info__(:functions) do

  {module, function}
end
```
とてもシンプルになりました。使えるところには積極的に使っていきたいですね。

# Domain Specific Language(DSL)

Elixirに慣れてくるとDSLを書きたくなるときが来るかもしれません。

例えば下記のようなシンプルなDSLがあったとします。

```elixir
defmodule Definition do
  use Schema

  settings do
    analysis do
      analyzer :ngram_analyzer,
        tokenizer: "ngram_tokenizer",
        char_filter: ["html_strip", "my_iteration_mark"],
        filter: ["lowercase", "my_stemmer", "cjk_width"]
    end
  end
end

iex(1)> Definition.es_analyses
{:analyzer, :ngram_analyzer,
 [tokenizer: "ngram_tokenizer",
  char_filter: ["html_strip", "my_iteration_mark"],
  filter: ["lowercase", "my_stemmer", "cjk_width"]]}
```

このDSLはこのように書けるかもしれません。

- `use Schema` をした時に `import Schema` される
- Importによって全てのmacro(`settings`, `analysis`, `analyzer`)が使用可能な状態

```elixir
defmodule Schema do
  @doc false
  defmacro __using__(_) do
    quote do
      import Schema
    end
  end

  defmacro settings([do: block]), do: block
  defmacro analysis([do: block]), do: block
  defmacro analyzer(name, opts) do
    quote do
      def es_analyses, do: {:analyzer, :"#{unquote(name)}", unquote(opts)})
    end
  end
end
```

これは `analyzer` が直接呼べてしまい、`settings`, `analysis` が無くても動作してしまいます。また予期せぬ操作を防ぐために、ユーザーには`settings`だけ触らせたいです。

少し書き換えます。


- Import対象は`settings/1`のみ
- `settings`が呼ばれた時に全てのMacroをImport、その後Blockを展開する

```elixir
defmodule Schema do
  @doc false
  defmacro __using__(_) do
    quote do
      import Schema, only: [settings: 1]
      Module.register_attribute(__MODULE__, :es_analyses, accumulate: false)
    end
  end

  defmacro settings([do: block]) do
    quote do
      import Schema

      unquote(block)
      """
      # unquote(block)で下記のようなコードが最終的に展開される
      #
      Module.put_attribute __MODULE__, :es_analyses, {
        :analyzer,
        :erlang.binary_to_atom("#{:ngram_analyzer}", :utf8),
        [tokenizer: "ngram_tokenizer", char_filter: ["html_strip", "kuromoji_iteration_mark"], filter: ["lowercase", "kuromoji_stemmer", "cjk_width"]]
      }
      """

      def es_analyses, do: @es_analyses
    end
  end

  # For just imitational function.
  defmacro analysis([do: block]), do: block

  defmacro analyzer(name, opts) do
    quote do
      Module.put_attribute(__MODULE__, :es_analyses, {:analyzer, :"#{unquote(name)}", unquote(opts)})
    end
  end
end

iex(1)> Definition.es_analyses
{:analyzer, :ngram_analyzer,
 [tokenizer: "ngram_tokenizer",
  char_filter: ["html_strip", "my_iteration_mark"],
  filter: ["lowercase", "my_stemmer", "cjk_width"]]}
```

これで`analyzer`, `analysis`は直接さわれなくなり、望んだ形になりました。


# Built-in Protocol

Elixirにはいくつかの[Built-in protocols](http://elixir-lang.org/getting-started/protocols.html#built-in-protocols)が用意されています、この機能を使用すると`Enum`の関数,`to_stirng`などに独自の振る舞いを追加、統合することができます。(プロトコルの説明は[Blank](https://hexdocs.pm/elixir/Kernel.html#defprotocol/2-examples)実装で有名なこのページが詳しいです。)
今回はBuilt-in Protocolの中でよく使うであろう、Collectionを扱う`Enumerable`を紹介したいと思います。


当初登場した、Response Module(Struct)を覚えていますか？少し書き換えてありますがこちらです。

```elixir
defmodule Response do
  defstruct response: [], records: []

  def wrap(response) do
    %__MODULE__{response: response}
  end

  def records(st) do
    %{st | records: st.response}
  end
end
```

このResponse Moduleは下記のように使用することを想定しています。

```elixir
response =
  [%{}, %{}, %{} ...]
  |> Response.wrap
  |> Response.records

data =
  Enum.map response.records, fn record ->
    ...

    converted_value
  end

IO.inspect data
# [[], [], [] ...]
```


これ自体は何も問題のないコードなのですが、、、あえてさらにResponse Moduleに機能を付け加える、
[Enumerable Protocol](https://hexdocs.pm/elixir/Enumerable.html)を使用してResponse StructをCollectionとして扱わせたいと思います。

少し書き換えます。


###### enum, mapなどで処理をさせたいだけなので `count/1`, `member?/2` は実装していません。

```elixir
defmodule Response do
  defstruct response: [], records: []

  def wrap(response) do
    %__MODULE__{response: response}
  end

  def records(st) do
    %{st | records: st.response}
  end

  defimpl Enumerable, for: Response do
    def count(_page), do: {:error, __MODULE__}

    def member?(_page, _value), do: {:error, __MODULE__}

    def reduce(%Response{records: records}, acc, fun) do
      Enumerable.reduce(records, acc, fun)
    end
  end
end

response =
  [%{}, %{}, %{} ...]
  |> Response.wrap
  |> Response.records

data =
  Enum.map response, fn record ->
    ...

    converted_value
  end

IO.inspect data
# [[], [], [] ...]

IO.inspect Enumerable.count(response)
# {:error, Enumerable.Response}
```
Responseには`Enumerable Protocol`を実装してあるので、直接Collectionとして扱われるようになりました。

# Linter

local関数は括弧を付けましょうとか、関数の引数は5個までが望ましいとか

そういう細かいところはLinterに任せちゃいましょう

`環境`:
- https://github.com/rrrene/credo
- https://github.com/vim/vim
- https://github.com/neomake/neomake


###### Note: Vimスクリプトはよく分かっていませんのでより良く書き換えてください
```vim

let g:neomake_elixir_mycredo_maker = {
      \ 'exe': 'mixup',
      \ 'args': ['credo', '--format=flycheck', '--strict'],
      \ 'errorformat':
          \ '%E%f:%l:%c: %m,' .
          \ '%E%f:%l: %m'
      \ }
let g:neomake_elixir_mymix_maker = {
      \ 'exe' : 'mixup',
      \ 'args': ['compile', '--warnings-as-errors'],
      \ 'errorformat':
        \ '** %s %f:%l: %m,'.
        \ '%f:%l: warning: %m'
      \ }

let g:neomake_elixir_enabled_makers = ['mycredo', 'mymix', 'elixir']

function! Mixmeansnothing()
    if !exists('t:cwd')
       let t:cwd = system("mixup pwd")
    endif

    execute 'cd' t:cwd
endfunction

autocmd! BufWritePre * call Mixmeansnothing()
autocmd! BufWritePost * Neomake
```

### mixup部分

```zsh
#!/usr/bin/env zsh

findup() {
    FILE=$1
    while [ ! -e "${FILE}" ]; do
        if [ "$(pwd)" = "/" ]; then
            return
        fi
        cd ..
    done
    echo "$(pwd)"
}

# usage example
MIX=mix.exs
PDIR=$(findup ${MIX})

if [ "$1" = "pwd" ]; then
    echo $PDIR
    exit 0
fi

if [ "${PDIR}/${MIX}" != "/${MIX}" ]; then
    # echo "(cd ${PDIR}; mix $@)"
    cd ${PDIR}; mix $@
    exit 0
fi

echo "couldn't find file ${MIX}"
exit 1
```


![mycredo](https://dl.dropboxusercontent.com/spa/ohj2tplmz3dn81a/-6muy9_z.png "mycredo")


# おわりに

簡単ですが以上です、読んでくださって本当にありがとうございました。来年まで文章は書けません。

[^1]: [Agent](https://hexdocs.pm/elixir/Agent.html)などを使用すれば似たような事を実現出来ます
[^2]: [Ecto](https://github.com/elixir-ecto/ecto), [Plug](https://github.com/elixir-lang/plug)などが代表例