lisp - 方案中的 RAII?

标签 lisp scheme raii

Scheme 中是否有实现资源获取即初始化的方案?

我知道 RAII 在 GC-ed 语言中效果不佳(因为我们不知道对象何时被销毁)。然而,Scheme 有一些不错的东西,比如延续、动态风和闭包——有没有办法使用它们的某种组合来实现 RAII?

如果不是,策划者如何设计他们的代码以不使用 RAII?

[我遇到的一个常见示例如下:

我有一个 3D 网格,我有一个附加到它的顶点缓冲区对象, 当不再使用 Mesh 时,我希望释放 VBO。]

谢谢!

最佳答案

如果这只是一次性的,您总是可以只编写一个环绕 dynamic-wind 的宏,在 thunk 之前和之后进行设置和拆卸(我假设allocate-vertex-buffer-objectfree-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)))))))

如果这是你经常使用的模式,对于不同类型的对象,你可能会编写一个宏来生成这种宏;并且您可能希望一次分配一系列绑定(bind),因此您可能希望在开始时有一个绑定(bind)列表,而不是只有一个。

这是一个现成的、更通用的版本;我不太确定这个名字,但它展示了基本思想(编辑以修复原始版本中的无限循环):

(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")))

这应该打印:

entering foo
entering bar
inside
foo: 1
bar: 2
exiting bar
exiting foo
* Let's try that again!
entering foo
entering bar
exiting bar
exiting foo
* All done

call/cc 只是call-with-current-continuation的缩写;如果您的方案没有较短的形式,请使用较长的形式。

更新:正如您在评论中阐明的那样,您正在寻找一种方法来管理可以从特定动态上下文中返回的资源。在这种情况下,您将不得不使用终结器;终结器是一个函数,一旦 GC 证明无法从其他任何地方访问它,它就会与您的对象一起调用。终结器不是标准的,但大多数成熟的 Scheme 系统都有它们,有时名称不同。例如,在 PLT 方案中,参见 Wills and Executors .

您应该记住,在 Scheme 中,可以重新输入动态上下文;这与大多数其他语言不同,在其他语言中,您可以使用异常在任意点退出动态上下文,但不能重新进入。在我上面的例子中,我演示了一种简单的方法,即当您离开动态上下文时使用 dynamic-wind 释放资源,并在您再次进入时重新分配它们。这可能适用于某些资源,但对许多资源来说并不合适(例如,重新打开一个文件,当您重新进入动态上下文时,您现在将位于文件的开头),并且可能有大量开销。

Taylor Campbell(是的,有关系)有 an article in his blag (2009-03-28 条目)解决了这个问题,并根据您想要的确切语义提出了一些备选方案。例如,他提供了一个 unwind-protext 表单,在不再可能重新进入可访问资源的动态上下文之前不会调用清理过程。

因此,这涵盖了许多可用的不同选项。与 RAII 没有完全匹配,因为 Scheme 是一种非常不同的语言并且具有非常不同的约束。如果您有更具体的用例,或者您简要提到的用例的更多详细信息,我或许可以为您提供一些更具体的建议。

关于lisp - 方案中的 RAII?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2090724/

相关文章:

scheme - 如何在 Edwin 中加载文件

C++:对象的引用计数器

c++ - C++类成员的生命周期

lisp - 如何在 Racket 中将 sql-timestamp 转换为字符串?

scheme - 在解释器中确定Scheme函数的定义和参数?/函数如何存储在Scheme中?

list - 在 LISP 中引入点对的原因是什么?

scheme - 尝试检查列表中的所有元素是否唯一

python-2.7 - 将 Python 翻译成 Scheme/Racket

cuda - 防止 CUDA 中的内核调用后析构函数调用

Emacs LISP - DeMorgan'ify 列表