ETS とは
Erlang/OTP には ETS というストレージライブラリが標準で用意されています。 当然 Elixir でも利用できます。
www.erlang.org elixirschool.com
ただ、ETS の問い合わせには match specification と呼ばれる構造を利用する必要があります。
たとえば。 「三つ組のタプルの、3 番目の値が 2 より大きいものの、1 番目のデータを集める」 という問い合わせをしたい場合、問い合わせには次のようなデータを用意しなければなりません。
[{{:"$1", :_, :"$2"}, [{:>, {:length, :"$2"}, 2}], [:"$1"]}]
これは構造としては次のような関数と同じです。
fn {a1, _, a2} when a2 > 2 -> a1 end
こうして見せられればなるほどとわかるのですが、最初から迷わずに書ける自信がありません。
個人的にはこの記述をするのがおっくうで、 データの格納先に ETS を利用することはあまり考えていませんでした。
それが最近になって ets:fun2ms/1
という関数の存在を知りました。
昔からある関数なのですが、ETS を利用する頭がなかったので、そのような便利なものがあることも知らず過ごしていた始末。
iex(1)> :ets.fun2ms(fn {a1, _, a2} when a2 > 2 -> a1 end) [{{:"$1", :_, :"$2"}, [{:>, :"$2", 2}], [:"$1"]}]
よし、これで使える! …と思ったのも束の間。
IEx 上では利用できるものの、モジュールに組み込んで実行するとエラーになってしまいます。
defmodule Foo do def run do :ets.fun2ms(fn {a1, _, a2} when a2 > 2 -> a1 end) end end Foo.run() |> IO.inspect()
$ elixir foo.exs ** (exit) exited in: :ets.fun2ms(:function, :called, :with, :real, :fun, :should, :be, :transformed, :with, :parse_transform, :or, :called, :with, :a, :fun, :generated, :in, :the, :s hell) ** (EXIT) :badarg (stdlib 3.17.1) ets.erl:608: :ets.fun2ms/1 foo.exs:7: (file) (elixir 1.13.3) lib/code.ex:1183: Code.require_file/2
詳しい理由とかは、この記事を読んでいただくとして。
悲しい気分で記事を読み進んでいたわけですが。
実は関数から match specification へ変換する Elixir 製のパッケージが提供されているとのこと。 これもけっこう昔から。
Ex2ms
Ex2ms には fun/1
というマクロが 1 つ定義されています。
import すれば、ほぼ関数と同じ書き方で match specification を取得することができます。
iex(1)> Mix.install([:ex2ms]) :ok iex(2)> import Ex2ms Ex2ms iex(3)> fun do {a1, _, a2} when a2 > 2 -> a1 end [{{:"$1", :_, :"$2"}, [{:>, :"$2", 2}], [:"$1"]}]
match specification を簡単に取得する方法が見つかった以上、ETS を使わない理由がなくなりました。
と、いうわけで、さっそく書いてみました。
ETS を Ex2ms で使う
気象庁の過去の気象データ検索を利用して、東京の 2022 年 2 月の日々の気温データを取得しました。
このデータを ETS のテーブルに格納して、いろいろ検索してみます。
Mix.install([:ex2ms]) defmodule JMA do import Ex2ms # [気象庁|過去の気象データ検索](https://www.data.jma.go.jp/obd/stats/etrn/index.php) で取得した # 東京の 2022年2月の1日ごとの平均気温,最高気温,最低気温 @temperatures [ {~D[2022-02-01], 5.6, 11.2, 1.1}, {~D[2022-02-02], 5.5, 11.1, 0.9}, {~D[2022-02-03], 5.8, 11.8, 0.6}, {~D[2022-02-04], 4.9, 8.5, 2.4}, {~D[2022-02-05], 3.5, 9.2, 0.3}, {~D[2022-02-06], 2.3, 8.2, -1.9}, {~D[2022-02-07], 4.5, 9.7, -0.5}, {~D[2022-02-08], 5.3, 9.1, 2.2}, {~D[2022-02-09], 6.2, 10.8, 1.8}, {~D[2022-02-10], 2.3, 6.1, 0.6}, {~D[2022-02-11], 4.1, 9.2, 0.7}, {~D[2022-02-12], 4.7, 9.9, 0.2}, {~D[2022-02-13], 3.1, 5.0, 0.8}, {~D[2022-02-14], 3.9, 8.0, 0.8}, {~D[2022-02-15], 5.5, 11.6, 1.1}, {~D[2022-02-16], 6.0, 11.8, 1.3}, {~D[2022-02-17], 4.6, 9.7, 0.5}, {~D[2022-02-18], 5.7, 11.4, -0.2}, {~D[2022-02-19], 5.0, 8.9, 1.5}, {~D[2022-02-20], 5.9, 10.0, 2.4}, {~D[2022-02-21], 4.1, 8.7, 1.2}, {~D[2022-02-22], 4.4, 10.0, -0.5}, {~D[2022-02-23], 4.4, 10.3, 0.1}, {~D[2022-02-24], 4.8, 10.1, 1.7}, {~D[2022-02-25], 6.5, 13.4, -0.1}, {~D[2022-02-26], 8.6, 14.7, 3.0}, {~D[2022-02-27], 9.5, 18.5, 3.2}, {~D[2022-02-28], 9.8, 15.9, 4.5} ] def run do # テーブルを作成する tid = :ets.new(:jma, []) # 気温データを投入する @temperatures |> Enum.each(&:ets.insert(tid, &1)) IO.puts("\n最低気温が氷点下の日のデータ") :ets.select(tid, fun do {_date, _avg, _max, min} = t when min < 0 -> t end) |> IO.inspect() IO.puts("\n最高気温と最低気温の差が15度を超える日のデータ") :ets.select(tid, fun do {date, _avg, max, min} when (max - min) > 15 -> {date, max, min, max - min} end) |> IO.inspect() IO.puts("\n最高気温と最低気温の差が15度を超える日のデータ(結果をキーワードリストで取得する)") :ets.select(tid, fun do {date, _avg, max, min} when (max - min) > 15 -> [date: date, max: max, min: min, diff: max - min] end) |> IO.inspect() end end JMA.run()
実行。
$ elixir jma.exs 最低気温が氷点下の日のデータ [ {~D[2022-02-06], 2.3, 8.2, -1.9}, {~D[2022-02-22], 4.4, 10.0, -0.5}, {~D[2022-02-07], 4.5, 9.7, -0.5}, {~D[2022-02-18], 5.7, 11.4, -0.2}, {~D[2022-02-25], 6.5, 13.4, -0.1} ] 最高気温と最低気温の差が15度を超える日のデータ [{~D[2022-02-27], 18.5, 3.2, 15.3}] 最高気温と最低気温の差が15度を超える日のデータ(結果をキーワードリストで取得する) [[date: ~D[2022-02-27], max: 18.5, min: 3.2, diff: 15.3]]
なかなかいい感じ。
いまさらながら ETS の便利さを感じたできごとでした。
いつか読むはずっと読まない:先入観
ETS を使わなければならない状況にならなかったというのもあってか、「問い合わせを書くのが面倒だなぁ」という思い込みを持ったままずっと来てしまいました。
そう感じるのは、たいていは、自分だけではないはずなので、それを解消する方法がすでにあるはず、と思い至ればよかったのですが。 一度思い込んでしまうと、なかなかそれを払拭できないものですね。