Elixir Tips

すぐ忘れる・困った・ハマった・調べるのに苦労した・最初に知りたかったポイントを雑多にまとめた自分用メモ。随時更新。

（一部の用語などは適当。あまり裏を取っていないので注意）

## IEx

### ctrl+D でシェルを終了したい

できない。Erlang にその機能がない上に、実装される予定もない。

https://github.com/elixir-lang/elixir/issues/7310
https://github.com/erlang/otp/pull/983

### recompile

アプリ全体を再コンパイルする。ただしアプリ自体は再起動せず、あくまでもコード（関数）を再コンパイルするだけであることに注意。シェル上で特定の関数を実行しながら開発・デバッグする際に使うととても便利。

```elixir
iex> recompile
iex> recompile; MyProject.do_something()
```

### 履歴を残す

IEx シェルの履歴を残したい場合は環境変数 `ERL_AFLAGS` を設定する。

```sh
export ERL_AFLAGS="-kernel shell_history enabled"
```

### 出力に色を付ける

```sh
vi ~/.iex.exs
```

```elixir
IEx.configure colors: [ enabled: true, eval_result: [ :cyan, :bright ] ]
```

### プロジェクト毎にデフォルトで実行する処理を指定する

プロジェクト直下に `.iex.exs` を用意する。

```sh
vi .iex.exs
```

デバッグ時によくつかうモジュールを読み込んでおくと便利。

```elixir
import :timer
import Myproject.Util
alias Myproject.Repo
alias Myproject.{User, Blog, Article}
```

### プロンプトを変更する

プロジェクト直下の `.iex.exs` へ追記する。ここでは `my-app` という名前を追加した。

```elixir
IEx.configure(
  default_prompt: "%prefix(%counter) my-app>",
  alive_prompt: "%prefix(%node)%counter my-app>"
)
```

### 結果値を省略せずすべて表示する

```elixir
iex> IEx.configure(inspect: [limit: :infinity])
```

### 稼働中のプロセスへ IEx をアタッチする

まず、名前を付けてプロセスを起動する。ここでは `app` とした。

```sh
elixir --sname app --cookie mysecretcookie -S mix run --no-halt
```

`--cookie` の値を指定しない場合は　`~/.erlang.cookie` が作成され使われるようだ。

例えば `asdf` + `systemd` なら次のようになるだろう。
（場合によっては `MIX_HOME` もセットする必要があるかもしれない）

```ini
Environment = MIX_ENV=prod
Environment = PATH=/opt/asdf/shims:/usr/local/bin:/usr/bin:/bin
WorkingDirectory = /path/to/app
ExecStart = /opt/asdf/shims/elixir --sname app --cookie mysecretcookie -S mix run --no-halt
```

別の iex を立ち上げ、接続したいプロセス名を `--remsh` オプションで指定して接続する。
ここでは `IEx` プロセス自身に `console` という名前を付けている（省略不可）。

```sh
iex --sname console --cookie mysecretcookie --remsh "app@`hostname -s`"
```

#### Docker 環境

`--sname` の代わりに `--name app@localhost` として起動する。

```Dockerfile
CMD ["elixir", "--name", "app@localhost", "--cookie", "mysecretcookie", "-S", "mix", "run", "--no-halt"]
```

同様に `--remsh` に渡せば接続できる。

```sh
docker compose exec myapp iex --sname console --cookie mysecretcookie --remsh app@localhost
```

IO.inspect
----------

### ラベル

出力にラベルを付けられるのでデバッグ時に便利。

```elixir
iex> [1, 2, 3]
...> |> IO.inspect(label: "before")
...> |> Enum.map(&(&1 * 2))
...> |> IO.inspect(label: "after")
...> |> Enum.sum
before: [1, 2, 3]
after: [2, 4, 6]
12
```

### 省略せずにすべての値を表示する

デフォルトは 50 となっているが、そのリミットに `:infinity` を指定する。

```elixir
1..500 |> Enum.map(& &1 * 2) |> IO.inspect(limit: :infinity)
```

Kernel
------

### 自作の関数名がカーネルの関数名とぶつかる場合

たとえば自作の `trunc/1` を実装した場合にはカーネルの `trunc/1` がすでに存在するのでエラーになる。そこで、カーネルの `trunc/1` だけをインポートしないようにして回避できる。

```elixir
import Kernel, except: [trunc: 1]
```

### Kernel.inspect

tuple や map などを文字列表現にしたい時に使える。Logger に渡す時に便利。

```elixir
require Logger

iex> data = {:hello, "world"}
iex> Logger.debug("#{inspect(data)}")
18:20:03.730 [debug] {:hello, "world"}
```

`IO.inspect` とは別モノなので混同しないよう注意。

デバッグ情報
------------

### observer を起動する

GUI のアプリ稼働状況画面を開く。

```elixir
iex> :observer.start
```

値を見る
--------

手っ取り早くプリントする。`nil` を渡しても問題ない。

```elixir
IO.puts("hello")
IO.inspect(%{foo: 42})
```

nil の扱い
----------

### nil かどうか

```elixir
is_nil(nil)    # -> true
is_nil("")    # -> false
```

### ブロックの返値

ブロックが値を明示的に返さない場合は `nil` が返る。これは Ruby と同じ。

```elixir
@spec run() :: non_neg_integer | nil
def run do
  100

  if false do
    42
  end
end

run()    # -> nil
```

### nil ガード？

（正式な機能名はわからない）

```elixir
result = nil || 42    # -> 42
```

文字列
------

Elixir には文字列という概念はなく、すべてバイナリ列である。よって、`is_string` といったものは存在しない。一般的な意味での文字列かどうかを調べるには `is_binary/1` を使う。

```elixir
is_binary("hello")    # -> true
is_binary(<<102>>)    # -> true
```

また、`"hello"` は Elixir の文字列（バイナリ列）だが `'hello'` は Erlang の文字列である。厳密に使い分けること。

```elixir
is_binary("hello")    # -> true
is_binary('hello')    # -> false
```

これらを比較したい場合は `==` ではなく `String.equivalent?/2` を使うこと。

```elixir
"hello" == 'hello'    # -> false
```

ただし 1.11 までは `String.equivalent?/2` で両者を直接比較できたが、1.12 以降は FunctionClauseError となるので注意。事前に変換すること。

```elixir
String.equivalent?('this', "this")    # -> true（1.11 以前）
String.equivalent?('this' |> to_string(), "this")    # -> true
```


文字列としてのバイト列は数値のリストとも取れるので、IEx 上で次のようなことが起きてびっくりするだろう。

```elixir
iex> [65, 66, 67, 68, 69]    # -> 'ABCDE'
```

つまり、ビットの羅列をどう解釈するかという視点の問題だ。また、バイトのリストとしてそのまま表示もできる。

```elixir
iex> [65, 66, 67, 68, 69] |> IO.inspect(charlists: :as_lists)
```

### 文字列として扱えるかどうかを検査する

```elixir
String.valid?("hello")    # -> true
String.valid?("")    # -> true
String.valid?('world')    # -> false Erlang 文字列。要注意
String.valid?(nil)    # -> false
String.valid?(100)    # -> false
String.valid?(<<128, 129, 130>>)    # -> false
```

### Erlang との相互変換

Erlang 製ライブラリへ値を渡す時などに変換を要する場面が多々ある。

```elixir
"hello" |> String.to_charlist()    # -> 'hello'
```

Erlang 文字列から Elixir 文字列へ変換するには単純に `Kernel.to_string/1` する。

```elixir
'world' |> to_string()    # -> "world"
```

### 空かどうか

`length/1` が手っ取り早いが、最適解ではないかもしれない。
empty? のようなものは（なぜか）ない。

```elixir
String.length("hello") == 0    # -> false
```

### サイズ

マルチバイト文字列に対して実行すると両者で結果が違うことに注意。

```elixir
"こんにちは" |> String.length()    # -> 5
"こんにちは" |> byte_size()    # -> 15
```

ただし、`String.length/1` に `nil` を渡すと `FunctionClauseError` が、`byte_size/1` に `nil` を渡すと `ArgumentError` がそれぞれ発生するので注意。関数のパターンマッチや `is_nil/1` などで事前にチェックすること。

### 連結

```elixir
"hello" <> " " <> "world"    # -> "hello world"
"hello" |> Kernel.<>(" ") |> Kernel.<>("world")    # -> "hello world"
```

### インターポレーション

Ruby ライクに可能。

```elixir
{foo, bar} = {"result", 42}
"#{foo} is #{bar}"    # -> "result is 42"
```

値が `nil` であっても問題ない。 

```elixir
foo = nil
"#{foo}"    # -> ""
```

### フォーマット

sprintf のようなものはない。Erlang の `:io.format` などを駆使すればできるようだが、必要なら素直に [ExPrintf](https://github.com/parroty/exprintf) のようなライブラリを使うのがよさそう。

### chomp

chomp はないので代わりに `String.trim/1` を使う。連続している改行文字を削除する。

```elixir
"hello\n" |> String.trim()    # -> "hello"
"hello\n\n\n" |> String.trim()    # -> "hello"
```

### 含まれるか

```elixir
"hello" |> String.contains?("e")    # -> true
"this is a pen" |> String.contains?("is a")    # -> true
```

リストを渡すと、（`AND` ではなく）`OR` で検査される。

```elixir
"hello" |> String.contains?(["e", "o"])    # -> true
"hello" |> String.contains?(["e", "x"])    # -> true
"hello" |> String.contains?(["x", "y"])    # -> false
```

これを `AND` で検査したければ一手間かかるだろう。

```elixir
["e", "x"] |> Enum.all?(fn p -> "hello" |> String.contains?(p) end)   # -> false
```

### 数値へ変換

変換できなければ `ArgumentError` が返る。

```elixir
"42" |> String.to_integer()    # -> 42
"42.1" |> String.to_float()    # -> 42.1

"foo" |> String.to_integer()    # ArgumentError
nil |> String.to_integer()    # ArgumentError
"" |> String.to_integer()    # ArgumentError
```

数値かどうか怪しいものは `Integer.parse/1` や `Float.parse/1` で検査するのがよい。

また、空文字列を渡すと（0 への暗黙の変換はなく）エラーになるので、空になる可能性があるなら必ず検査すること。つまり、外部（ユーザ）からの入力値を変換する際はほとんどの場合で常にチェックする必要があるだろう。

```elixir
str = "foo"
result =
  case Integer.parse(str) do
    {n, _} when is_integer(n) -> n
    :error -> 0
  end
```

変換を要する機会は多いので次のような簡易的なラッパー関数を用意することにした。

```elixir
@spec integerize(String.t()) :: integer | nil
def integerize(str) when is_binary(str) do
  case Integer.parse(str) do
    {n, _} when is_integer(n) -> n
    :error -> nil
  end
end

@spec floatize(String.t()) :: float | nil
def floatize(str) when is_binary(str) do
  case Float.parse(str) do
    {f, _} when is_float(f) -> f
    :error -> nil
  end
end

"200" |> integerize || 0    # -> 200
"two hundreds" |> integerize || 0    # -> 0
```

### Atom へ変換

```elixir
"foo" |> String.to_atom()    # -> :foo
```

### 文字コード変換

`iconv` もしくは `eiconv` を入れる。

```elixir
{:iconv, "~> 1.0.10"}
```

```elixir
:iconv.convert("shift_jis", "utf-8", "なにかSJISの文字列")    # -> sjis から utf-8 へ
```

存在しないエンコーディング（空文字列を含む）を渡しても黙って元の文字列を返すだけなので注意。昔はエンコーディングは case insensitive だったが今試すと sensitive のようだ。

有効なエンコーディングの一覧はシェルで次のようにすれば確認できる。

```sh
iconv -l
```

### Unicode 関連

（そのうち調べる）

```elixir
 :unicode.characters_to_nfd_binary("だ") == "だ"    # -> false
```

### UTF-8 の BOM を除く

文字列の先頭にある BOM を除く。BOM が存在しない場合はなにも起こらない。

```elixir
str |> String.replace_prefix("\uFEFF", "")
```

### 最初の文字だけ大文字にする

PHP でいうところの `ucfirst` と同等の処理。

```elixir
def upcase_first(<<first::utf8, rest::binary>>) do
  String.upcase(<<first::utf8>>) <> rest
end
```

その他、大文字小文字などのケースを変換したい場合は [ProperCase](https://github.com/johnnyji/proper_case) などを使うとよい。

### 文字列のエンコーディングを判別する

拙作の [CharsetDetect](https://github.com/ryochin/charset_detect) を使うのが楽で確実だろう。

```elixir
{:charset_detect, "~> 0.1.0"}
```

```elixir
content |> CharsetDetect.guess
{:ok, "Shift_JIS"}
```

Atom
----

### 定義する

```elixir
:foo
:"my-project"    # 記号などが含まれる場合はクォートする
```

### チェック

```elixir
is_atom(:foo)    # -> true
is_atom("foo")    # -> false
```

いくつかの定義値などは atom で実装されているため？次のようになるので注意すること。

```elixir
is_atom(nil)    # -> true (!) なので注意
is_atom(true)    # -> 同上
is_atom(false)    # -> 同上
```

こういった罠のため、`is_atom` は基本的に使わないようにすべきだろう。とくに、`nil` になる可能性のある変数に `is_atom` を使ってはいけない。見つけにくいバグを埋め込むことになる。

### 文字列へ変換

```elixir
:foo |> Atom.to_string()    # -> "foo"
```

数値
----

* [Integer](https://hexdocs.pm/elixir/Integer.html)
* [Float](https://hexdocs.pm/elixir/Float.html)

### 文字列へ変換

事前に `is_integer/1` 等で型をチェックしてから実行すること。

```elixir
42 |> Integer.to_string()    # -> "42"
42.1 |> Float.to_string()    # -> "42.1"
"123" |> Integer.to_string()    # -> ArgumentError
```

### 無限の扱い

Infinity, INF といった値はない。

### 累乗

```elixir
4 |> :math.pow(3)    # -> 64.0
```

整数で欲しければ `Kernel.round/1` する。

```elixir
4 |> :math.pow(3) |> round    # -> 64
```

### 算術演算子をパイプする

```elixir
100 |> Kernel.+(5)    # -> 105
```

### 乱数を作る

```elixir
:rand.uniform()    # -> 0.0 =< X < 1.0 の少数を返す。0.0 が不要なら uniform_real を参照
:rand.uniform(5)    # -> 1..5 の範囲の整数を返す
```

### unique_integer()

```elixir
:erlang.unique_integer()    # -> -576460752303423358
```

ランタイムインスタンスごとにユニークな整数値を返す、とあるが有効に使える場面があるのかわからない。

### カンマ区切り

[number](https://github.com/danielberkompas/number) を使う。

```elixir
{:number, "~> 1.0.1"}
```

```elixir
12345678 |> Number.Delimit.number_to_delimited    # -> "12,345,678.00"
12345678 |> Number.Delimit.number_to_delimited(precision: 0)    # -> "12,345,678"
```

適当にこんな関数を用意している。

```elixir
  @spec commify(number) :: Strint.t()
  def commify(n) when is_number(n) do
    precision =
      case n do
        n when is_integer(n) -> 0
        n when is_float(n) -> 2
      end

    Number.Delimit.number_to_delimited(n, precision: precision)
  end
```

Range
-----

```elixir
1..3    # -> 1..3
3 in 1..5    # -> true
1..3 |> Enum.sum()    # -> 6
```

無限リストを扱いたい時は Stream が使える。

```elixir
Stream.iterate(0, &(&1 + 1))
|> Stream.map(&(&1 * 2))
|> Enum.take(5)    # -> [0, 2, 4, 6, 8]
```

正規表現
--------

[Regex](https://hexdocs.pm/elixir/Regex.html)

細かいフラグも多く柔軟性は高そうだ。PCRE 互換。

### コンパイルする

文字列を正規表現へコンパイルする。`compile!/1` もある。

```elixir
Regex.compile("ab+c")    # -> {:ok, ~r/ab+c/}
Regex.compile("???!!??")    # -> {:error, {'nothing to repeat', 0}}
```

ただし、次のようなケースではバックスラッシュを自前でエスケープしておく必要があるようだ。

```elixir
Regex.compile("\\d+")    # -> {:ok, ~r/\d+/}
```

が、こちらはエスケープなしでいける（条件がよくわからない）。

```elixir
Regex.compile("[\s\t]+")    # -> {:ok, ~r/[ \t]+/}
```

### マッチするかどうか

```elixir
Regex.match?(~r{o+}i, "foo")    # -> true
```

### マッチさせる（最初の要素のみ）

最初にマッチした文字列が返る。

```elixir
Regex.run(~r{o+}i, "foo fooooo!")    # -> ["oo"]
Regex.run(~r{o+}i, "bar")    # -> nil
```

マッチした箇所を返すよう指定できる。

```elixir
Regex.run(~r{o+}i, "accde foo", return: :index)    # -> [{7, 2}]
```

### マッチさせる（すべて）

`run/2` のマルチ版だと思えばよい。

```elixir
Regex.scan(~r{o+}i, "foo fooooo!")    # -> [["oo"], ["ooooo"]]
Regex.scan(~r{o+}i, "foo fooooo!", return: :index)    # -> [[{1, 2}], [{5, 5}]]
```

### 置き換える

```elixir
Regex.replace(~r{^(.+)@(.+)$}, "me@example.com", "\\1 at \\2")    # -> "me at example.com"
```

デフォルトは greedy match なので、初回マッチのみに限定したければ global 値をセットする。

```elixir
Regex.replace(~r{fo+}, "foo fooo! foooo!!", "bar")    # -> "bar bar! bar!!"
Regex.replace(~r{fo+}, "foo fooo! foooo!!", "bar", global: false)    # -> "bar fooo! foooo!!"
```

関数に渡せるので柔軟に処理できる。

```elixir
Regex.replace(~r{^(.+)@(.+)$}, "me@example.com", fn _, user, domain -> "#{user} at #{domain}" end)
```

### 分割する

正規表現を使って文字列をリストに分割できる。

```elixir
Regex.split(~r/[\s\t]+/, "aa bb \t cc dd")    # -> ["aa", "bb", "cc", "dd"]
```

タプル
------

### 定義する

```elixir
tuple = {:ok, 42, "hello"}
is_tuple(tuple)    # -> true
```

### 要素を得る

添字は 0 から始まる。

```elixir
tuple |> elem(0)    # -> :ok
```

### サイズを得る

```elixir
tuple |> tuple_size    # -> 3
```

### 要素を置き換えた tuple を返す

```elixir
tuple |> put_elem(2, "bonjour")    # -> {:ok, 42, "bonjour"}
```

### リストへ変換する

```elixir
{"127", "0", "0", "1"} |> Tuple.to_list    # -> ["127", "0", "0", "1"]
```

リスト
------

### 空かどうか

```elixir
[] |> Enum.empty?    # -> true
[1, 2, 3] |> Enum.empty?    # -> false
```

### 要素の数（長さ）

```elixir
[] |> Enum.count    # -> 0
[1, 2, 3] |> Enum.count   # -> 3
```

多次元配列の場合は `List.flatten/1` したい場合もあるだろう。

```elixir
["a", ["x", "y"], "b", "c"] |> Enum.count    # -> 4
["a", ["x", "y"], "b", "c"] |> List.flatten |> Enum.count    # -> 5
```

### 値が含まれるかどうか

```elixir
3 in [1, 2, 3]    # -> true
[1, 2, 3] |> Enum.member?(3)    # -> true
```

### head / tail

以下は同様に使える（パフォーマンスは違うかもしれない）。

```elixir
[n | tail] = [1, 2, 3]    # n -> 1
hd [1, 2, 3]    # -> 1
List.first [1, 2, 3]    # -> 1
```

ただし、リストが空の時は例外が発生するので注意が必要。

```elixir
[n | tail] = []    # -> ArgumentError
hd []    # -> ArgumentError
```

### first / last

`List.first/1` や `List.last/1` を使うと例外は起きないが：

```elixir
List.first []    # -> nil
List.last []    # -> nil
```

やはり値そのものが `nil` の場合は区別が付かないので、場合によっては事前にサイズを確かめるなどの措置が必要だろう。一長一短。

```elixir
List.first []    # -> nil
List.first [nil]    # -> nil
```

### Set へ変換

```elixir
[4, 1, 2, 3, 1, 4, 5, 2, 3, 5] |> Enum.into(MapSet.new)    # -> #MapSet<[1, 2, 3, 4, 5]>
```

### tuple へ変換

```elixir
["127", "0", "0", "1"] |> List.to_tuple    # -> {"127", "0", "0", "1"}
```

### 関数いろいろ

```elixir
ary = [10, 20, 30]
ary2 = List.insert_at(ary, 1, 15)    # -> [10, 15, 20, 30]
Enum.at(ary, 2)    # -> 30
Enum.count(ary)    # -> 3
```

Map
---

### 特徴

#### キーにどんな値も指定できる

タプルも空文字列も `nil` も正規表現も Map も無名関数ですら指定できる。若干キモい。

```elixir
%{{:foo, :bar} => 42}
%{"" => 42}
%{nil => 42}    # -> %{nil: 42}
%{~r/o+/i => 42}
%{%{foo: "bar"} => 42}
%{fn -> "hello" end => 42}    # -> %{#Function<21.126501267/0 in :erl_eval.expr/5> => 42}
```

#### キーの順序は保証されない

順序が必要ならキーワードリストを使うか、出力する直前にソートする必要がある。

#### キーが atom の場合はドット記法が使える

```elixir
m = %{ one: 1, two: 2 }
m.one    # -> 1
```

クォートが必要な atom も、使えることは使える。

```elixir
m = %{:"hello-world" => 10}
m."hello-world"    # -> 10
```

### 存在しないキーを指定すると

キーが存在しなくても例外は発生しない。単純に `nil` となる。

```elixir
m = %{}
m[:does][:not][:exist]    # -> nil
```

そのため、例えばテキストを期待しているなら次のようにするのがよいのだろう。

```elixir
if is_binary(param[:my_setting][:user_name]) do
  ...
end

case param[:my_setting][:user_name] do
  user_name when is_binary(user_name) -> ...
  nil -> ...
end
```

あるいは、値を直接得るには `Kernel.get_in` を使う。

```elixir
%{10 => %{apple: "red"}} |> Kernel.get_in([10, :apple])    # -> "red"
```

ちなみに Ruby での `#dig` と同等。

```ruby
m.dig(:does, :not, :exist)    # -> nil
```

ただし、値に `nil` が含まれる場合には、キーが存在しなかったのか値が `nil` だったのかの区別はつかない。基本的にはキーの存在を事前にきちんとチェックすべきだ。

```elixir
m = %{foo: %{bar: nil}}
result = m[:foo][:bar]    # -> nil
```

### キーの存在をチェック

キーの存在をチェックするには `Map.has_key?/1` を使う。

```elixir
%{foo: 42} |> Map.has_key?(:foo)    # -> true
%{} |> Map.has_key?(:foo)    # -> false
```

### マップのサイズを得る

`Kernel.map_size/1` を使う。

```elixir
%{first: 20, second: 30} |> map_size    # -> 2
```

### リストから Map を生成する

キーの順序は保証されない点に注意。

```elixir
list = [{"foo", 1}, {"bar", 2}]
list |> Enum.into(%{})    # -> %{"bar" => 2, "foo" => 1}
```

キーワードリストからも生成できる。

```elixir
list = [foo: 1, bar: 2]
list |> Enum.into(%{})    # -> %{bar: 2, foo: 1}
```

### リストからペアを取り出して Map を生成する

```elixir
["a", "b", "c", "d"]
|> Enum.chunk_every(2)
|> Enum.into(%{}, fn [k, v] -> {k, v} end)    # -> %{"a" => "b", "c" => "d"}
```

すこし複雑な例。

```elixir
"foo=bar\nbaz=42\n"
|> String.split(~r/\n+/)
|> Enum.filter(fn s -> s != "" end)
|> Enum.map(fn line ->
  line
  |> String.split("=", parts: 2)
end)
|> Enum.into(%{}, fn [k, v] -> {k, v} end)
# -> %{"baz" => "42", "foo" => "bar"}
```

やはりキーの順序は保証されないことに注意。

### キーとバリューをひっくり返す

```elixir
@spec invert(map) :: map
def invert(map) when is_map(map) do
  for {k, v} <- map, into: %{}, do: {v, k}
end

%{ one: 1, two: 2 } |> invert()    # %{1 => :one, 2 => :two}
```

（かつてバリューであった）キーの重複（による予期しない上書き）には注意。

```elixir
%{ one: 1, two: 2, zwei: 2 } |> invert()    # %{1 => :one, 2 => :zwei}
```

### バリューでソートする

順序を維持するため、キーワードリストへ変換される。

```elixir
%{ two: 2, one: 1 }
|> Enum.sort(fn(a, b) -> elem(a, 1) < elem(b, 1) end)
# -> [one: 1, two: 2]
```

### バリューを加工する

map_values はないので次のようにする。

```elixir
for {k, v} <- %{one: 10, two: 20}, into: %{}, do: {k, v + 5}    # -> %{one: 15, two: 25}
```

あるいは定義してもいいかもしれない。

```elixir
@spec map_values(map(), (any() -> any())) :: map()
def map_values(map, func) when is_map(map) and is_function(func, 1) do
  map
  |> Enum.map(fn {k, v} -> {k, func.(v)} end)
  |> Enum.into(%{})
end
```

### 再帰的にマージする

複雑な構造の Map を再帰的にマージしたい場合は [deep_merge](https://github.com/PragTob/deep_merge) を使うとよい。

```elixir
{:deep_merge, "~> 1.0"}
```

```elixir
%{a: 1, b: [x: 10, y: 9]}
|> DeepMerge.deep_merge(%{b: [y: 20, z: 30], c: 4})
```

### 各キーの Atom を文字列へ変換する

例えば JSON を処理する一部のライブラリなどは、キーが文字列であることを前提にするケースがある。

```elixir
@spec atoms_to_keys(map) :: map
def atoms_to_keys(map) when is_map(map) do
  map |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end)
end

%{foo: 42} |> atoms_to_keys    # -> %{"foo" => 42}
```

recursive に処理したい場合は別のソリューションが必要になるだろう。

### 各キーのケース（大文字小文字）を変更する

[recase](https://github.com/sobolevn/recase) を使う。

```elixir
{:recase, "~> 0.5"}
```

例えば JSON 用にキャメルケースへまとめて変換するなら、次のような関数を用意しておくと便利。

```elixir
@spec camelize_keys(map) :: map
def camelize_keys(map) when is_map(map) do
  map |> Recase.Enumerable.convert_keys(&Recase.to_camel/1)
end

%{serial_no: 42} |> camelize_keys    # -> %{serialNo: 42}
```

Struct
------

### 定義する

```elixir
defmodule Point do
  defstruct x: 10, y: 20
end
```

### 作成する

```elixir
%Point{}    # -> %Point{x: 10, y: 20}
%Point{x: 500}    # -> %Point{x: 500, y: 20}
```

### Map へ変換する

```elixir
%Point{} |> Map.from_struct    # -> %{x: 10, y: 20}
```

Set (MapSet)
------------

* [MapSet](https://hexdocs.pm/elixir/MapSet.html)

### 基本

```elixir
set = MapSet.new([4, 1, 2, 3, 1, 4, 5, 2, 3, 5])    # -> #MapSet<[1, 2, 3, 4, 5]>
```

### 値を追加/削除する

Map のように振る舞えるので put で追加できる。

```elixir
set2 = set |> MapSet.put(100)    # -> #MapSet<[1, 2, 3, 4, 5, 100]>
```

結果値は常にソートされている、ように見えるがマニュアルに記述はない。

削除する場合は delete する。

```elixir
set |> MapSet.delete(3)    # -> #MapSet<[1, 2, 4, 5]>
```

### 値が含まれるかどうか

リストと同じようにチェックできる。

```elixir
3 in set    # -> true
set |> MapSet.member?(3)    # -> true
```

### セットどうしをマージする

```elixir
set |> MapSet.union(set2)
```

### リストへ変換

```elixir
set |> MapSet.to_list    # -> [1, 2, 3, 4, 5]
```

関数
----

### 再帰による繰り返し処理

（多くの場合は）入力として逐次処理したいリストと結果の初期値を渡す。

```elixir
defmodule Main do
  # リストの先頭の値を取り出し、結果値を加工する。リストの残りを自分自身に処理させる
  def foo([head | tail], result) do
    foo(tail, result + String.length(head))
  end

  # リスト内のすべての値を処理したら、結果を返す
  def foo([], result), do: result
end

Main.foo(["hello", "world"], 0)
```

結果値（アキュムレータ）は複数持てるので、中間値として使い複雑な処理が書けるが・・

```elixir
def foo([head | tail], n, cnt, tmp1, tmp2, total, result), do ...
```

処理したいリスト（等）が複数ある場合はそれぞれ工夫が必要だ（これは手続き型言語の while 文でも同じ）。

別の例。単語の出現回数を再帰で数える。

```elixir
defmodule Main do
  # リストに中身があるうちはこちらにマッチし、先頭の値が name に代入される
  @spec countup(list(String.t()), map) :: map
  def countup([name | tail], result) do
    # 現在の数字
    count = result[name] || 0

    # result の値を加工する（ここでは数を追加した）
    countup(tail, result |> Map.put(name, count + 1))
  end

  # すべてのリストを処理した場合はこちらにマッチし、結果を返す
  def countup([], result), do: result
end

["eeny", "eeny", "meeny", "miny", "miny", "miny", "moe", "moe"]
|> Main.countup(%{})
# -> %{"eeny" => 2, "meeny" => 1, "miny" => 3, "moe" => 2}
```

### 複数の when ガード

when ガードを単純に複数回繰り返して記述すると、ロジックは `OR` となるようだ。つまり、この場合は引数の型がリスト*または*バイナリの場合にマッチする。

```elixir
def do_something(data) when is_list(data) when is_binary(data) do
  ...
end
```

AND にしたければ普通に and で繋ぐ。

```elixir
def do_something(one, two) when is_struct(one) and is_struct(two) do
  ...
end
```

無名関数
--------

### 基礎

引数なしの無名関数を作る。

```elixir
f =
  fn ->
    42
  end

f.()    # -> 42
```

仮引数は `()` で指定する。`map` 等は `{}` なので少し紛らわしい。

```elixir
f = fn (a, b) -> a + b end
```

ブロック内で外の値と同じものを指定するとローカライズされる。一般的な LL 言語と同様。

```elixir
n = 10
f = fn n -> n end

f.(20, nil)    # -> 20
n    # -> 10
```

### 省略記法

`&` 記号を使って簡潔に書ける。

```elixir
["aa\nbb", "cc\ndd\nee"] |> Enum.flat_map(&(String.split(&1, "\n")))

# こちらと同じ意味
["aa\nbb", "cc\ndd\nee"] |> Enum.flat_map(fn s -> String.split(s, "\n") end)
```

制御構文
-------

### else if はない

`else if` はない。代わりに `cond` を使う。

### cond

`cond` は**すでに存在する複数の値を使った判定**ができる。過去指向。

```elixir
{n, m} = {100, 150}

result =
	cond do
		n > 100 -> "n is big"
		m > 100 -> "m is bigger"
		true -> "n nor m is not so big"
	end
```

* この例のように、結果値を返すこともできる
* 最初にマッチした行が評価され、以降の行は実行されない
* すべてキャッチするデフォルト（`else` とも言える）は `true` を指定する

効果的に使える場面は意外と限られる。単純なケースでは `if/else` で書き換えができるケースも多い。

### case

`case` は**これから計算する結果値を使って条件分岐したい時**に使う。未来指向。

```elixir
func = fn -> "hello" end

result =
  case func.() do
    str when is_nil(str) -> "empty!"
    str when is_binary(str) and str == "hello" -> "yeah!"
    _ -> "oops!"
  end
```

* この例のように、結果値を返すこともできる
* 最初にマッチした行が評価され、以降の行は実行されない
* すべてキャッチするデフォルト（`else` とも言える）は `_` を指定する

### with

複数のマッチングを束ねる文法。あまり直感的でもないので使いすぎ注意。

https://qiita.com/shufo/items/fadf75b13d9bef408ab0

```elixir
with {:ok, content} when is_binary(content) <- read_from_file(file),
     {:ok, _result} <- check_content(content) do
  true
else
  {:error, :invalid} ->
    Logger.warn("invalid content: #{file}")
    false

  {:error, _} ->
    false
end
```

モジュール名・関数
------------------

### 文字列をモジュールに変換する

```elixir
"MyApplication.HeavyTask" |> Macro.camelize()    # -> MyApplication.HeavyTask
```


### モジュール名・関数名を得る

モジュール名と関数名を指定して実行する。

```elixir
{myfunc, _} = __ENV__.function
result = apply(__ENV__.module, myfunc, ["hello", "world"])
```

再帰関数を定義する際に、関数名を内部にハードコードしたくない時に使えるかもしれない。

```elixir
def foo([head | tail], result) do
  {myfunc, _} = __ENV__.function
  apply(__ENV__.module, myfunc, [tail, result + String.length(head)])
end
```

### 特定の関数の arity を得る

（失念）

struct から値を抜き出す
-----------------------

パターンマッチさせる。

```elixir
uri = URI.parse("https://httpbin.org/get")

%URI{path: path} = uri    # path -> "/get"
```

環境変数
--------

```elixir
System.get_env("HOME")    # -> "/Users/you"
System.get_env("HOME_SWEET_HOME")    # -> nil

System.fetch_env("HOME")    # -> {:ok, "/Users/you"}
System.fetch_env("HOME_SWEET_HOME")    # -> :error

System.put_env("HOME_SWEET_HOME", "Tokyo, Japan")    # -> :ok
```

アプリケーションに定数を持つ
----------------------------

好きなファイル名をいくらでも指定できる。

```elixir
# config/config.exs
import_config "constants.exs"
```

```elixir
# config/constants.exs
import Config    # elixir >= 1.9

config :myproject, :mystatus, %{
  10 => :inactive,
  20 => :active
}
```

次のように取り出せる。

```elixir
mystatus = Application.get_env(:myproject, :mystatus)
mystatus = Application.compile_env(:myproject, :mystatus)    # コンパイル時。get_env よりこちらが推奨される
```

モジュール変数？に置けるので便利。

```elixir
defmodule Main do
	@mystatus Application.get_env(:myproject, :mystatus)
	
	def run() do
		IO.inspect @mystatus
	end
end
```

並列処理
--------

### Task.async_stream

async/3 もあるが結果を統合したいなら async_stream が便利だ。
これを使えば、例えば「時間のかかる処理を５件ずつ処理して結果を待つ（統合する）」が可能になる。

```elixir
1..3
|> Task.async_stream(fn n -> n * 10 end)
|> Enum.to_list    # -> [ok: 10, ok: 20, ok: 30]
```

単純に結果値だけが欲しいなら次のように取れるが・・

```elixir
[ok: 10, ok: 20, ok: 30] |> Keyword.values    # -> [10, 20, 30]
```

すべてのタスクが成功したかどうかによって後続の処理を分けたい場合は一工夫必要だろう。

ここでは `reduce_while/3` を使って、すべて成功した場合は `{:ok, [10, 20, 30]}` を、失敗した場合は `{:error, reason}` を返すようにしてみた。

```elixir
[ok: 10, ok: 20, ok: 30]
|> Enum.reduce_while({:ok, []}, fn elem, {:ok, acc} ->
  case elem do
    {:ok, value} -> {:cont, {:ok, acc ++ [value]}}
    {:exit, reason} -> {:halt, {:error, reason}}
  end
end)    # -> {:ok, [10, 20, 30]}
```

返値が必要なければ `Enum.to_list/1` の代わりに `Stream.run/0` を呼ぶとよい。

タイムアウトのデフォルト値は 5 秒と短いので指定するとよい。最大同時実行数も指定できる。

```elixir
import :timer
1..3 |> Task.async_stream(fn n -> n * 10 end, 
  max_concurrency: System.schedulers_online,
  timeout: seconds(30),
  on_timeout: :kill_task
)
```

一般的に `System.schedulers_online` は CPU コア数となるようだ。

Agent
-----

[Agent](https://hexdocs.pm/elixir/1.10.2/Agent.htm)

強引に OOP 言語に例えるなら、「プロパティを１つだけ持てるオブジェクト」的なものを生成する。

### 基本

start_link で、エージェントの初期値を設定（生成）する。
name を指定しておくと便利。

```elixir
{:ok, agent} = Agent.start_link(fn -> 42 end, name: :mytest)    # -> {:ok, #PID<0.110.0>}
```

現在の値を get/3 で得る。`pid` または name を指定する。

```elixir
Agent.get(agent, &(&1))    # -> 42
Agent.get(:mytest, &(&1))    # -> 42
```

タイムアウトも指定できる。デフォルトは 5,000 ms となっている。

```elixir
Agent.get(:mytest, &(&1), 10_000)    # １０秒待つ
```

内部の値を加工して取り出せる。

```elixir
Agent.get(:mytest, fn state -> "this is #{state}" end)    # -> "this is 42"
```

内部の値を更新する。

```elixir
Agent.update(:mytest, &(&1 + 1))    # -> :ok
Agent.get(agent, &(&1))    # -> 43
```

### module にラップする

マニュアルの例そのまま。

```elixir
defmodule Counter do
  use Agent

  def start_link(initial_value) do
    Agent.start_link(fn -> initial_value end, name: __MODULE__)
  end

  def value, do: Agent.get(__MODULE__, & &1)
  def increment, do: Agent.update(__MODULE__, &(&1 + 1))
end
```

OOP 言語のオブジェクトに近い感じで扱える。

```elixir
Counter.start_link(100)
Counter.increment    # -> :ok
Counter.value    # -> 101
```

Agent.stop/1 で停止できる。これも module に組み込むとよい。

```elixir
  def stop, do: Agent.stop(__MODULE__)
```

GenServer と同様に supervisor の元で起動するのが一般的なようだ。

```elixir
children = [
  {Counter, 0}
]

Supervisor.start_link(children, strategy: :one_for_all)
```

エラーハンドリング
------------------

### Erlang の throw 対策

Erlang の `throw` は `rescue` ではキャッチできず、`catch` を使わなくてはならない。

Erlang のすべての例外を一律でキャッチすることもできる。一例。

```elixir
try do
  ...
catch
  :exit, {:fatal, _} -> ...
end
```

多くのケースではこのように `:exit` を拾えばいいようだが、そのあたりのまとまった仕様 or ベストプラクティスはどこにあるのだろうか。

動的コンパイル
--------------

動的言語のようなことができる。

```elixir
Code.compile_string("defmodule Main do def add(a, b) do a + b end end")
Main.add(10, 20)    # -> 30
```

https://qiita.com/sym_num/items/46f286fd145f4007afad

ログ
----

### メタデータをセット

メタデータは個々に指定することができる。

```elixir
Logger.debug("hello world", device: "iPhone")
```

### デフォルトのメタデータをセット

そのアプリケーション内のスコープで有効になるメタデータのデフォルト値をセットできる。
もちろん都度上書きもできる。

```elixir
Logger.metadata(device: "iPhone")
```

```elixir
# config/config.exs
config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  metadata: [:module, :device]
```

この例では `metadata` に `:device` を足している。

ただし `async` な処理に回すとメタデータは消失する（ようだ。まだよく理解していない）。

時刻・時間
--------

### sleep

```elixir
Process.sleep(1000)    # 単位はミリ秒

import :timer
Process.sleep(seconds(2))
```

次のような同等の例を見ることもあるかもしれない。

```elixir
import :timer

:timer.sleep(2000)    # 単位はミリ秒
:timer.sleep(seconds(2))
```

### epoch

次のような関数を用意しておくと便利。

```elixir
defmodule Util do
  @spec epoch(atom) :: non_neg_integer
  def epoch(unit \\ :second) do
    DateTime.utc_now() |> DateTime.to_unix(unit)
  end
end

Util.epoch()    # -> 1579047584
Util.epoch(:millisecond)    # -> 1579047584288
```

### 時刻の差を得る

実行にかかった時間を知りたい場合。[Timex](https://github.com/bitwalker/timex) を使った例。

```elixir
{:timex, "~> 3.5"}
```

結果を秒単位で算出するようにしてみた。

```elixir
use Timex

start = Timex.local()

do_heavy_task()

Timex.diff(Timex.local(), start, :milliseconds) / 1000    # -> 5.782
```

ベンチマーク
------------

```elixir
{:benchee, "~> 1.0"},
{:benchee_html, "~> 1.0"},
```

```elixir
Benchee.run(
  %{
    "md5" => fn -> :crypto.hash(:md5, "blah-blah-blah-blah") end,
    "sha" => fn -> :crypto.hash(:sha, "blah-blah-blah-blah") end
  },
  warmup: 1,    # 回
  time: 2,    # 秒
  formatters: [
    Benchee.Formatters.Console,
    {Benchee.Formatters.HTML, file: "./benchmark_result.html"}
  ]
)
```



データ
------

あれこれ。

### BaseXX

```elixir
"hello" |> Base.encode64(case: :lower)    # -> "aGVsbG8="
```

### ハッシュ

```elixir
:crypto.hash(:md5, "hello") |> Base.encode16(case: :lower)    # -> "5d41402a...1017c592"
:crypto.hash(:sha512, "hello") |> Base.encode16(case: :lower)
```

### ランダム

[secure_random](https://hexdocs.pm/secure_random/SecureRandom.html) が使える。引数のデフォルトはどれも 16 となっている。

```elixir
{:secure_random, "~> 0.5"}
```

```elixir
SecureRandom.hex(16)    # -> "3b60c3c61127c704a5633e14b6e82901"
SecureRandom.base64    # -> "Q1I/swCrSJhJpxsY43S9Ng==
SecureRandom.random_bytes(4)    # -> <<44, 71, 133, 248>>
```

`urlsafe_base64()` や `uuid()` もある。

型
--

dializer にかける時の例あれこれ。以下、`run()` は入力をそのまま返す関数とする。

### 文字列

```elixir
@spec run(String.t()) :: String.t()
def run(str) do
	str
end
```

Elixir の世界では `binary` も概ね同じ意味、らしい。

### 数値

正の整数は `non_neg_integer` を使おう。

```elixir
@spec run(non_neg_integer) :: non_neg_integer
```

### リスト

どちらでも同じ意味。個人的には `list()` を使っている。

```elixir
@spec run(list(String.t())) :: list(String.t())
@spec run([String.t()]) :: [String.t()]
```

### Map

```elixir
@spec run(map) :: map
@spec run(%{}) :: %{}
```

### 時刻

Timex の例。

```elixir
use Timex

@spec run(DateTime.t()) :: DateTime.t()
def run(time), do: time

run(Timex.local())
```

### nil

`nil` が返る可能性がある場合

```elixir
@spec run(String.t() | nil) :: String.t() | nil
```

### no_return

投げっぱなしで結果値が必要ない場合。

```elixir
@spec throw_into_blackhole(thingy) :: no_return
```

### モジュール

場合により、いろいろ（元の定義によるようだ、よくわかっていない）。

#### struct

%Tesla.Env{}

#### モジュール（〜名としての atom）

Tesla.Adapter.Mint

#### 独自の型

ConCache.Item.t()

Tesla.Env.headers()

### モジュール名

dializer 的には atom として認識しているように見える。

```elixir
defmodule MyProject.Crawler.Google do
	...
end

@spec service_module(String.t()) :: atom
def service_module(name) do
	case name do
		"google" -> MyProject.Crawler.Google
		...
	end
end
```

erlang との相互運用
-------------------

たとえば Phoenix などを使っていると `sys.config` などを erlang 文法で書く場面がある。

まとまった資料をまだ見つけていないので手探りで。そのうちちゃんと調べる。

### 型

bare word を書くと atom になる。

```erlang
production    % -> :production
```

文字列。

```erlang
<<"this is a pen">>    % -> "this is a pen"
```