Elixirでmatch_specを使って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

## [match_spec]とは

- [ets]テーブルのオブジェクトを検索したりするのに使える[Erlang]独自のクエリ言語
- 表現力では関数に比べて劣るが、[Erlangランタイムシステム][Erlang]によってコンパイルされ効率的に動作する
- [match_spec]は自力で書いてもいいのですが、[:ets.fun2ms/1]関数（もしくは[ex2ms]というHexパッケージが提供する[Ex2ms.fun/1]マクロ）が便利です

## 論よりRUN

### 基本的な[ets]テーブルの操作

IExを起動します。

```
iex
```

一意のモジュール名で名前をつけて[ets]のインスタンスを生成。

```elixir
iex> :ets.new(MyEts, [:set, :named_table])
MyEts
```

適当にデータを挿入。

```elixir
iex> :ets.insert(MyEts, [a: 1, b: 2, c: 3, d: 4, e: 5])
true
```

キーで値を取得。

```elixir
iex> :ets.lookup(MyEts, :a)
[a: 1]
```

[:ets.lookup/2]を使って簡単にキーで検索ができるのですが、それ以上の複雑な検索機能には[match_spec]が必要となります。

### 全部取得

```elixir
iex> match_spec = :ets.fun2ms(fn row -> row end)
[{{:"$1", :"$2"}, [], [{{:"$1", :"$2"}}]}]

iex> :ets.select(MyEts, match_spec)
[e: 5, d: 4, c: 3, b: 2, a: 1]
```

### キーを指定

```elixir
iex> match_spec = :ets.fun2ms(fn {key, _ } = row when key != :b -> row end)
[{{:"$1", :_}, [{:"/=", :"$1", :b}], [:"$_"]}]

iex> :ets.select(MyEts, match_spec)
[e: 5, d: 4, c: 3, a: 1]
```

### 値を指定

マッチした列を返す場合

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

iex> :ets.select(MyEts, match_spec)
[c: 3, b: 2]
```

マッチした列のキーだけを返す場合

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

iex> :ets.select(MyEts, match_spec)
[:c, :b]
```

以上、簡単な例を挙げさせていただきました。

ちなみに挿入するデータは別にキーバリューでなくても良いので、空でない[タプル][Tuple]であればどんな長さでもOKです。

```elixir
:ets.insert(MyEts, [{"hello"}, {:x, 1, 2, 3, 4, 5}])
```

そういった場合にも[match_spec]は柔軟に対応できるはずです。[タイムスタンプ]を確認して古いデータを削除など。

mentatというElixirパッケージで古いキャッシュを消去するのに[match_spec]が利用されています。
https://github.com/elixir-toniq/mentat/blob/7f1811779ca2dfc80dcb30fe5d70d5809afb3abb/lib/mentat.ex#L228

## [:ets.fun2ms/1]関数と[Ex2ms.fun/1]マクロの違い

これがよくわかりません。個人的には[Erlang]に備えついている[:ets.fun2ms/1]関数でいいんじゃないかと思っているのですが、もし[Ex2ms.fun/1]マクロを使う利点についてご存知の方おられましたらお便りください。よろしくお願いいたします。

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

## もっと気軽に[ets]を使いたい場合

[Map]のような感覚で[ets]を使うだけであれば、[match_spec]は不要です。[ets]を簡単なモジュールでラップしてやれば気軽に[ets]を使えると思います。

https://qiita.com/mnishiguchi/items/54846209c37d74157868

[Erlang]: https://www.erlang.org/doc/system_architecture_intro/sys_arch_intro.html
[match_spec]: https://erlang.org/doc/apps/erts/match_spec.html
[ets]: https://www.erlang.org/doc/man/ets.html
[:ets.lookup/2]: https://www.erlang.org/doc/man/ets.html#lookup-2
[:ets.select/2]: https://www.erlang.org/doc/man/ets.html#select-2
[:ets.fun2ms/1]: https://www.erlang.org/doc/man/ets.html#fun2ms-1
[Ex2ms.fun/1]: https://hexdocs.pm/ex2ms/Ex2ms.html#fun/1
[ex2ms]: https://hex.pm/packages/ex2ms
[タイムスタンプ]: https://ja.wikipedia.org/wiki/%E3%82%BF%E3%82%A4%E3%83%A0%E3%82%B9%E3%82%BF%E3%83%B3%E3%83%97
[Tuple]: https://hexdocs.pm/elixir/Tuple.html
[Map]: https://hexdocs.pm/elixir/Map.html

## ご参考までに

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

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

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