v1.14 リリース*1
Rust の dbg!
は理に適ってて羨ましいなと思っていたところやってくれた。
単なる真似では終わらせないという José Valim の心意気にしびれる。
--以下感想--
ElixirConf 2022 - José Valim - Elixir v1.14 - YouTube
- 元記事
- 2022/09/01 Andrea Leopardi
これまでのリリースと同様、開発者体験に焦点を置き、デバッグ作業の改善、
デバッグツール、評価式の調査出力の改善を施した
dbg
Kernel.dbg/2
は IO.inspect/2
に似たマクロで、デバッグ作業用に特別にしつらえたやつである
IO.inspect/2
を置き換えるように使えて、デバッグコード自身とコードの位置、引数で与えた内容を要素ハイライト付き(!)で印字する
dbg/2
はさらに、マクロであることから Elixir コードを理解する
|>
による連続パイプを渡すとそれらすべてのステップを印字する
IEx + dbg
IEx シェルでのブレイクポイントで行ごとのステップ進行が可能になった
iex> break! URI.parse/1
で、URI.parse 関数の先頭にブレイクポイントを置くと
iex> URI.parse "/foo"
でデバッグモードに入り、現在行までに評価された変数を確認でき、n
で一行ステップを進められるようになった
Break reached: URI.parse/1 (lib/elixir/lib/uri.ex:770)
767: def parse(string) when is_binary(string) do
768:
769:
770: regex = ~r{^(([a-z][a-z0-9\+\-\.]*):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?}i
771:
772: parts = Regex.run(regex, string)
773:
pry(1)> n
...
pry(2)> n
...
これは dbg/2
でも利用できる
dbg/2
は構成可能なバックエンドをサポートする。
IEx は自動で既定のバックエンドを dbg/2
のバックエンドに交換し、
それが IEx 上でコード実行を停止させる
我々はこのプロセスを "prying"(=覗き見)と呼ぶ
なぜなら変数やインポートへのアクセスは得るが、コードの実際の動きは変更できないようになっているから
これはパイプライン上でも機能する
連続した |>
パイプを dbg
へ渡す(または末尾に |> dbg()
をつなぐ)と、すべてのパイプを一行ずつステップすることが可能
既定では iex 上で dbg() を実行すると pry プロセス移行の Allow? [Yn]
プロンプトが現れるが、--no-pry
フラグをつけて実行するとモード移行を省略できる
dbg in Livebook
Livebook により Jupyter Notebook のような計算ノートの能力が Elixir にやってくる
Livebook チームは dbg
のバックエンドとして視覚化された表現形式を実装した。
こいつのおかげでパイプラインステップの1つ1つが単独で機能するUI要素として描画される。
要素を一つ選び取って出力を見ることもできるし、さらに、パイプラインを並べ替えたり
一定区間を無効化したりしてその結果を即座に見られる
PartitionSupervisor
PartitionSupervisor
は新しいタイプのスーパーバイザを実装する
単体の管理(supervised)プロセスがボトルネックになってくる状況で役立つようデザインされている
管理プロセスの状態が容易に区分可能である場合、PartitionSupervisor
によってそのプロセスのコピーを管理し、
複数の独立したプロセスとして同時並行に走らせ、各プロセスが自分用の区画を持つようにできる
たとえば ErrorReporter
プロセスがあり、それを使って監視サービスにエラー報告したいとする
children = [
ErrorReporter
]
Supervisor.start_link(children, strategy: :one_for_one)
アプリの並行動作が増えていくにつれ ErrorReporter
プロセスは多数の他プロセスから要求を受け取るかもしれず、
しまいにはボトルネックになりかねない。
このような状況では、ErrorReporter
のコピーを PartitionSupervisor
のもとで複数起ち上げるとよい
children = [
{PartitionSupervisor, child_spec: ErrorReporter, name: Reporters}
]
PartitionSupervisor
は既定で System.schedulers_online()
と同数のプロセス(たいていCPUコアにつき1つ)を起ち上げる。
これで ErrorReporter
プロセスへの要求経路指定として :via
タプルを利用でき、区画スーパーバイザを経由して要求を渡せる
partitioning_key = self()
ErrorReporter.report({:via, PartitionSupervisor, {Reporters, partitioning_key}}, error)
この例のように self()
を区画キーとして使うと同じプロセスのエラー報告先は常に同じ ErrorReporter
プロセスとなり、背圧形式を保証できる。区画キーにはどんな評価式でも使用可能
一般的な例
PartitionSupervisor
の一般的かつ実用的な好例は
DynamicSupervisor
のようなものを区画化することである
単体の動的スーパーバイザ配下で多数のプロセスを開始するとスーパーバイザがボトルネックになり得る。
とくにスーパーバイザ上でプロセス開始の初期化に時間がかかる場合など。
そこで、単体の DynamicSupervisor
を起ち上げることはやめて、複数起ち上げてしまう:
children = [
{PartitionSupervisor, child_spec: DynamicSupervisor, name: MyApp.DynamicSupervisors}
]
Supervisor.start_link(children, staragegy: :one_for_one)
これで適当な区画で複数の動的スーパーバイザを起ち上げられる。
例えば、先ほどの例のように PID で区画を切るなら:
DynamicSupervisor.start_child(
{:via, PartitionSupervisor, {MyApp,DynamicSupervisors, self()}},
my_child_specification
)
バイナリのエラーと評価の改善
Erlang/OTP 25 ではバイナリ構築時のエラーと評価を改善したが、これらの改善は Elixir にも適用された。
v1.14 より前はバイナリ構築時のエラーはしばしばデバッグしづらい漠然とした"引数エラー"に過ぎなかった
Erlang/OTP 25 と Elixir v1.14 ではデバッグ作業しやすいようさらなる詳細情報を提供する
この成果はEEP54の一部である
以前のこれが:
int = 1
bin = "foo"
int <> bin
これからはこうだ:
int = 1
bin = "foo"
int <> bin
コード評価(IEx と Livebook)も同様に改善されてエラー報告とスタックトレースが良くなった
Slicing with Steps
Elixir v1.12 で導入された ステップ範囲 は "ステップ"(=小ジャンプ) を指定できる範囲である
Enum.to_list(1..10//3)
ステップ範囲は特にベクトルと行列が関わる数値計算で有用である(例えばNxを見よ )
ただ、Elixir 標準ライブラリは API にあまりステップ範囲を使ってこなかった。
Elixir v1.14 では手始めにいくつかの関数においてステップ範囲をサポートすることでステップを活用していくことにした。
その一つが Enum.slice/2
である:
letters = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
Enum.slice(letters, 0..5//2)
binary_slice/2
(厳密には binary_slice/3
も)が Kernel
モジュールに追加され、バイナリに対して同様にステップ範囲をサポートする
binary_slice("Elixir", 1..5//2)
Expression-based Inspection and Inspect
Improvements
Elixir では不透明構造体を特殊な表記でもって調査出力するように Inspect
プロトコルを実装する慣習があり、こんな具合である:
MapSet.new([:apple, :banana])
この表記は一般的には構造体の内容またはその一部が非公開で %name{...}
の表現形式を使うと公開 API 以外のフィールドが露出してしまう場合にそれを嫌って行われる
#name<...>
慣習表現が残念なのは調査出力が有効な Elixir コードではない点である。例えば、調査出力をコピーして IEx セッションに貼り付けても使えない
Elixir v1.14 ではいくつかの標準ライブラリの構造体についてこの慣習を改めた
それら構造体の Inspect
プロトコルの実装はこれからは評価すればその構造体が再生成される有効な Elixir 式を文字列で返す。
上の MapSet
の例では、こうなる:
fruits = MapSet.new([:apple, :banana])
MapSet.put(fruits, :pear)
MapSet.new/1
式は調査出力している構造体そのものへと評価される。この式なら MapSet
の内部構造を隠せるし、それでいて有効な Elixir コードのままである。この式形の調査出力は Version.Requirement
, MapSet
, Date.Range
について実装済みである。
最後に、我々は構造体の Inspect
プロトコルを改善し、構造体の調査出力のフィールドが defstruct
において宣言された順になるように変更した
例えば URI 構造体は以前までは各フィールドがアルファベット順に表示されていたが、
iex> URI.new!("https://elixir-lang.org/")
%URI{
fragment: nil,
host: "elixir-lang.org",
path: "/",
port: 443,
query: nil,
scheme: "https",
userinfo: nil
}
これからは URI 構造順に出力してくれる
iex> URI.new!("https://elixir-lang.org/")
%URI{
scheme: "https",
userinfo: nil,
host: "elixir-lang.org",
port: 443,
path: "/",
query: nil,
fragment: nil
}
また、:optional
オプションを追加して Inspect
プロトコルを導出(deriving)する際に開発者が構造体表現についてより制御できるようにした
更新されたドキュメントで使い方の概略と利用可能なオプションについて見よ
Learn more
完全な変更リストについては、full release note を見よ
楽しいデバッグ作業を!