For Development HEAD DRAFTSearch (procedure/syntax/module):

6.19 例外

Gaucheの例外システムは次の3つのコンポーネントから構成されています。 (1)例外状況が発生したことを通知する方法、 (2)例外状況をどのように処理するかを指定する方法、 (3)例外状況を知らせたコードとそれを処理するコードがやりとりするための 標準オブジェクト(コンディション)。

普通これらの3つのコンポーネントは一緒に使われます。 そこで、最初に例を用いて典型的な使い方について説明し、 そのあとでそれぞれの機能について詳しく解説します。

用語について: いくつかの言語では例外(exception)というと、 例外的状況に遭遇したコードとそのハンドラがやりとりをするために用られる オブジェクトのことを指します。Gaucheではそのようなオブジェクトのことを言うときには、 SRFI-35にならってコンディション(condition)を使います。 例外というのは、状況であり、コンディションはそれを記述する 実行時のオブジェクトです。


6.19.1 例外処理の概要

特定のエラーを捕まえる

最もよくある例外処理のひとつは、組み込みあるいは ライブラリの手続きから発生した特定のエラーを捕捉するというものです。 guardマクロがこのような目的の場合に使えます。 コードは以下のような感じになるでしょう。

(guard (exc [(condition-has-type? exc <read-error>)
             (format #t "read error!")
             'read-error]
            [else 'other-error])
  (read-from-string "(abc"))

guard節の cadr 部は (variable clause …) という 形式です。この例では、変数は excで、2つの節があります。 それぞれの clausecond と似た形式になります。

guard の cddr 部は本体で、式のリストです。この例では、式は (read-from-string "(abc") のひとつだけです。

guard はその本体部を実行するところから始めます。 read-from-string は構文エラーに出くわすと、<read-error>型の エラーを発生させます。guardフォームがこのエラーを捕捉し、 そのコンディションオブジェクトを変数excに束縛し、excの後の 節を、condと同じようにチェックします。この場合、投げられた コンディションは <read-error>なので、最初の節のテストを満し、 その節の残りの部分が実行されます。すなわち、"read error!" が 印字され、シンボル read-error が返ります。

他の言語を使い慣れていれば、同じパターンであることがわかると思います。 guard フォームの cddr 部は、C++やJavaの try 節、あるいは、 Common Lisp の handler-case の cadr 部に似ています。 また、guard フォームの cdadr 部は、catch 節あるいは、 handler-case の cddr 部に似ています。

テスト式においては、投げられたコンディションのタイプをチェックする のが普通です。condition-has-type? という関数が SRFI-35 で定義されていますが、これはちょっと冗長です。Gauche の コンディションクラスは述語のようにも使えるようになっており、上の 式は以下のように書くこともができます。

(guard (exc [(<read-error> exc)
             (format #t "read error!")
             'read-error]
            [else 'other-error])
  (read-from-string "(abc"))

注意事項: 一般的には、投げられたコンディションが特定の タイプであるかをチェックするのにis-a?は使えません。 コンディションが合成されたものである可能性があるからです。 合成されたコンディションについての 詳細は コンディション を参照してください。

もし、clause のどのテストも満されず、かつ else 節があたえられて いなければ、その例外は guard から「抜け」ます。すなわち、guard の外側のレベルあるいはトップレベルで処理されることになります。たとえば、 以下の guard フォームでは、<read-error><system-error> としか処理できず、もし、本体が他のタイプのコンディション を投げてきたら、その外側のレベルで処理しなければなりません。

(guard (exc [(<read-error> exc) (handle-read-error)]
            [(<system-error> exc) (handle-system-error)])
  body ...)

guardおよび他の低レベルの例外処理の構成についての詳細は 例外の処理 を参照してください。

自分のコードから例外を通知する

例外を通知する一般的な方法は raise手続きを使うことです。

(raise condition)

conditionにはどんなオブジェクトでも渡すことができます。 それをどのように解釈するかはひとえに例外ハンドラにかかってます。 もし、コンディションとして整数があがってくるというのが判っていれば、 guard で以下のように捕捉することができます。

(guard (exc [(integer? exc) 'raised])
  (raise 3))

とはいうものの、<condition> あるいはそのサブクラスのインスタンスを 使うのが好ましいというのが通例です。condition マクロはコンディション オブジェクトを作成するのに使えます。以下の例は、いくつかのスロット値をもち それらを発生させるコンディションの作りかたを示したものです。

;; create and raise an error condition
(raise (condition
        (<error> (message "An error occurred."))))

;; create and raise a system error condition
(raise (condition
        (<system-error> (message "A system error occurred.")
                        (errno EINTR))))

condition マクロおよび、どのようなコンディションクラスが用意 されているかの詳細については コンディション を参照してください。

最も一般的なコンディションのタイプはエラーコンディションなので、 error および errorf という便利な手続きが 用意されています。これらはメッセージ付きのエラーコンディションを 生成し、それを発生させます。

;; `error' concatenates the arguments into a message.
(unless (integer? obj)
  (error "Integer expected, but got:" obj))

;; `errorf' uses format to create a message.
(unless (equal? x y)
  (errorf "~s and ~s don't match" x y))

いくつかの言語での例外を投げる機構、たとえば、 C++やJavaの throw はその継続を破棄します。これとは違い Schemeの raise はその呼び出し元へ戻ることができます。もし、 raise で元へもどらないで欲しいのなら、簡便な方法としては、 常にエラーコンディションの一つをわたるようにするというのがあります。 そうすると Gauche では raise は戻らないことを保証します。 raiseの詳細については、例外の通知を参照してください。

註: R7RSでは少し違ったセマンティクスを採用しています。 raiseは継続不可能な例外を投げるものとして(もし例外ハンドラから 制御が戻ってきたら、別のエラーを報告します)、 別に継続可能な例外を投げるraise-continuableという手続きを 設けました。R7RS環境にいるときは、本項のraiseではなく R7RS互換のraiseが見えるようになっています。

独自のコンディションを定義する

独自のコンディションクラスを定義することが可能で、そうすることで、 アプリケーション固有の情報を例外が発生した点からハンドラへ渡すことが できます。

Gauche のフレームワーク(SRFI-35)に適合させるためには、新しく定義する コンディションクラスは組み込みの <condition> クラスあるいは その子孫を継承し、また、メタクラス <condition-meta> のインスタンスであることが望まれます。

可搬性を増すと同時に上の慣例を確実にするための方法のひとつは、 define-condition-typeマクロを使うことです。これは、 srfi.35で定義されています。

(define-condition-type <myapp-error> <error>
  myapp-error?
  (debug-info myapp-error-debug-info)
  (reason myapp-error-reason))

これは、(Gauche内のクラス)<myapp-err>を定義するもので、 このクラスにはmyapp-error?という述語とアクセサのあるスロット があります。こうすれば、以下のようなコードで新しいコンディション 型が使えます。

(guard (exc
         [(myapp-error? exc)
          (let ([debug-info (myapp-error-debug-info exc)]
                [reason (myapp-error-reason exc)])
            ... handle myapp-error ...)])
  ...
  ...
  (if (something-went-wrong)
    (raise (condition
             (<myapp-error> (debug-info "during processing xxx")
                            (reason "something went wrong")))))
  ...
  ...
  )

SRFIとの互換性が重要でないなら、Gaucheの拡張されたerror手続きを 使うと<error>のサブタイプであるコンディションを投げるコードを より簡潔に書くことができます。

  (if (something-went-wrong)
    (error <myapp-error>
           :debug-info "during processing xxx"
           :reason "something went wrong"))

Gauche のオブジェクトシステムでコンディション型がどのように実装されているかは define-condition-typeマクロの解説を参照してください。


6.19.2 例外の通知

エラーの通知

最も良くある例外ケースはエラーです。単純なエラーを通知するために、 ふたつの簡単な関数が用意されています。 複合コンディションを通知する必要がある場合は 下で説明するraise手続きを使って下さい。

Function: error string arg …
Function: error condition-type keyword-arg … string arg …

[R7RS+ base][SRFI-23+] エラーを通知します。最初の形式は、 stringarg …からなるメッセージを持つ <error>コンディションを作成それをraiseします。 この形式はR7RS及びSRFI-23のerrorと互換です。

gosh> (define (check-integer x)
        (unless (integer? x)
           (error "Integer required, but got:" x)))
check-integer
gosh> (check-integer "a")
*** ERROR: Integer required, but got: "a"
Stack Trace:
_______________________________________

2番目の形式は<error>以外のエラーコンディションを通知したいときに 使います。condition-typeはコンディションタイプ でなければなりません (コンディションタイプについてはコンディションを 参照して下さい)。その後に、キーワードと値のリストを与えることで コンディションのスロットを初期化することができます。また、その後に メッセージを構成する文字列と他のオブジェクトのリストを与えることができます。

(define-condition-type <my-error> <error> #f
  (reason)
  (priority))

...
  (unless (memq operation *supported-operations*)
    (error <my-error>
           :reason 'not-supported :priority 'urgent
           "Operation not supported:" operation))
...
Function: errorf fmt-string arg …
Function: errorf condition-type keyword-arg … fmt-string arg …

errorに似ていますが、エラーメッセージはformatによりフォーマット されます。すなわち、最初のフォームは以下と等価です。

(define (errorf fmt . args)
  (error (apply format #f fmt args)))

2番目の形式は<error>以外のエラーコンディションを通知するのに 使えます。condition-type, keyword-argの意味については errorと同じです。

一般的な条件の通知

Function: raise condition

[SRFI-18][R7RS base] これは、例外事態を通知する基本となるメカニズムです。

この手続きは現在の例外ハンドラを呼び出します。引数conditionは 例外の性質を表現するのに使われ、例外ハンドラに渡されます。 Gaucheの組み込み手続きやライブラリ手続きは常に、<condition>クラス もしくはそのサブクラスのインスタンスをconditionとして用いますが、 ユーザは任意のオブジェクトをraiseに渡すこともできます。 渡されたconditionの解釈は例外ハンドラに任されます。

注意事項: いくつかの主流の言語では、例外を「投げる」と制御はそこに 戻りません。Gaucheではraiseから戻って来れるように セットアップすることが可能です。詳細は例外の処理を 参照してください。

raiseから戻って来ることが無いようにしたい場合は、 <serious-condition>やそのサブクラスのインスタンスをcondition としてraiseに渡すのが確実です。 組み込みコンディションのクラス階層についてはコンディションを参照して下さい。

R7RSではやや異なるセマンティクスを採用しています。R7RSのraiseは 決して戻りません—もしハンドラが戻ってきた場合は、別の例外が投げられます。 R7RSはraise-continuableという別の手続きで、ハンドラから 戻っても良いことを明示します。ポータブルなプログラムでは、 raiseには常に<serious-condition>かそのサブクラスを 渡すようにするのが良いでしょう。


6.19.3 例外の処理


6.19.3.1 上位レベルの例外処理機構

Macro: guard (var clause …) body …

[R7RS base] これはGaucheでのエラー処理の高水準フォームです。

var はシンボルで clausecond 節と同じ形式です。 つまり、各節は以下の形式のどれかひとつです。

  1. (test expr …)
  2. (test => proc)

最後のclause(else expr …)という形式も許されます。

このフォームは通常の場合には body … を評価し最後の body の式の値を返します。本体の式を評価している最中に例外が 発生した場合、発生した例外を変数 var に束縛し、その後 各節の test 式を評価します。もし、test 式のひとつが 真値を返したとき、その節が上述の最初の形式であれば、対応する expr が評価されます。あるいは、節が二番目の形式であれば、 手続きproctestの結果が渡されます。

指定された節の test および expr が評価されるとき、 guardを呼び出した時点での例外ハンドラが設定されます。つまり、 clause 内部で再び例外が発生した場合、その例外は、外側の 例外ハンドラまたは guard フォームで処理されます。

もし、どの test も真値を返さず、最後の clauseelse 節であれば、それに結びつけられた expr が評価されます。 もし、どの test も真値を返さず、else 節がなければ、再び 例外が発生し、外側の例外ハンドラで処理されます。

例外が clause のどれかで処理された時には、guard は それを処理した節の最後の expr の値を返します。

clauseguardと同じ動的環境で評価されます。すなわち、 body中のdynamic-windclauseの評価の前に 巻戻されます。これは低レベル関数のwith-error-handlerwith-exception-handlerとは異なることに注意してください。 これら低レベル関数では例外ハンドラが呼ばれてから動的環境が巻戻されます。

(let ([z '()])
  (guard (e [else (push! z 'caught)])
    (dynamic-wind (lambda () (push! z 'pre))
                  (lambda () (error "foo"))
                  (lambda () (push! z 'post))))
  (reverse z))
 ⇒ (pre post caught)

(guard (e [else (print 'OUTER) #f])
  (with-output-to-string
    (lambda ()
      (print 'INNER)
      (error "foo"))))
 ⇒ OUTERを文字列ポートではなくguard実行時の
      current-output-portに出力。
Macro: unwind-protect expr cleanup …

exprを実行してからcleanup …を実行し、exprの結果を返します。 expr内で継続不可能な例外が挙がった場合、その例外がunwind-protectフォー ムを抜ける前に、cleanup …が実行されます。たとえば、以下のコードで はなにも問題が起きなければ、start-motordrill-a-holestop-motorが、この順で呼ばれます。何か不具合が start-motorあるいはdrill-a-holeで起った場合でも、例外が unwind-protectを抜ける前にstop-motorが呼ばれます。

(unwind-protect
  (begin (start-motor)
         (drill-a-hole))
  (stop-motor))

cleanupフォームはunwind-protectと同じ動的環境で評価されま す。例外がcleanup内で投げられた場合その例外は、 unwind-protectフォームの外側で処理されることになります。

このフォームはdynamic-windと似ていますが、この2つは動作するレイ ヤが違いますので混同しないようにしてください。 dynamic-windは最下位レイヤのもので、現在の例外ハンドラ、現在の入出力 ポート、パラメータなどを管理するのに用います。 dynamic-windbeforeおよびafterのサンクは 対応する制御フローの遷移が起きたときに必ず呼ばれます。 一方、unwind-protectはGaucheの例外システムの面倒しか見ません。 unwind-protectcleanupexprが正常終了するか Gaucheの例外を投げたときにのみ呼びだされます。上述の例で、unwind-protectの外側で 補足された継続が呼ばれ、制御がdrill-a-holeを抜けると、 cleanupは呼ばれません。制御が再びdrill-a-holeに戻る可能性 があるからです。たとえば、ユーザレベルのスレッドシステムが call/ccで実装されているような場合にこのようなことが起こる可能性 があります。

expr内で捕捉された継続をunwind-protectの外側で起動することで、 expr内に再び戻ることは可能です。

ただ、ひとたびcleanupが実行されてしまったら、それによってクリーンアップ されたリソースはexpr内から使えなくなっているという可能性に留意してください。 そういったリソースに依存しない計算なら再実行できるので、再起動自体は禁止されていません。

exprから複数回戻った場合(通常の評価でもエラーでも)、cleanupが 実行されるのは最初の時のみです。

このフォームの名前はCommon Lispから取りました。同様のマクロに try-finallyというような別の名前を使っているSchemeの処理系もあります。

Function: with-error-handler handler thunk

handlerをアクティブなエラーハンドラにし、thunkを実行します。 thunkが正常に戻ったら、その結果が返されます。 thunkの実行中にエラーが通知されたら、エラーを表す例外オブジェクトを 1引数とするhandlerが、with-error-handlerの継続とともに呼ばれます。 すなわち、with-error-handlerは、handlerが返す値を返します。

handlerがエラーを通知したら、それはwith-error-handlerが 呼ばれたときにインストールされていたハンドラにより処理されます。

handlerが実行される場合の動的な環境は、エラーが起きたときのそれと 同じです。thunk内でdynamic-windが使われていたら、 そのafterメソッドはhandlerが戻った後、かつwith-error-handlerが 戻る前に呼ばれます。

註: この手続きを直接使うことはもはや推奨されませんguardの方が より安全でポータブルだからです。互換性を保つためまだしばらくは この手続きを残しますが、この手続きを使っているコードをguardによって 書き直すことを推奨します。「エラー時に後始末をする」というよくある次のような処理は:

(with-error-handler (lambda (e) (cleanup) (raise e))
  (lambda () body ...))

次のように書き直すことができます。

(guard (e [else (cleanup) (raise e)])
  body ...)

6.19.3.2 処理されなかった例外のふるまい

プログラムで定義した例外ハンドラを設定していないところで例外が発生した 場合以下のようなことが起ります。

  1. もしメインスレッド以外のスレッドで捕捉されない例外が起きた場合、そのスレッドは終了し、 投げられた例外は<uncaught-exception>でラップされて スレッドオブジェクトに保存されます。他のスレッドがthread-join!で スレッドの結果を取り出そうとした時に、その<uncaught-exception>が投げられます。 元の捕捉されない例外が起きた時点では何もメッセージなどが表示されないことに注意してください。 詳しくはスレッドプログラミングTipsを参照。
  2. そうでなく、プログラムが対話的に走っている場合(REPL)、投げられた例外の情報とスタッ クトレースが表示され、プログラムはトップレベルのプロンプトに戻ります。
  3. そうでなく、プログラムが対話的に走っているのではない場合は、投げられた例外の情報と スタックトレースが表示された後、プログラムは終了コード EX_SOFTWARE (70)で終了します。

上の2と3でのエラーメッセージとスタックトレースは、report-error手続きによって 出力されています。自分のエラーハンドラ内で同じ情報を出力したければ これを使うことができます。

Function: report-error exn :optional sink

投げられたコンディションオブジェクトexnの型とメッセージを表示し、 それからスタックトレースを出力します。REPLでエラーが報告される時の 表示を出しているのがこの手続きです。

raiseは任意のオブジェクトをコンディションとして投げることが出来るので、 exn<condition>オブジェクトの インスタンスである必要はなく、どんな型でも許されます。 report-errorは適切なメッセージを選んで表示します。

出力の行き先は省略可能引数sinkで指定できます。出力ポートを渡せば そこに出力されます。また、formatと同様に、#tを渡すことで 現在の出力ポートに、#fを渡すことで一時的な文字列ポートに出力できます。 #fを渡した場合、一時的な文字列ポートに出力された文字列が 返り値となります。その他の場合は未定義値が返されます。 sinkが省略されるか、上記以外の値であった場合は 現在のエラーポートが使われます。

内部的に、この手続きはexnの情報を表示するために print-default-error-headingおよび print-additional-error-headingを呼び、その後スタックトレースを表示します。 スタックトレースが不要な場合、これらの手続きを直接呼ぶこともできます。

註: 0.9.5の時点で、この手続きはexnが投げられたコンテキストでの スタックトレースではなく、report-error自身が呼ばれたコンテキストでの スタックトレースを表示します。report-errorをエラーハンドラから 直接呼んでいる限りにおいてはあまり違いは出ませんが、一般的に望ましいのは 前者なので、将来的には<condition>オブジェクトにスタックトレース情報を つける予定があります。

Function: print-default-error-heading exn out

投げられたコンディションexnについて、デフォルトのエラーレポートの 1行目を出力ポートoutに書き出します。

exn<condition>なら、 コンディションクラス名(からブラケットを取り除き、大文字化したもの)と、 コンディションメッセージが出力されます:

*** READ-ERROR: Read error at "foo":line 1: EOF inside a list (starting from line 1)

exn<condition>でない場合は、 本来補足されるべきであったコンディションがすり抜けたとみなし、 例えば次のようなメッセージが出力されます:

*** ERROR: unhandled exception: foo

この手続きはreport-errorが使っていますが、 この手続きだけを直接呼び出すことも可能です。

Function: print-additional-error-heading exn out

投げられたコンディションexnについて、追加の情報を出力ポートoutに書き出します。

exn<condition>でなければ、何も出力されません。

exnが複合コンディションの場合は、まず個々のコンディションに分解されます。

次に、各コンディションのうち<mixin-condition>を継承していない コンディションについてreport-additional-conditionメソッドが呼ばれ、 最後に<mixin-condition>を継承しているコンディションについて report-additional-conditionメソッドが呼ばれます。

例えば、次のようなエラーレポートがあった場合:

*** UNBOUND-VARIABLE-ERROR: unbound variable: load
    NOTE: `load' is exported from the following modules:
     - scheme.r5rs
     - scheme.load
    While loading "/home/shiro/src/Gauche/src/../src/ttt.scm" at line 3
    While compiling "./tttt.scm" at line 1: (use ttt)
    While loading "./tttt.scm" at line 1
  • 最初の行(***で始まる行)は print-default-error-headingが出しています。 残りはprint-additional-error-headingが出しています。
  • 投げられたコンディションは、 <unbound-variable-error><load-condition-mixin><compile-error-mixin>、そしてもうひとつの <load-condition-mixin>の複合コンディションです。
  • NOTEから始まる3行は、<unbound-variable-error>report-additional-conditionメソッドが出力しています。
  • While loading ...の行は<load-condition-mixin>report-additional-conditionメソッドが出力しています。
  • While compiling ...の行は<compile-error-mixin>report-additional-conditionメソッドが出力しています。

この手続きはreport-errorが使っていますが、 この手続きだけを直接呼び出すことも可能です。

Generic Function: report-additional-condition condition out

コンディションオブジェクトconditionについての追加の情報を 出力ポートoutに出力します。 いくつかの組み込みのコンディションはこのジェネリックファンクションを特定化しています。 具体例は上のprint-additional-error-headingの項を見てください。


6.19.3.3 下位レベルの例外処理機構

このレイヤはSRFI-18互換のシンプルな例外メカニズムを提供します。 with-error-handlerのような高次元の構造の振る舞いを、 with-exception-handlerを使って上書きすることができます。

これは諸刃の剣であることに注意して下さい。あなたは独自の例外処理 セマンティクスを構築する自由がありますが、Gaucheシステムは何か 間違いがあっても救ってくれません。システムの高次元のセマンティクスを カスタマイズしたいか、他のSRFI-18準拠のコードを移植している場合にのみ、 これらのプリミティブを使って下さい。

Function: current-exception-handler

[SRFI-18] 現在の例外ハンドラを返します。

Function: exception-handler-stack

[SRFI-226] 現在の例外ハンドラスタックにある例外ハンドラのリストを返します。 リストは新にアロケートされたもので、最後に設定されたハンドラが最初に来ます。

Function: with-exception-handler handler thunk

[R7RS base][SRFI-34] handlerは1引数を取る手続きです。この手続きは、handlerを 現在の例外ハンドラにセットし、thunkを呼び出します。 (この手続きはSRFI-18のwith-exception-handlerと微妙に異なります。)

例外がraiseerrorで通知されると、 投げられたコンディションを引数としてhandlerが 呼び出し元の動的環境のうち、例外ハンドラだけがwith-exception-handler 呼び出し時点のものに置き換えられた環境で呼び出されます。

註: SRFI-18のwith-exception-handlerでは、 例外ハンドラは例外発生箇所と全く同じ動的環境で呼び出されるとしています。 つまり例外ハンドラ中で例外が発生すると、同じハンドラが再び呼ばれます。 これは、with-exception-handlerを最も基本的なレイヤとして、 その上に必要なセマンティクスを実装することが意図されていたからです。

0.9.10まではGaucheの組み込みのwith-exception-handlerはSRFI-18の セマンティクスで、その上にR7RSのwith-exception-handlerが実装されていました。 しかし、SRFI-18セマンティクスは非常に使い方を間違えられやすかったのです。 ユーザはハンドラ中で発生した例外は外側のハンドラで処理されると思い込みがちでした。 正しくハンドラの置き換えを行わないと、ハンドラ中の例外はハンドラへの無限再帰を引き起こし、 Cのスタックを食いつぶしてsegfaultします。実質的に全ての場合においてユーザが期待するのは R7RSセマンティクスなので、切り替えることにしました。

例外がerrorにより投げられた場合、あるいは投げられたコンディションが <serious-condition>を継承していた場合、 handlerから戻ることは禁止されます。もし戻ってしまったら、 ハンドラを外側のものに置き換えた上で別のエラーが投げられます。 したがって、errorの呼び出し元、あるいは<serious-condition>を 引数としたraiseの呼び出し元は、 その呼び出しが決して戻らないと考えて構いません。

これらの手続きの振る舞いは、次の概念的なSchemeコードによって 説明されるでしょう。

;; 低レベルな例外メカニズムの概念的な実装
;; %xhは例外ハンドラのリスト

(define (current-exception-handler) (car %xh))

(define (raise exn)
  (let ((prev %xh))
    (dynamic-wind
      (lambda () (set! %xh (cdr %xh)))
      (lambda ()
        (receive r ((current-exception-handler) exn)
          (if (uncontinuable-exception? exn)
            (raise (make-error "returned from uncontinuable exception"))
            (apply values r))))
      (lambda () (set! %xh prev)))))

(define (with-exception-handler handler thunk)
  (let ((prev %xh))
    (dynamic-wind
      (lambda () (set! %xh (cons handler %xh)))
      thunk
      (lambda () (set! %xh prev)))))

6.19.4 コンディション

組み込みコンディションクラス

現在のところGaucheには以下の組み込みコンディションクラスの階層があります。 これは、おおよそのところ SRFI-35 および SRFI-36 のコンディションの階層を 反映したものですが、Gauche風のクラス名になっています。対応する SRFI の コンディションタイプがあるものについては、SRFI でのクラス名も使えます。

  <condition>
    +- <compound-condition>
    +- <serious-condition>
    |    +- <serious-compound-condition> ; also inherits <compound-condition>
    +- <message-condition>
         +- <error>                      ; also inherits <serious-condition>
              +- <system-error>
              +- <unhandled-signal-error>
              +- <continuation-violation>
              +- <read-error>
              +- <io-error>
                   +- <port-error>
                        +- <io-read-error>
                        |    +- <io-decoding-error>
                        +- <io-write-error>
                        |    +- <io-encoding-error>
                        +- <io-closed-error>
                        +- <io-unit-error>
                        +- <io-invalid-position-error>

いくつかのコンディションが同時に発生することがあることに注意してください。 たとえば、ファイルの読み込がデバイスの欠陥により失敗した場合は、 <system-error> および <io-read-error> の両方からなる エラーとなるでしょう。 このような場合、合成したコンディション (compound condition) が発生します。 したがって、たとえば、<io-read-error> が投げられたかどうかをチェック するのに、単に (is-a? obj <io-read-error>) を使えばよいというわけ にはいきません。 後述の「コンディション API」の節を参照してください。

Metaclass: <condition-meta>

すべてのコンディションクラスはこのクラスのインスタンスです。 このクラスは object-apply を定義していますので、 コンディションクラスは述語として使うことができます。たとえば、

(<error> obj) ≡ (condition-has-type? obj <error>)
Class: <condition>
Condition Type: &condition

[SRFI-35] コンディションの階層のルートクラスです。

Class: <compound-condition>

合成コンディションを表現します。合成コンディションは make-compound-condition を用いて1つ以上のコンディションから 生成することができます。このクラスを直接使ってはいけません。

合成コンディションはcondition-has-type? に対して、 元のコンディションのどれかが、与えられたタイプなら、#t を返します。

Class: <serious-condition>
Condition Type: &serious

[SRFI-35] このクラスのコンディションは無視して先を続けることはできない深刻な 状況のためにあります。特に、このタイプのコンディションを raise したら、それは元には絶対に戻らないと考えて問題ありません。

Class: <serious-compound-condition>

深刻なコンディションを含む合成コンディションを表現するための 内部クラスです。<compound-condition> および <serious-condition> の両方を継承しています。 make-compound-condition は深刻なコンディションを含む複数の コンディションを渡されると、このクラスを使います。このクラスを 直接使ってはいけません。

Class: <message-condition>
Condition Type: &message

[SRFI-35] このクラスはメッセージ付のコンディションを表現し、スロットを一つ もっています。

Instance Variable of <message-condition>: message

メッセージ

Class: <error>
Condition Type: &error

[SRFI-35] エラーを表します。<serious-condition> および <message-condition> を継承しています。したがって、 message スロットを持っています。

注意事項: SRFI-35 の&errorコンディションは &serious のみを継承し、&message は継承していません。したがって、 このエラーコンディションにメッセージを付与するためには、 合成コンディションを使わなければなりません。Gauche は 主として過去のバージョンとの互換性を確保するために、 ここで多重継承を用いています。 可搬性のあるコードを書くには、以下のようにメッセージコンディション 付きのエラーコンディションを使うべきです。

(condition
  (&message (message "Error message"))
  (&error))
Function: message-condition? obj
Function: serious-condition? obj
Function: error? obj

[SRFI-35] これらはSRFI-35互換の述語で、それぞれobj&message&serious、あるいは&error型のコンディションであるか どうかを検査します。

Class: <system-error>

<error> のサブクラス。 システムコールがエラーを返したとき、この型の例外が投げられます。 message スロットには通常エラーの(strerror(3)のような)説明が 含まれています。それ以外に、このクラスにはもうひとつ次のような インスタンススロットがあります。

Instance Variable of <system-error>: errno

システムのエラー番号の整数値を持ちます。

エラー番号はシステムによって異なる可能性があります。Gaucheは典型的な Unixのエラー番号に対して定数を定義している(例: EACCES, EBADF)ので、 それを使うと良いでしょう。定義されている定数に関しては システムへの問い合わせsys-strerrorの説明を参照してください。

このクラスには対応する SRFI のコンディションタイプがありませんが、 OSの生のエラーコードを取得するのに重要です。いくつかのケースで このタイプのコンディションは他の、たとえば <io-read-error> のようなコンディションと合成されます。

Class: <unhandled-signal-error>

<error>のサブクラス。多くのシグナルのデフォルトハンドラは このコンディションを投げます。詳しくはシグナルの処理を 参照してください。

Instance Variable of <unhandled-signal-error>: signal

受け取ったシグナル番号を示す整数値。典型的なシグナル番号については 定数が定義されています。シグナルとシグナルセットを参照のこと。

Class: <read-error>
Condition Type: &read-error

[SRFI-36] <error> のサブクラス。リーダがS式を読み込み中に、字句エラー または構文エラーを検出したとき、この型のコンディションが投げられます。

Instance Variable of <read-error>: port

リーダがS式を読みこんでいたポート。 (註: SRFI-36 の &read-error はこのスロットを定義していません。 ポータブルなプログラムを書く場合はこのスロットを使わないで下さい)。

Instance Variable of <read-error>: line

リーダがこのエラーを発生させたときの入力行カウント(1がベース)。 リーダが行カウントを保持しないポートから読み込むときには、-1 となる。

Instance Variable of <read-error>: column
Instance Variable of <read-error>: position
Instance Variable of <read-error>: span

これらのスロットは、SRFI-36 の &read-error で定義されています。 今のところ、これらのスロットは常に #f を保持するようになっています。

Class: <io-error>
Condition Type: &io-error

[SRFI-36] I/O エラーのベースとなるクラス。<error> を継承しています。

Class: <port-error>
Condition Type: &io-port-error

[SRFI-36] ポート関連の I/O エラー。<io-error> を継承しています。

Instance Variable of <port-error>: port

エラーを起したポートを保持。

Class: <io-read-error>
Condition Type: &io-read-error

[SRFI-36] ポートから読み込み中の I/O エラー。<port-error> を継承しています。

Class: <io-write-error>
Condition Type: &io-write-error

[SRFI-36] ポートへの書き出し中の I/O エラー。<port-error> を継承しています。

Class: <io-closed-error>
Condition Type: &io-closed-error

[SRFI-36] クローズされたポートで読み込み/書き出しをしようしたときの I/O エラー。 <port-error> を継承しています。

Class: <io-unit-error>

対象となるポートでサポートされていない単位での読み/書き要求 (たとえば、キャラクタ専用ポートでのバイナリ I/O 要求)の際の I/O エラー。 <port-error> を継承しています。

Class: <continuation-violation>
Condition Type: &continuation

[SRFI-226] 継続に関する制約が破られた場合に投げられるエラーです。 典型的なのは、指定された継続プロンプトタグがコンテクスト中に見つからない場合です。 詳しくは継続プロンプトを参照してください。

Function: make-continuation-violation prompt-tag

[SRFI-226] <continuation-violation>コンディションを作ります。 prompt-tagは原因となった継続プロンプトタグです。

Function: continuation-violation? obj

[SRFI-226] obj<continuation-violation>コンディションなら #tを、そうでなければ#fを返します。

Function: continuation-violation-prompt-tag condition

[SRFI-226] <continuation-violation>コンディションであるconditionの、 prompt-tagスロットの値を取り出します。

コンディションAPI

Macro: define-condition-type name supertype predicate field-spec …

[SRFI-35+] 新しいコンディションタイプを定義します。Gaucheでは、コンディションタイプは クラスであり、そのメタクラスは <condition-meta> です。

name が新しいタイプの名前になり、この名前の変数が作成された コンディションタイプに束縛されます。supertype はこのコンディション タイプのスーパータイプ(直接のスーパークラス)の名前です。コンディション タイプは <condition> を継承するか、その子孫を継承しなければ なりません。(この形式では、多重継承を指定することはできません。 一般的にいって、コンディションタイプの階層の中では多重継承は避ける べきです。そのかわりに、合成コンディションを使えます。合成コンディションは 多重継承を使いません。)

変数 predicate はこのコンディションタイプ用の述語手続きに 束縛されます。

field-spec(field-name accessor-name) の形式で このコンディションは、フィールド名は field-name で決まります。 変数 accessor-name はそのフィールドにアクセスする手続きに 束縛されます。Gauche では、それぞれのフィールドは生成された クラスのスロットとなります。

GaucheはSRFI-35を拡張して、predicateaccessor-nameを 定義する必要が無い場合はその位置に#fを指定できるようにしています。 accessor-nameが必要無い場合はそれを省略することもできます。

define-condition-typeがクラス定義に展開される際に、 各スロットは:init-keywordスロットオプションにスロット名と 同名のキーワードを取るように定義されます。

Function: condition-type? obj

[SRFI-35] objがコンディションタイプである場合で、その場合にかぎり、 #t を返します。Gauche では (is-a? obj <condition-meta>) と同じです。

Function: make-condition-type name parent field-names

[SRFI-35] 新しいコンディションタイプを生成する手続き版です。

Function: make-condition type field-name value …

[SRFI-35] コンディションタイプ type のコンディションを生成し、 field-name および value のペアで指定されたように フィールドを初期化します。

Function: condition? obj

[SRFI-35] obj がコンディションである場合で、その場合にかぎり、 #t を返します。Gauche では (is-a? obj <condition>) と 同じです。

Function: condition-has-type? obj type

[SRFI-35] obj がコンディションタイプ type に属している場合で、その 場合にかぎり、#t を返します。合成コンディションがあるので、 これは、is-a? と同じではありません

Function: condition-ref condition field-name

[SRFI-35] condition のフィールド field-name の値を検索します。 condition が合成コンディションであれば、元のコンディションの フィールドにアクセスできます。もし、複数の元のコンディションが、 field-name を持つ場合には、最初に make-compound-condition に渡されたものが優先されます。

コンディションのフィールドにアクセスするには、slot-ref および ref の両方あるいはどちらかを使えます。合成コンディションでは、 slot-missing メソッドが定義されますので、slot-ref は あたかも、合成コンディションが元になったコンディションの全てのスロットを もつかのように振舞います。しかしながら、condition-ref を 使う方が可搬性が増します。

Function: condition-message condition :optional fallback

[SRFI-35+] condition<message-condition>を持っていれば そのmessageスロットを、そうでなければfallbackを返す手続きです。 fallbackが省略された場合は#fが指定されたとみなします。

投げられたコンディションを一度捕まえてメッセージを記録したりユーザに見せたりする 一般的なコードはよくあります。Schemeでは任意のオブジェクトをraiseできるため、 捕まえたコンディションがメッセージスロットを持っているとは限りません。 従ってそういったコードはいちいちコンディションの型を検査して分岐することになります。 そのパターンは充分によくあるので、この手続きが追加されました。

Function: make-compound-condition condition0 condition1 …

[SRFI-35] condition0 condition1 … のすべてを持つ合成コンディション を返します。返されたコンディションのフィールドは、与えられたコンディション のすべてのフィールドの和集合になります。同じ名前のフィールドを持つ コンディションがある場合には最初に与えられものが優先されます。 返されたコンディションは元になったコンディションのすべてのタイプの コンディションタイプをもつことになります。 (これは多重継承ではありません。上の <compound-condition> を参照)

Function: extract-condition condition condition-type

[SRFI-35] condition はコンディションで、condition-typeタイプで なければなりません。この手続きは condition-type のコンディション を返し、condition からとりだされた値のフィールドを持ちます。

Macro: condition type-field-binding …

[SRFI-35] コンディションを生成するのに便利なマクロ。 合成されたコンディションも生成できます。 Type-field-binding は、 (condition-type (field-name value-expr) …) という形式になります。

(condition
  (type0 (field00 value00) ...)
  (type1 (field10 value10) ...)
  ...)
 ≡
(make-compound-condition
  (make-condition type0 'field00 value00 ...)
  (make-condition type1 'field10 value10 ...)
  ...)


For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT