2010-01-19 19 views
7

¿Hay alguna forma de implementar la adquisición de recursos es la inicialización en el esquema?RAII en Scheme?

Sé que RAII no funciona bien en los idiomas de GC-ed (ya que no tenemos idea de cuándo se destruye el objeto). Sin embargo, Scheme tiene cosas buenas como continuaciones, viento dinámico y cierres: ¿hay alguna forma de usar alguna combinación de esto para implementar RAII?

En caso negativo, ¿cómo diseñan los programadores su código para no usar RAII?

[Un ejemplo común corro en es la siguiente:

tengo una malla 3D, tengo un objeto Vertex Buffer atached a ella, cuando la malla ya no se utiliza, quiero que el VBO liberado .]

Gracias!

+0

Hola, anon. Me pregunto si mi respuesta te ha satisfecho o si estás buscando algo más. –

+0

Creo que su respuesta es tan buena como se le da su esquema. En cierto nivel, tenemos que saber cuándo el modelo "muere" y abandona su vbo. Sin embargo, en RAII + GC, no necesitamos saber esto de antemano, podemos decir "Modelo, no sé cuándo vas a morir, pero sé que cuando lo hagas, abandonarás el VBO". ". No podemos hacer la última porque el esquema está en gc-ed; lo que esperaba en un principio ... era algún tipo de macro mack inteligente que intercalara automáticamente algún tipo de ref-counting, que proporcionaría ese tipo de RAII + Refcounting. – anon

+0

Para agregar más a esto, considere la siguiente situación: creamos un Modelo, no sabemos cuándo se borra, pero sabemos que se procesó mucho; entonces le damos una VBO; pasarlo mucho; ... y cuando nadie lo usa, libera la VBO. No hay un solo lugar en el código donde sé "ahora puedo liberar el modelo". – anon

Respuesta

14

Si esto es sólo una sola vez, siempre se puede simplemente escribir una macro que se envuelve alrededor de dynamic-wind, haciendo el montaje y desmontaje en el antes y después de procesadores (estoy suponiendo que allocate-vertex-buffer-object y free-vertex-buffer-object son el constructor y los destructores aquí):

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

Si esto es un patrón que se utiliza mucho, para diferentes tipos de objetos, es posible escribir una macro para generar este tipo de macro; y es probable que desee asignar una serie de estos a la vez, por lo que es posible que desee tener una lista de enlaces al principio, en lugar de solo una.

Aquí hay una versión más general; No estoy muy seguro sobre el nombre, pero demuestra la idea básica (editado para fijar bucle infinito en la versión original):

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

Y se usaría de la siguiente manera:

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

Aquí hay un ejemplo que demuestra que funciona, incluida la salida y el reingreso al alcance mediante continuaciones (este es un ejemplo bastante artificial, se disculpa si el flujo de control es un poco difícil de seguir):

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

Esto debe imprimir:

 
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 es simplemente una abreviatura de call-with-current-continuation; use la forma más larga si su esquema no tiene la más corta.

Actualización: Como ha aclarado en sus comentarios, está buscando una forma de gestionar los recursos que pueden devolverse desde un contexto dinámico particular. En este caso, vas a tener que usar un finalizador; un finalizador es una función que se invocará con su objeto una vez que el GC haya demostrado que no se puede alcanzar desde ningún otro lugar. Los finalizadores no son estándar, pero los sistemas Scheme más maduros los tienen, a veces con diferentes nombres. Por ejemplo, en PLT Scheme, ver Wills and Executors.

Debe tener en cuenta que en Scheme, se puede volver a ingresar un contexto dinámico; esto difiere de la mayoría de los otros lenguajes, en los que puede salir de un contexto dinámico en cualquier punto arbitrario utilizando excepciones, pero no puede volver a ingresar.En mi ejemplo anterior, demostré un enfoque ingenuo de usar dynamic-wind para desasignar recursos cuando salga del contexto dinámico y reasignarlos si ingresa de nuevo. Esto puede ser apropiado para algunos recursos, pero para muchos recursos no sería apropiado (por ejemplo, volver a abrir un archivo, ahora estará al principio del archivo cuando vuelva a ingresar al contexto dinámico), y puede tener significativa sobrecarga.

Taylor Campbell (sí, hay una relación) tiene an article in his blag (la entrada 2009-03-28) que aborda este problema y presenta algunas alternativas basadas en la semántica exacta que desee. Por ejemplo, proporciona un formulario unwind-protext que no llamará al procedimiento de limpieza hasta que ya no sea posible volver a ingresar al contexto dinámico en el que se puede acceder al recurso.

Por lo tanto, eso cubre una gran cantidad de opciones diferentes que están disponibles. No existe una coincidencia exacta con RAII, ya que Scheme es un lenguaje muy diferente y tiene restricciones muy diferentes. Si tiene un caso de uso más específico o más detalles sobre el caso de uso que mencionó brevemente, es posible que pueda proporcionarle un consejo más específico.