このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

Erlang実験室:分かりにくいと評判のErlangエラーのまとめ

Erlangのエラーメッセージは暗号のようだ(Cryptic error messages)とか言われたりします。確かに分かりにくいです。http://bluebones.net/2006/12/erlang-error-messages/ に良いまとめがあった*1ので、これを敷衍<ふえん>する形で説明します。

内容:

  1. 例外の一般論
  2. ランタイムエラーとスタックトレース
  3. reasonタームの一覧

●例外の一般論

Erlangの例外(exceptions)は次の3種に分類されます。

  1. throw例外 : ユーザー(プログラマ)が任意の時点で生成する例外
  2. exit例外 : プロセス*2の終了に伴って生成される例外
  3. error例外 :主にシステムが生成するランタイムエラー

それぞれの例外を、次の関数で引き起こす(raiseする)ことができます。

  1. throw(Term)
  2. exit(Term)
  3. erlang:error(Term) または erlang:fault(Term)

(catch 式). を評価すれば、式で生成される例外データを見ることができます。


1> (catch throw(foo)).
foo
2> (catch exit(foo)).
{'EXIT',foo}
3> (catch erlang:error(foo)).
{'EXIT',{foo,[{erl_eval,do_apply,5},
{erl_eval,expr,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]}}
4>

throw例外とexit例外の発生はユーザー(プログラマ)が制御できますが、error例外はランタイムシステムが生成することがほとんどです。ランタイムシステムが検出したエラーに関しては、決まったフォーマットのデータが投げられます -- これが分かりにくいと評判なんですね。

●ランタイムエラーとスタックトレース

ランタイムシステムが投げるエラーデータ(Erlangターム)は次の形をしています。

  • {'EXIT', {Why, Where}}

Whyはreasonタームと呼ばれる部分で、次の節に、一覧表にまとめてあります。Whereはシンボリックに表現されたスタックトレースです。次の実例を見てみましょう。


4> (catch 1 / 0).
{'EXIT',{badarith,[{erlang,'/',[1,0]},
{erl_eval,do_apply,5},
{erl_eval,expr,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]}}
5>

スタックトレースの部分だけを取り出すと:


[
{erlang,'/',[1,0]}, % (1) erlang:'/'(1, 0)
{erl_eval,do_apply,5}, % (2) erl_eval:do_apply/5
{erl_eval,expr,5}, % (3) erl_eval:expr/5
{shell,exprs,6}, % (4) shell:exprs/6
{shell,eval_exprs,6}, % (5) shell:eval_exprs/6
{shell,eval_loop,3} % (6) shell:eval_loop/3
]

次のことに注意すれば読み解くことができます。

  1. 各項目は {モジュール, 関数名, 引数} の形。
  2. 引数のところが整数値なら、それは引数個数(アリティ)を表す。
  3. 引数のところがリストなら、渡された引数そのものを表す。
  4. 最初の項目が例外が起きた関数を示す。
  5. スタック全体が含まれるとは限らない。どこまで(何個分だけ)トレースするかは実装依存

●reasonタームの一覧

一覧表の例に書いてある式 を (catch 式). で実行すると当該ランタイムエラーが発生します。簡単に引き起こせないエラーの例は書いてありません。

理由 説明
badarg 引数の型が不正。 io:fwrite(foo, bar)
badarith 算術演算の引数が不正。 1 / 0
{badmatch,V} パターンマッチが失敗。値Vがマッチしない。 a = b
function_clause 関数呼び出しにおいて、マッチする関数節が見つからない。 io:fwrite(1, 2, 3)
{case_clause,V} case式において、マッチする分岐が見つからない。値Vがマッチしない。 case 1 of 2->ok end
if_clause if式において、真となる分岐が見つからない。 if false->ok end
{try_clause,V} try式のof節において、マッチする分岐が見つからない。値Vがマッチしない。 try a of b -> ok catch _ -> ng end
undef 関数呼び出しにおいて、関数が見つからない。 not_defined()
{badfun,F} 関数Fに関してなにか不具合がある。 -
{badarity,F} fun式に対する引数の個数が食い違っている。Fはfun式の実体と引数を記述するタプル。 (fun(X)->X end)(1, 2)
timeout_value receive after式のタイムアウト値が、整数でもinfinityでもない。 receive after a -> ok end
noproc 存在しないプロセスにリンクしようとした。 link(pid(0, 123, 456))
{nocatch,V} catchの外でthrow式を評価しようとした。Vはthrowしたターム。 spawn(fun ()->throw(foo) end)
system_limit システムリミットに到達した。システムリミットに関する情報は「Efficiency Guide」を参照。 -

*1:[追記]同じ内容は、http://erlang.org/doc/reference_manual/errors.html にありました。こっちがオリジナルで、bluebones.netブログのほうは単にコピーでした。[/追記]

*2:もちろん、OSプロセスではなくて、Erlang内の軽量プロセスです。