Elixirで気軽にErlang Term Storage(ETS)を使う

[elixir]: https://elixir-lang.org/
[erlang]: https://www.erlang.org/
[phoenix]: https://www.phoenixframework.org/
[nerves]: https://hexdocs.pm/nerves
[livebook]: https://livebook.dev/
[iex]: https://elixirschool.com/ja/lessons/basics/basics/#%E5%AF%BE%E8%A9%B1%E3%83%A2%E3%83%BC%E3%83%89
[:ets.fun2ms/1]: https://www.erlang.org/doc/man/ets.html#fun2ms-1
[Match Spec]: https://www.erlang.org/doc/apps/erts/match_spec.html
[ex2ms]: https://hex.pm/packages/ex2ms
[Ex2ms.fun/1]: https://hexdocs.pm/ex2ms/Ex2ms.html#fun/1

## はじめに

みなさんErlang Term Storage(ETS)を使ってますか？
依存関係なしでElixirから利用できるので便利ですよね。

確かにETSの関数はちょっとクセがあるので使いにくい面もあります。
そこで[ElixirのMap](https://hexdocs.pm/elixir/Map.html#get/3)風のインターフェイスでETSを使えるようにしたらもっと気軽にできるのではと思い、取り組んでみました。

https://qiita.com/torifukukaiou/items/17d55cf896c24b13350e

## ETSとは

https://elixirschool.com/ja/lessons/storage/ets

https://www.erlang.org/doc/man/ets.html

## 気軽にETSを使う

- 最低限これだけあれば気軽にETSを使えます。
- あらかじめ名前付きのETSテーブルを作れるようにしておきます。
- 値の挿入と取得もモジュール経由ででき便利です。

### 自前のETSモジュールを作る

```elixir
defmodule MyEts do
  def init, do: :ets.new(__MODULE__, [:set, :named_table])

  def get(key, default \\ nil) do
    case :ets.lookup(__MODULE__, key) do
      [] -> default
      [{_, value} | _] -> value
    end
  end

  def put(key, value), do: :ets.insert(__MODULE__, [{key, value}])

  def delete(key), do: :ets.delete(__MODULE__, key)
end
```

### テーブルを作る

```elixir
MyEts.init()
```

### テーブルに値を挿入

```elixir
MyEts.put(:feeling, "awesome")
```

### テーブルから値を取得

```elixir
MyEts.get(:feeling)
```

### テーブルから値を削除

```elixir
MyEts.delete(:feeling)
```

## その他の関数

[Erlangの公式ドキュメント]: https://www.erlang.org/doc/man/ets.html
[Elixir School]: https://elixirschool.com/ja/lessons/storage/ets

- [Erlangの公式ドキュメント]にリストアップされています。
- [Elixir School]にもサンプルコードがいくつか紹介されています。
- 個人的には`select`系の関数が興味深いです。

```elixir
db = :ets.new(:example_db, [:set, :named_table])
for i <- 0..9, do: :ets.insert(db, [{i, i**2}])

# キーの範囲を指定して複数の値を取得
match_spec = :ets.fun2ms(fn { key, value } when key in 3..5 -> value end)
:ets.select(db, match_spec)
```

## Match Spec

[`:ets.fun2ms/1`]: https://www.erlang.org/doc/man/ets.html#fun2ms-1
[Match Spec]: https://www.erlang.org/doc/apps/erts/match_spec.html
[ex2ms]: https://hex.pm/packages/ex2ms
[ex_rated]: https://hex.pm/packages/ex_rated

- [Match Spec]と呼ばれるErlang独自のクエリ言語で検索できる仕組みがあります。
- [Match Spec]は自力で書いてもいいのですが、[`:ets.fun2ms/1`]関数（もしくは[ex2ms]というHexパッケージが提供する`Ex2ms.fun/1`マクロ）が便利です。
- ややこしいですが、慣れると楽しめそうです。

[Erlang]の[:ets.fun2ms/1]は[Elixir]で使うとたまに謎のコンパイル関連エラーが出て怖いので[Elixir]用の[ex2ms]パッケージを使うと無難そうです。

```elixir
# :ets.fun2ms(fn { key, value } when key in 3..5 -> value end)
[
  {{:"$1", :"$2"},
   [
     {:andalso, {:is_integer, :"$1"},
      {:andalso, {:>=, :"$1", 3}, {:"=<", :"$1", 5}}}
   ], [:"$2"]}
]
```

https://www.erlang.org/doc/apps/erts/match_spec.html

https://www.erlang.org/doc/man/ets.html#fun2ms-1

一例として[ex_rated]というHexパッケージでは、ある条件で古いデータを消去する目的でMatch Specを使ってます。

https://github.com/grempe/ex_rated/blob/e6e6e2130312c404c714d76f50d5101ede366221/lib/ex_rated.ex#L255-L263

## エラー

- Erlang特有のわかりにくいエラーメッセージが出ることがあります。

```elixir
# 2回同じ名前のテーブルを生成した場合に出るエラー
iex> :ets.new(:example_table, [:set, :named_table])
:example_table

iex> :ets.new(:example_table, [:set, :named_table])
** (ArgumentError) errors were found at the given arguments:

  * 2nd argument: invalid options

    (stdlib 4.0.1) :ets.new(:example_table, [:set, :named_table])
    iex:2: (file)
```

## DETS

- ETSがインメモリのデータストレージであるのに対して、DETSはディスクベースのストレージです。
- 関数はETSと似ています。
- ディスクベースなのでデータベースファイルを開いて、使用後に閉じる必要があります。

https://www.erlang.org/doc/man/dets.html

## @zacky1972先生のvarious_map

さらに@zacky1972先生がさまざまなMapの実装をされています。

https://github.com/zacky1972/various_map

## さいごに

Elixirを楽しみましょう！

https://qiita.com/piacerex/items/e5590fa287d3c89eeebf

https://qiita.com/torifukukaiou/items/1edb3e961acf002478fd

各コミュニティの詳細は、「Elixirコミュニティの歩き方 －国内オンライン編－」をご覧ください

[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/f891b7ad-d2c4-3303-915b-f831069e28a4.png)](https://speakerdeck.com/elijo/elixirkomiyunitei-falsebu-kifang-guo-nei-onrainbian)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/7fdc5db7-dfad-9d10-28f8-1e0b8830a587.png)

各種Elixirイベントの開催予定は、「Elixirイベントカレンダー」から確認／参加できます :calendar:

[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109744/985acaa4-50c9-da42-ae32-50fbf9119e61.png)](https://elixir-jp-calendar.fly.dev/)

## ご参考までに

https://qiita.com/piacerex/items/e0b6e46b1325bb931122

https://qiita.com/torifukukaiou/items/1edb3e961acf002478fd

https://qiita.com/piacerex/items/e5590fa287d3c89eeebf