0.9におけるAPIの変更


(For English version, see API Changes in 0.9).

Gaucheのリリース0.9では、いくつかのC APIに非互換な変更があります。 Cで書かれた拡張パッケージは影響を受けるかもしれません。

これらの変更はかなり以前からアナウンスされており、新しいAPIは #define GAUCHE_API_0_9と定義することで見えるようになっていました。 したがってアクティブにメンテナンスされている拡張パッケージは 既に新しいAPIを使っている可能性が高いです。その場合、何もしなくても 再コンパイルするだけで拡張パッケージは0.9で使えます。

しばらく更新されていない拡張モジュールは、0.9インストール後に コンパイルしようとするとエラーとなるかもしれまん。 最も簡単な回避策は、Cソースの冒頭に #define GAUCHE_API_PRE_0_9を入れることです。 (Cソースがstubファイルから生成される場合は、Makefileを編集して genstubのコマンドラインに-DGAUCHE_API_PRE_0_9フラグを 足す必要があります)。

言い換えれば、プリプロセッサフラグによって見えるAPIが 次のように変わったということです。

0.9以前

0.9およびそれ以降

GAUCHE_API_PRE_0_9を定義することで旧APIが見える機能は 1.0リリースまでには削除されるので、拡張パッケージのメンテナは なるべく新APIにスイッチするようにしてください。

以下のC関数が、非互換に変更されるか、非推奨となっています。

Eval系関数

以前のGaucheでは Scm_Eval, Scm_EvalCString, Scm_Applyは エラーを捕捉しませんでした。Scheme式の評価中に捕捉されない例外が 投げられた場合、これらの関数からは戻って来ず、 つぎの二つのケースのいずれかが起きていました。

  1. VMがアクティブである、すなわち、Scm_Eval等を呼び出しているCコード自身が VMから呼び出されている場合。この時はもともとのVMが例外を捕捉して、 設定されているハンドラがあれば呼び出します。
  2. VMがアクティブでない、例えばCのmain関数からScm_Evalを呼び出している ような場合。この時は例外がデフォルトハンドラによって処理されます。 すなわち、メッセージを出力してexitします。

最初のケースは大抵は好ましいものです。Schemeの関数も同じように 振る舞うので。ただし、Cコードが何らかのクリーンアップを必要とする 場合は、SCM_UNWIND_PROTECTマクロで後処理を書いてやる必要があります。

二番目のケースは良くありません。SCM_UNWIND_PROTECTでプログラムが 終了してしまうのを防ぐことはできますが、どんなエラーが起きたのか わかりませんし、全てのScm_Eval呼び出しをSCM_UNWIND_PROTECTで 囲むのも煩わしいものです。

また、旧APIには別の問題もあります。Scheme式は複数の値を 生成する可能性がありますが、呼び出し元は最初の値しか受け取れません。 他の値が生成されたかどうかを調べたり、その値を取り出すには VMに対してさらにAPIを呼び出す必要があります。

そこで0.9では、Eval系関数に対してふたつの関数セットが提供されます。 最初のセットは「フル装備」版で、これはエラーを捕捉します。 この判のAPIは追加の引数としてScmEvalPacket構造体への ポインタを取り、捕捉された例外はそこに格納されます。 また、多値の戻り値もそこに格納されます。

フル装備版が最初にCプログラムからSchemeプログラムを呼び出す安全な方法 となります。そこで、こちらの版にScm_Eval, Scm_EvalCString, Scm_Applyの名前を与えることにしました。

二番目のセットは「軽量」版で、旧APIのScm_Eval等と全く同様に 動作します。こちらの版は、VMからコールバックされたCコードの中で のみ使われることを想定しています。軽量版APIはエラーを捕捉しません。 生じたエラーはVMが捕まえてくれるからです。こちらのAPIは Scm_EvalRec, Scm_EvalCStringRec, Scm_ApplyRec と名付けられています。"Rec"は、Cコードから再帰的にVMを呼び出すからです。

したがって、旧コードを新APIに合わせる最も簡単な方法は、 Scm_Eval等の呼び出しを対応するScm_EvalRec等に変えることです。 ただし、それらの呼び出しがVMがアクティブでない箇所で行われているなら、 新APIのScm_Evalを使った方が良いでしょう。

Load系関数

同様に、Scm_LoadScm_LoadFromPortScm_Requireもまた ロード中のエラーを捕捉するように変更されました。新APIではこれらの関数は 新たな引数としてScmLoadPacket構造体へのポインタを取ります。 エラーが起きた場合、投げられた例外がScmLoadPacketの中に 格納されます。ScmLoadPacketはScm_LoadPacketInit関数で 初期化されなければなりません。 これらの関数の戻り値は一貫して、0が成功、-1がロード中のエラーを示します。

旧APIに近い動作を得るには、これらの関数の呼び出しを次のように 書き換えます。

フラグSCM_LOAD_PROPAGATE_ERRORは、ロード中のエラーを 「捕捉しない」ように指示するものです。このフラグが与えられた場合、 ロード中にエラーが起きるとこれらの関数からは戻らず、 アクティブなVMによって例外がハンドルされます。

これらのコードは旧APIとほぼ同様に動作しますが、 実際のコードではSCM_LOAD_PROPAGATE_ERRORを与えずに、 Scm_Load等の戻り値を検査する方が使い勝手が良いでしょう。

ハッシュテーブル関数

ハッシュテーブルのAPIはほぼ一新されました。新しいAPIの多くは 名前を変えてあり、名前のぶつからない旧APIも互換のために残してありますが 非推奨です。2つばかり、名前のぶつかっている関数については 旧コードを変更する必要があります。

主要な変更は、ハッシュテーブルの実装が2つのレイヤに分けられたことです。 基本となるレイヤ ScmHashCore はSchemeオブジェクトではなく単なる Cの構造体で、汎用のハッシュテーブルとして使えます (キーや値が Schemeオブジェクトである必要はありません)。 ScmHashCoreの上に、SchemeオブジェクトであるScmHashTableが 実装されます。ScmHashTableのキーと値はSchemeオブジェクトに制限されます。

もうひとつの変更は、ハッシュテーブルの実装をインタフェースから切り離す ことです。旧APIは、ハッシュテーブル内のキー-値のペアの実体への参照を 返していました。返されたペアを変更すればハッシュテーブルを変更することが できたのです。これは、「キーをルックアップし、値を検査して必要なら 更新する」というパターンにおいて、キーの検索を一度だけで済ませられるという 利点があります。ルックアップの操作がキー-値のペアを返し、呼び出し側は ハッシュテーブルを変更したければ再検索せずともそのペアをいじれば良いからです。 ただ、このAPIはハッシュテーブルの実装を制限してしまいます。キーと値が ハッシュテーブル内でも並んでいなければならないからです。 このことは、ハッシュテーブルの実装を入れ替えたい場合に 障害となります。

新しいAPIはこれらを念頭において設計されました。ScmHashTable上の 基本APIは次のとおりです。

コンストラクタScm_MakeHashTableSimpleによって、SchemeレベルのAPIと同様に eq?、eqv?、equal?、string=? タイプのハッシュテーブルを作ることができます。 将来はカスタマイズされたハッシュ関数と比較関数を取るより汎用的な コンストラクタが提供される予定です。 (高レベルAPIでカスタマイズされたハッシュテーブルがなかなか提供されないのは、 これらの関数の満たすべき制約についてScheme界と矛盾を起こさないように 仕様を決めなければならないためです。 低レベルAPIであるScmHashCoreについては、使う側が責任を持つということで カスタマイズされたハッシュ関数、比較関数を与えることができます)。

Scm_HashTableRefはSchemeのインタフェースと似ています。 キーを与えると、対応する値が返ります。与えたキーを持つエントリが 無ければfallbackに渡した値が返ります。 エントリが存在するかどうかをチェックするには、fallbackに SCM_UNBOUNDを渡してください。SCM_UNBOUNDはScmHashTableの 値として格納することは出来ないので、それが返ればエントリが存在していなかった ことがわかります。

ハッシュテーブルを変更するにはScm_HashTableSetを使います。 flagsにはSCM_DICT_NO_CREATEやSCM_DICT_NO_OVERWRITEの 論理和を必要に応じて与えます。

旧APIのScm_HashTablePutのふるまいはflags=0で、 Scm_HashTableAddのふるまいはflags=SCM_DICT_NO_OVERWRITEで実現できます。

他のAPI、Scm_HashTableDelete, Scm_HashTableKeys, Scm_HashTableValues等に変更はありません。

イテレータについては、ScmHashTableのレイヤではなく ScmHashCoreのレイヤで定義されているので少々面倒です。 いずれScmHashTableのレイヤで使える簡単なインタフェースを提供しますが、 今のところ古いコードは以下のように書き換える必要があります。

文字列ポート関数

Scm_GetOutputString, Scm_GetOutputStringUnsafe, Scm_GetRemainingInputStringflags引数を余分に取るようになりました。 ここにSCM_STRING_INCOMPLETEを渡して結果の文字列を 常に不完全文字列とするようなことができます。

旧APIのふるまいを得るには、flagsに0を渡してください。


Last modified : 2009/11/21 19:58:35 UTC