LuneScript がエラーハンドリングと大域脱出をサポートできていない理由

Page content

数ヶ月間 LuneScript から離れていますが、生存アピールのためにちょっと触れておきます

今日現在、 LuneScript は言語機能としてエラーハンドリングと大域脱出をサポートしていません。

現在でも、module 機能を利用して 裏技的にエラーハンドリングと大域脱出を使うことは出来なくもないです。 しかし、それはあくまでも裏技で正式機能ではありません。

「何故サポートしていないか?」というと、 エラーハンドリングと大域脱出のベストプラクティスが分からないためです。

なお、「今の LuneScript の言語仕様が全てベストプラクティスなのか?」と問われれば、 残念ながら違います。 しかし、 自分の中で納得した仕様 になっています。

一方で、「エラーハンドリングと大域脱出」に関しては、 イマイチ決めかねています。

エラーは例外か?

『エラー』を、『例外として投げて、投げられた例外を掴まえて処理する』という方法は、 よく使われています。 しかし、 この方法はなにか違う気がしています。

そもそも 『「例外」ってなんだ?』 という疑問が浮びます。

私は、「例外 == 普通は起らないような状態を表わすべきもの」と考えています。 例えば「kill ジグナルの受信」などが例外に該当すると思います。

しかし幾つかの言語では、 「正常系ではない異常系」を「例外」として扱かっているケースがあります。 例えば、「ファイルの書き込みに失敗した状態」が「例外」として扱われていたりします。

これは なにか違う 気がします。

何が違うって、「ファイルの書き込みに失敗した状態」なんていうのは、 良くあるケースです。 良くあるケースが「例外」って何か変じゃない? と感じるんです。

プログラムっていうのは、エラー処理を含めて完成するものだと私は思います。 つまり、エラー処理って「例外」じゃないですよね?と考えています。

まぁ、ユースケース記述でいうところの「メインフロー、代替フロー、例外フロー」のうち、 「例外フロー」がエラー処理になるから、やっぱり「例外」じゃないか? というツッコミはあると思います。 ですが、 ちょっとした操作ミス等で発生するエラーと、 普通は発生しないエラーとを「例外」という一つの概念で扱うのは間違っているんじゃないか? と感じています。

たとえば Java では ErrorRuntimeExceptionその他Exception とで、 これらエラーを区別して管理できるようになっていますが、 exception という 1 つの概念であることには違いありません。

区別できるという点では、Java のエラー種別は私の考えと一致しています。 しかし、 実際に Java でプログラムしているとメンドイなぁ、と思ってしまいます。

「メンドイ」と思ってしまうのは、「何かが違う」ということだと思います。

例えば nil 安全を実現する unwrap 等の処理は一手間かかりますが、 nil 安全によるメリットと、unwrap 等の一手間を天秤にかければ、 メリットの方が大きいと感じることができ、面倒とはそれほど思いません。

もちろん「それほど思わない」というのは「少しは思っている」ということであり、 nil 安全に関しても、もっと手間がかからない方法があるんじゃないか模索しています。

「例外」の良くないところは、 「エラー」と「大域脱出」が一括りに扱われてしまっているところだと思います。

「例外」が「 普通は発生しないエラー 」に限られるのであれば、 「大域脱出」とセットになっているのも理解できますし、合理的だと思います。 例えば、 kill シグナルを受けたら「大域脱出」するのは納得できますよね?

しかし上述した通り、「 ちょっとした操作ミス等で発生するエラー 」も 「例外」として扱う場合は、 「大域脱出」がセットになっているのは影響が大き過ぎると考えます。

「大域脱出」は最終手段であり、文法上も専用に扱わなければなりません。 この「専用の文法」が、「メンドイ」と感じる元になっています。

ちょっとした操作ミス等で発生するエラー 」は、 普通に発生する可能性があるものであり、 普通に発生するならば、 特別に扱うことなく普通に処理を書けるべきです。

go のエラーハンドリング

go のエラーハンドリングは error 型のデータを戻り値で返し、 それを処理することでエラーハンドリングしています。

なお、 error 型のデータはあくまでも関数の戻り値であって、大域脱出とは別ものです。

つまり、 go のエラーハンドリングは、特別に扱うことなく普通に処理が書けます。

この error 型によるエラーハンドリングは、 以下の go の特徴によって支えられています。

  • 関数の多値返却
  • defer

特に defer は go の大きな特徴と言えます。

この仕様をパクって「LuneScript でエラーハンドリングをサポートする」ことも考えましたが、 LuneScript はトランスコンパイラであり、 go の特徴に依存する実現方法は採用すべきでない 、 ということもあって採用を見送っています。

defer がなくても error 型を追加すれば それなりのエラーハンドリングは実現出来ます。 しかし、go のエラーハンドリングは defer があってこそです。

そもそも、error 型を追加するだけなら、 LuneScript の言語仕様として組込まなくても ユーザプログラムレベルで実現できますし。。

LuneScript でのエラーハンドリング

上で例として挙げた go だけでなく、 Rust でも関数戻り値の Option, Result 型でエラーを扱っています。

エラー型を追加し、それを処理することでエラーハンドリングする、 というのは「 特別に扱うことなく普通に処理を書けるべき 」という 私の考えにも合致します。一方で go の defer は便利ですが、 トランスコンパイル先の言語仕様に大きく依存します。

今後対応するかもしれないトランスコンパイル先の言語で、 defer と同等の機能が実現できない、 あるいは実現できてもコストが大きくかかる、ということが容易に想像できます。

現状でも、Lua では簡単には defer を実現できません。

そんな訳で、LuneScript はエラーハンドリングと大域脱出をサポートしていません。

言語仕様に依存せず、且つ、 簡便にエラーハンドリングを記述できる方法を模索中です。

とはいえ、 そう遠くない未来に、 必要に迫られてなんらかの方法を対応しなければならなくなる気はしています。