8

Resource Acquisation is Initialization in Scheme を実装する方法はありますか?

RAII が GC-ed 言語ではうまく機能しないことはわかっています (オブジェクトがいつ破棄されるかがわからないため)。しかし、Scheme には、継続、動的風、クロージャーなどの優れた機能があります。これを組み合わせて RAII を実装する方法はありますか?

そうでない場合、スキーム作成者は RAII を使用しないようにコードをどのように設計しますか?

[私が遭遇する一般的な例は次のとおりです。

3D メッシュがあり、頂点バッファ オブジェクトがアタッチされています。メッシュが使用されなくなったら、VBO を解放します。]

ありがとう!

4

1 に答える 1

15

これが 1 回限りの場合はdynamic-wind、前後のサンクでセットアップと分解を行うマクロをいつでも作成できます (ここではallocate-vertex-buffer-objectandfree-vertex-buffer-objectがコンストラクタとデストラクタであると想定しています)。

(define-syntax with-vertex-buffer-object
  (syntax-rules ()
    ((_ (name arg ...) body ...)
     (let ((name #f))
       (dynamic-wind
         (lambda () (set! name (allocate-vertex-buffer-object args ...)))
         (lambda () body ...)
         (lambda () (free-vertex-buffer-object name) (set! name #f)))))))

これが、さまざまなタイプのオブジェクトに対して頻繁に使用するパターンである場合、この種のマクロを生成するマクロを作成できます。一度にこれらの一連のバインディングを割り当てたい可能性が高いため、最初にバインディングのリストを 1 つだけではなく配置することをお勧めします。

これはすぐに使える、より一般的なバージョンです。名前についてはよくわかりませんが、基本的なアイデアを示しています(元のバージョンの無限ループを修正するために編集されています):

(define-syntax with-managed-objects
  (syntax-rules ()
    ((_ ((name constructor destructor)) body ...)
     (let ((name #f))
       (dynamic-wind
         (lambda () (set! name constructor))
         (lambda () body ...)
         (lambda () destructor (set! name #f)))))
    ((_ ((name constructor destructor) rest ...)
      body ...)
     (with-managed-objects ((name constructor destructor))
       (with-managed-objects (rest ...)
         body ...)))
    ((_ () body ...)
     (begin body ...))))

これを次のように使用します。

(with-managed-objects ((vbo (allocate-vertex-buffer-object 1 2 3)
                            (free-vertext-buffer-object vbo))
                       (frob (create-frobnozzle 'foo 'bar)
                             (destroy-frobnozzle frob)))
  ;; do stuff ...
  )

これは、継続を使用してスコープを出て再び入ることを含む、それが機能することを示す例です (これはかなり不自然な例です。制御フローが少しわかりにくい場合はお詫びします)。

(let ((inner-continuation #f))
  (if (with-managed-objects ((foo (begin (display "entering foo\n") 1) 
                                  (display "exiting foo\n")) 
                             (bar (begin (display "entering bar\n") (+ foo 1)) 
                                  (display "exiting bar\n")))
        (display "inside\n")
        (display "foo: ") (display foo) (newline)
        (display "bar: ") (display bar) (newline)
        (call/cc (lambda (inside) (set! inner-continuation inside) #t)))
    (begin (display "* Let's try that again!\n") 
           (inner-continuation #f))
    (display "* All done\n")))

これは次のように表示されます。

フーに入る
バーに入る
中身
フー: 1
バー: 2
バーを出る
フーを終了する
*もう一度やってみましょう!
フーに入る
バーに入る
バーを出る
フーを終了する
* すべて完了

call/ccは単純にcall-with-current-continuation;の省略形です。スキームに短い形式がない場合は、長い形式を使用してください。

更新:コメントで明確にしたように、特定の動的コンテキストから返すことができるリソースを管理する方法を探しています。この場合、ファイナライザーを使用する必要があります。ファイナライザーは、GC が他のどこからも到達できないことが証明されたら、オブジェクトと共に呼び出される関数です。ファイナライザは標準ではありませんが、ほとんどの成熟した Scheme システムにはファイナライザがあり、名前が異なる場合もあります。たとえば、PLT スキームでは、Wills and Executorsを参照してください。

Scheme では、動的コンテキストに再入力できることに注意してください。これは、例外を使用して任意の時点で動的コンテキストを終了できる場合があるが、再入力できない他のほとんどの言語とは異なります。dynamic-wind上記の例では、動的コンテキストを離れるときに を使用してリソースの割り当てを解除し、再び入るときにそれらを再割り当てする単純なアプローチを示しました。これは一部のリソースには適切かもしれませんが、多くのリソースには適切ではありません (たとえば、ファイルを再度開いて、動的コンテキストに再度入るとファイルの先頭にいることになります)。かなりのオーバーヘッド。

Taylor Campbell (はい、関係があります) は彼のブログ(2009 年 3 月 28日のエントリ) にこの問題に対処する記事があり、必要な正確なセマンティクスに基づいていくつかの代替案を提示しています。たとえばunwind-protext、リソースにアクセスできる動的コンテキストに再び入ることができなくなるまで、クリーンアップ手順を呼び出さないフォームを提供します。

したがって、それは利用可能なさまざまなオプションをカバーしています。Scheme は非常に異なる言語であり、非常に異なる制約があるため、RAII と完全に一致するものはありません。より具体的なユースケース、または簡単に言及されたユースケースの詳細がある場合は、より具体的なアドバイスを提供できる場合があります.

于 2010-01-19T02:58:24.353 に答える