【问题标题】:RAII in Scheme?计划中的 RAII?
【发布时间】:2010-01-19 02:46:08
【问题描述】:

Scheme中是否有实现Resource Acquiisation is Initialization的方法?

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

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

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

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

谢谢!

【问题讨论】:

  • 嗨,阿农。我想知道我的回答是否让您满意,或者您是否正在寻找其他东西。
  • 我认为你的反应和它的计划一样好。在某种程度上,我们必须知道模型何时“死亡”并放弃它的 vbo。但是,在RAII+GC中,我不需要提前知道,我们可以说“模型,我不知道你什么时候会死;但我知道当你死了,你就会放弃VBO ”。我们不能完全这样做,因为方案是 gc-ed;我最初希望...是某种类型的聪明的宏 mack,它会自动交错某种类型的引用计数,它们会提供那种类型的 RAII + Refcounting。
  • 为了进一步补充,考虑以下情况:我们创建一个模型,我们不知道它什么时候被删除,但我们知道它被渲染了很多;所以我们给它一个VBO;传递它很多; ...当没有人使用它时,它会释放 VBO。代码中没有一个地方我知道“我现在可以释放模型了。”
  • 啊,好的。我不知道您指的是 RAII 的具体方面;该术语有点模糊,可用于安全地分配具有明确定义的生命周期的资源,这正是我所讨论的。如果您需要任意生命周期的对象,则需要使用某种终结器;该标准不涉及终结器,但大多数相对成熟的方案都有一些定义终结器的方法。我将扩展我的答案以解决您的问题。

标签: lisp scheme raii


【解决方案1】:

如果这只是一次性的,您总是可以只编写一个环绕 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)))))))

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

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

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

这应该打印出来:

进入 foo 进入酒吧 里面 富:1 酒吧:2 退出栏 退出 foo * 让我们再试一次! 进入 foo 进入酒吧 退出栏 退出 foo * 全部做完

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-07
    • 2012-09-06
    • 2019-04-13
    • 2012-06-19
    • 1970-01-01
    • 2017-02-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多