Elixir v1.20: ついに段階的型付け言語へ
(elixir-lang.org)- 型推論と段階的(gradually)型検査がすべての Elixir プログラムに適用され、型アノテーションがなくても、デッドコードや実行時に必ず失敗する検証済みバグを見つけられる
dynamic()型は、何でも許容するany()とは異なり、実行時に取り得る型の範囲を追跡し、許容される型とまったく重ならない場合にのみ違反を報告するdynamic(integer() or binary())の値は、数値演算や文字列関数のように一部の可能性が重なる呼び出しでは違反にならないが、Map.fetch!のように map だけを受け取る呼び出しでは違反になるdynamic()は使い方に応じて絞り込まれ、data.a + data.bのようなコードでは、dataを%{..., a: number(), b: number()}形式の map として精緻化する- guard では 和集合・積集合・否定 を推論し、
is_list、is_integer、is_map_key、not is_map_key、tuple_sizeのような条件を型情報として活用する caseと条件分岐は、先行する節の情報を次の節に反映し、先にnilを処理したあと残りの値をbinary()に絞り込む、といった型検査を行う- 標準ライブラリの tuple・map 関連の複数の関数に型が追加され、既存コードベースで重複した節やデッドコードを見つける助けになる
- “If T: Benchmark for Type Narrowing” では 13 カテゴリ中 12 カテゴリを通過し、一般的な Elixir コードで高精度な型情報を復元できることを示した
- v1.20 はマルチコア環境のアプリケーションでコンパイル時間を再び改善し、合成ベンチマークでは Elixir のビルドツールが BEAM 言語の中で最速の結果を示した
- 新しいコンパイラオプション
:module_definitionにより、module 定義の実行方式をデフォルトの:compiledまたは:interpretedから選べるようになり、mix.exsのelixirc_options: [module_definition: :interpreted]で有効化できる :module_definitionオプションは、ディスクに書き込まれる.beamファイルには影響せず、defmodule内部の実行方式だけを変更するもので、大規模プロジェクトのコンパイル時間改善に役立つ可能性がある- 集合論的型を活用する新しい型シグネチャは、v1.20 の型システム性能、再帰型、パラメータ化型、map の key-value 走査に関する研究が満たされたあと、typed struct 定義とあわせて議論される予定である
1件のコメント
Hacker Newsの反応
個人的な経験かもしれないが、最初から型があった言語でない限り、真の静的型付き言語ほどうまく機能しない気がする
Elixir のプロ開発者として10年ほど働いてきて、型の導入を長く待ち望んでいた。今回その第一歩が実際に入ったのは本当にうれしい
ただ、v1.20 に入った現状が、仕様なしの Dialyzer と比べてどうなのか知りたい。Dialyzer の**成功型(success typing)**アプローチは、「失敗しうる引数の組み合わせがあれば警告」ではなく、「動作する引数の組み合わせが1つでもあれば警告しない」に近いと理解しているが、Elixir がここでやっていることも似たものだと思っていたし、Dialyzer はあまり有用だと感じなかった
Elixir の段階的型システムに関する記事は HN で何度か見たが、詳しく追ってはいなかった。この段階的型システムが、型なしコードと比べてプログラムの漸近的計算量を変えうるのか知っている人がいるか気になる
私の知る限り、Racket のような大半の段階的型システムは、プログラムを漸近的に遅くしうるし、例外も一部ある [1]
[1] https://doi.org/10.1145/3314221.3314627
ほとんどの段階的型システムは、型付きコードと型なしコードの境界を値がまたぐときに強制を挿入する。たとえばリストの全要素を検査したり、値を型プロキシで包んだりする。しかし Elixir チームは、そうしたランタイム検査なしで健全性を得るためにstrong arrowsの結果を発表しており、コンパイラが出力するバイトコードは型なしコードと意味的に同一である
皮肉なことに、批判者たちは型が必要だと言い、Elixir ファンは型は不要で Elixir はなぜか魔法のようだから型関連のバグは起きないと言っていたのに、いま型を入れたらバグを見つけてくれている。バグ防止に必要ないのではなかったのか? それでも良い変化だ。以前 Elixir をかなり使って楽しんでいたが、型の不在には同意しづらかった
https://en.wiktionary.org/wiki/Goomba_fallacy
その観点には同意しないが、「$LANGUAGE は魔法」よりははるかに擁護可能な主張だ
集合論的型理論が発展する前は、そうした立場にも妥当性があった可能性がある
そして結局、静的型が追加される。Python、JavaScript、Ruby で起きたし、ほかにもあるだろう
Elixir を更新しても複数のプロジェクトで破壊的変更がなく、しかもコンパイラが無料でバグを見つけてくれるのは本当に素晴らしい。すっかり慣れてしまった
これを見ると本当にうれしい。これで「素晴らしい言語」にかなり近づいていて、私にとって Elixir は第一候補だ
すでに使いやすいのに、優れた機能を安定的かつ安全に追加し続けている他の言語があるなら教えてほしい。Go をある程度極めたあと、高度な C# を学ぶ方向に移ったが、Go は良い機能追加が止まった感じがしていた
この1か月、Elixir exercism.io トラックを進めてみた https://exercism.org/tracks/elixir
本当に素晴らしい
ああ、また始まった。また1年かけて Elixir を学び直すことになりそう
Elixir のあらゆる点が好きだけど、どの言語よりも Elixir は自分自身を疑わせてくる。自分の脳は関数型に向いていない気がするけれど、この変化のせいでまた挑戦してみたくなる
残念なのは、エコシステムが初心者に優しいとは言い難く、質問に答えるときにたいていすでにその言語をよく知っている前提になっていることが多い点だ
タイトルにだまされないように。この本の前半はただの Elixir だ
この8年間、Elixir に再び慣れようとするたびにこの本を使ってきたが、毎回うまくいった。最後まで読んだことはない
こういうチュートリアルプロジェクト形式のプログラミング本が良い本かどうかを見る基準のひとつは、何度も読み始めても最後まで行かなくてよく、中ほどまでで自分の仕事に取りかかれる道具がもう手に入るかどうかだ
でも、脳が向いていないというより、命令型言語で積み上げた経験値と、純粋関数型スタイルではまた初心者として始まることとの対比なんだと思う
だんだん良くなっていくはず。関数型プログラミングがしっくりきたきっかけは、自分がゆったりスペースを入れた Bash の「ワンライナー」っぽいコードを組み合わせるのがどれほど好きかに気づいたときだった。データがある形で始まったら、コマンドで吐き出し、欲しい形に近づく段階を考えて次のコマンドにパイプし、また眺める。それを続けていくと、最後にはたいてい変更されないデータ変換の連なりが残る
シェルでこれが心地よい理由のひとつは、毎日ファイルシステムをうろつきながらコマンドの語彙を蓄えているからだ。Unix 系環境で馴染みのある「関数」ライブラリは、何年もかけてかなり大きくなっている。純粋関数型プログラミング環境でも同じことをしなければならないが、語彙を覚えるのに少し余計な労力がかかる。よく使う「コマンド」は grep、cat、sort ではなく map、fold、zip のような関数になる
でも本質は本当に同じで、パイプラインを組む魅力も両方にまったく同じように当てはまる。少しずつ作れるし、それぞれのパズルでは前の段階は忘れて、目の前のデータを次にどう変換するかだけを考えればいい。この低いコンテキスト依存性が新鮮で心地いい
ぜひ試して楽しんでほしい。何かができない状態を楽しめるようになったとき、ようやく本当に上達する
もちろん数回の午後を使った程度だけど、もう一度関数型言語に自分の脳を慣らすなら、その親しみやすさゆえに Gleam を選ぶと思う
たまに曖昧すぎて関心を持たれないとか、「宿題を代わりにやってくれ」臭がして無視される投稿はある
でも本物の好奇心がこもった投稿なら、自分が見る限りどれも返答をもらっている
すばらしい。1.20 では、うちの大きな umbrella アプリ のコンパイルがかなり速くなったように見える
Gleam と比べてどうなのか気になる。あるいは、これでなぜ Gleam ではなく Elixir を使うべきなのだろう? Phoenix、とりわけ LiveView が Elixir の大きな魅力ではありそう
Gleam OTP の現状は知らないが、最後に見たときは良くなかった
どちらでもよくて型だけが重要なら Gleam を使えばいい。でも、それならただ Rust を使えばいいのでは?
たとえば Gleam では JSON のデコード/エンコードが冗長になりがちだ。Rust なら serde を derive すればいいし、Elixir なら関数呼び出しひとつで済む
Elixir にはより成熟したエコシステムがある。たとえば Gleam で Phoenix やほかの Gleam フレームワークを使うことはできるが、体験は同じではない
Gleam が Elixir より魅力的に見える大きな理由は型で、Elixir はいまその差を縮めつつある。さらに JavaScript にコンパイルできる点もあるが、Elixir では Hologram が似たようなことをしている
個人的には Gleam の型システムと Rust ライクな構文のほうが好きだが、現時点では自分のすべての Web 開発プロジェクト にとって Elixir のほうがより良い選択だと感じる