(rontolisp) docs
← 関数

rontolisp:wit-provide

(rontolisp:wit-provide "wasi:keyvalue/store@0.2.0" #'my-store)

rontolisp:wit-import で取り込んだ WIT インターフェースの実装を束縛します。インタプリタと JVM バックエンドには呼び出す先の WASM ホストが存在しないため、インポートされた関数は すべてそのインターフェースの プロバイダ 経由でディスパッチします。それを供給するのが この関数です。インターフェース id を返し、そのインターフェースに既に束縛されている プロバイダを置き換えます

rontolisp はどのインターフェースについてもプロバイダを一切同梱していません。 同梱しているのはプロバイダの仕組みであって、wasi:keyvalue が — あるいは他のどの インターフェースが — 何であるかを rontolisp は知りません。WIT インターフェースの実装は通常の Lisp コードであり、それを渡す手段がこの関数です。

プロバイダは、束縛された関数の Lisp メンバー名 (文字列 — "open""bucket-get"。生の WIT ラベルではなく、束縛が綴られる名前) に続けてその関数の引数を 受け取る、通常の Lisp 呼び出し可能オブジェクトです。以下は完全なプロバイダです — ハッシュテーブル 1 つのストアで、ストアに必要なのはこれだけです。

これをソースに置けば、wit-importwasi:keyvalue プログラムは *rows* を相手にします。呼び出し箇所 ((kv:bucket-get b "visits")) も .wit もそのままで、キーと値がどこにあるかを 知っているコードはプログラムの中に 1 行もありません。

引数

  • インターフェース id (文字列)。.wit の完全修飾 id ("wasi:keyvalue/store@0.2.0") です。rontolisp:wit-import:interface に どの綴りを渡された場合でも、この正規化された id でディスパッチします ("wasi:keyvalue/store" や裸の store も同じインターフェースを指します)。 そのため、このキー 1 つですべての綴りに対してプロバイダが束縛されます。
  • プロバイダ: (member &rest args) の形の任意の Lisp 呼び出し可能オブジェクト — #'name 関数、lambda、その他 funcall が受け付けるものなら何でも構いません。

プロバイダが尋ねられるメンバー

インターフェースが宣言する関数ごとに 1 つ、綴りは wit-import が束縛するとおりです: "open""bucket-get"、コンストラクタなら "bucket-new"。加えてリソースごとに "<resource>-drop" があり、その引数はハンドル 1 つだけです。この最後のメンバーは どの .wit も宣言していません。リソースの解放はインターフェースの関数ではなく コンポーネントモデルの canonical な組み込み機能だからです。そこで rontolisp がこれに <resource>-drop という名前を与え、他のメンバーと同じようにプロバイダへディスパッチします。

drop が何を意味するかを決めるのはプロバイダであり、プロバイダだけです。 コアが知っているのは「プログラムがそのハンドルを使い終えた」ことだけで、そのハンドルが 何を指していたかは知りません。ですから、行をハッシュテーブルに持つストアはハンドルを 忘れて行はそのまま残し (上の my-storenil を返すだけで、それで実装として完全 です)、JDBC 接続や開いたファイルを握っているプロバイダはそれを閉じ、ハンドルに何の コストもないプロバイダは解放するものが無いので同じく nil を返します。drop がやってはいけないのは、ハンドルが指していたものを破棄することです。ハンドルは ストアへの参照であってストアそのものではないので、後から (kv:open "counts") したときには、drop されたハンドル経由で書かれたキーがすべて見えなければなりません。

メンバーを書き落とすと、drop はプロバイダのフォールバック節に落ちます — 上の my-store なら rontolisp:wit-error の節です。解放するものが無いプロバイダも、 黙っているのではなく nil を返すようにしてください。

rontolisp はプロバイダを同梱しません

コアが知っているのはプロバイダの仕組みです。wasi:keyvalue が何であるかをコアは知らず、その実装も — 他のどのインターフェースの実装も — 同梱していません。これは意図的なものです: 新しいホストインターフェースに必要なのは .wit ファイルであるべきで、コアのコードであるべきではありません。プロバイダが まだ束縛されていない状態で束縛済みの関数を呼ぶと、何らかの既定値に到達するのではなく rontolisp:wit-error がシグナルされます。

$ rontolisp counter.lisp
No provider is bound for the WIT interface wasi:keyvalue/store@0.2.0 -- bind one with rontolisp:wit-provide

プロバイダはただの関数なので、偽物と本物は互換であり、プログラムはその違いを 見分けられません。 wit/keyvalue の例wasi:keyvalue/store に対して書かれたページビューカウンタで、その背後には 2 つのストアがあります: 可搬なインメモリ Lisp ストアと、JVM では java: interop を通じて本物の java.util.LinkedHashMap に支えられたストア (後から束縛されるので、こちらが前者を 置き換えます) です。ストアは変わりますが、(kv:bucket-set b "/index" "3") という呼び出し箇所は変わらず、カウンタの出力もどちらでも同一です。実際のデプロイでは、 同じ 1 行でその map を Redis や JDBC 接続に差し替えます。代わりに Preview 1 WASM へコンパイルすればホストがプロバイダになり、これもプログラムの変更なしで済みます。

wit-error コンディション

rontolisp:wit-error は WIT の result<T, E>error アームがシグナルする コンディションです。ok アームは関数の戻り値、error アームはコンディション — すべてのバックエンドでそうなります。プロバイダはマップされた E をペイロードとしてこれをシグナルし (上の例では wasi:keyvalueerror variant、 すなわちタグ付きリスト)、呼び出し側は (rontolisp:wit-error-payload e) でペイロードを読み戻します。

;;; The caller of an imported function, in the same program as the wit-import.
(handler-case (kv:bucket-delete *bucket* "visits")
  (rontolisp:wit-error (e)
    (print (rontolisp:wit-error-payload e))))   ; (:other "read-only store")

これは通常のコンディションクラスです。handler-caseignore-errorsunwind-protect がすべて使え、error のサブクラスなので素の (handler-case ... (error (e) ...)) でも捕捉できます。

バックエンド

BackendEffect
interpreterbinds the provider; imported functions dispatch to it
JVM (-o Prog.class)the same
Preview 1 WASM (-o prog.wasm)a top-level form is dropped — the WASM host is the provider
--componentthe same: dropped, the host (or a composed component) is the provider
--no-gcit rejects rontolisp:wit-import itself

エラーにせず捨てるからこそ、1 つのソースがすべてのバックエンドで動きます。 インタプリタでプログラムを支えていた rontolisp:wit-provide は、ホストが引き継いだ時点で単に無効化されるだけです。

制限事項

  • インターフェース id は文字列として照合されます。したがって "wasi:keyvalue/store" で束縛したプロバイダは "wasi:keyvalue/store@0.2.0" へディスパッチする呼び出しには使われません。 wit-import:interface と同じ綴りにしてください。
  • プロバイダはグローバルでスコープを持ちません。あるインターフェースについては最後の rontolisp:wit-provide が、プログラムの残り全体で有効になります。
  • これらのバックエンドでは境界で何もマーシャリングも型チェックもされません — プロバイダには Lisp の値がそのまま渡され、その戻り値もそのまま返されます。 WIT 型の表 が、プロバイダを書くときの契約です。
  • 手書きの rontolisp:wasm-import には束縛すべきプロバイダはありません。 rontolisp:wit-provide が相手にするのは rontolisp:wit-import が束縛したインターフェースです。