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

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

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

参照用 記事

Catyスクリプトの例外処理 これでほぼ決まりかな

棚上げになっていたCatyスクリプトの例外処理ですが、「これでいいかな」という案を考えました。型推論(静的型解析)に目星が付いたので、それをベースに他のことを考えることができるようになった、という事情です。以下に一通り説明し、最後に補足説明を付けます。

内容:

  1. Catyスクリプトで扱える例外
  2. catch式
  3. catch-handle式
  4. 宣言されていない例外
  5. いろいろと補足

Catyスクリプトで扱える例外

ネイティブコマンドは、実装言語の例外機構により例外を発生させてもかまいません。というか、それを避けることは事実上不可能です。ただ、コマンドが発生させたどんな例外でもCatyスクリプトで扱えるかというと、それは無理なので、一定の基準を満たす例外に限ってCatyスクリプト内で捕捉とハンドルをします。

まず、投げられる例外データの型は、前もって型定義しておく必要があります。


@exception
type FileNotFound = @FileNotFound object {
"filePath" : string(remark="ファイルパス"),
"stackTrace" : opaque // opaqueはCatyでは関知しないデータ型
};

@exceptionアノテーションは、その型を例外として使えることを表します(例外以外の目的で使ってもかまいません)。例外型は、必ずタグ付きでなくてはならず、そのタグが例外の種別を表します。タグ名の名前空間はフラットなので、階層的種別は提供しません。偶発的に同じタグを使ってしまった場合でも、同種の例外とみなされます。

例外の発生可能性はコマンド宣言に書きます。throwsに書けるのは、前もって例外型として宣言された型だけです。


command read-file [string(remark="ファイルパス")]
:: void -> _FileContent throws FileNotFound
refers python:mafslib.ReadFile;

catch式

catch {式} とすると、式のなかで発生した例外(ただし、Catyの例外)は捕捉され、捕まえた例外データがそのまま値として出力されます。catchブロック内部で、例外発生時点より後に実行予定だった部分は実行されません。

このcatch式は、古典的な(今ではほとんどすたれた)throw-catchセマンティクス*1を持ちます。例外という異常事態であることを無視して、単なる値に変換してしまうので、あまりよろしくないのですが、対話的に使うときなどはお手軽で便利です。

catch-handle式

catch {式} handle {ハンドラー} という構文を使うと、捕捉した例外をどうすべきかをハンドラー部に書けます。handle {ハンドラー} 部分の構文は、when式と同じです。ただし、例外が起きなかった場合を示すために、特殊な記号'-'をタグ代わりに使います。


catch {read-file /tmp/test.txt}
handle {
FileNotFound => print /tmp/not-found-error.html | break,
- ==> pass
} |
print /tmp/show-text.html

「- ==> pass」はよく使われる上に、「- => pass」と間違って書くと期待してない結果となる(タグを剥ぎ取ってしまう)場合があるので、省略することができ、省略すると「- ==> pass」とみなされます。handle節内に列挙してない例外は捕捉されず、その外側のcatch-handle式、最後はシェルによりハンドルされます。

handle節がないcatch式の場合、次のようなhandle節が処理系により補われると考えます。


catch {read-file /tmp/test.txt}
handle {
FileNotFound ==> pass,
- ==> pass
}

正常値も異常値(例外データ)もそのまま出力に流されるので区別が付かなくなるわけです。

宣言されていない例外

command宣言文にthrows節*2がなくても、それは例外が発生しないことを意味しません。また、throws節があっても、それ以外の例外が起きない保証はありません。ネイティブコマンドが、宣言されてない例外を発生させた場合は、そこで全体の評価は中止され失敗します。throws節で宣言されてない例外を、Catyスクリプト内で捕捉することはできず、評価を続けることもできません。インタプリタ自身が発生させるランタイムエラー(型のミスマッチなど)をスクリプトで捉えることもできません。

Webから起動されたCatyスクリプトの評価が失敗すると、ステータスコード500番が返され、エラーメッセージやスタックトレースがログに書き出されます。

いろいろと補足

型の名前は、パッケージ名とモジュール名で修飾できます。つまり、例外型は階層化できるのです。それなのになぜ、フラットなタグ名を分類に使うかというと、型名はインスタンスには記録されてないからです。捕まえた例外データ(JSONです!)をいくら眺めても型はわからないのです。そこで、when式と同じくタグ名による分岐になるわけ。型定義のとき、タグ名の衝突があれば警告することはできます。

記号「=>」の意味が以前とは変わっています。「=>」を通るときにデータのタグが剥ぎ取られます。「==>」を使うと、タグ付きのままで引き続く処理に流されます。

サンプルに出した次のコードはちょっと見にくいです。


catch {read-file /tmp/test.txt}
handle {
FileNotFound => print /tmp/not-found-error.html | break,
- ==> pass
} |
print /tmp/show-text.html

passを省略して:


catch {read-file /tmp/test.txt}
handle {
FileNotFound => print /tmp/not-found-error.html | break,
} | print /tmp/show-text.html

あるいは:


catch {read-file /tmp/test.txt}
handle {
FileNotFound => print /tmp/not-found-error.html,
- ==> print /tmp/show-text.html
}

Catyスクリプトは長くなると可読性が悲惨なことになるので、1行でも短く書いたほうがいいと思います。えっ? 可読性が悪くていいんかって。いいんです。「長いCatyスクリプトは書くな」ってのがCatyのメッセージですから。

割と無難にまとまったので、普通の(ワイルドでない)Catyスクリプトに入れてもいいかな、という気もしますが、無難てことは中途半端ってことでもあります。

  • 例外という概念がそもそも難しいって事実は変わらないので、学習負担になる概念を入れるべきかどうかは、やはり問題。
  • テストスクリプトを書くには例外捕捉が必須だが、その目的では捕捉能力が弱い気がする。

ウーム。

[追記]タイトルが「ほぼ決まりかな」なのに、最後は「ウーム」。あんまり決まりじゃないかもな。[/追記]

*1:通常はキャッチタグを持ちますが、Catyのcatch式はキャッチタグを持たず、例外は常に一番内側のcatch式で捕捉されます。

*2:節だっけ、句だっけ? まーどっちでもいいや