RAII в схеме? - PullRequest
       42

RAII в схеме?

7 голосов
/ 19 января 2010

Есть ли способ реализовать получение ресурсов при инициализации в схеме?

Я знаю, что RAII не работает хорошо в языках GC-ed (так как мы не знаем, когда объект уничтожен). Тем не менее, в Scheme есть такие приятные вещи, как продолжения, динамический ветер и замыкания - есть ли способ использовать некоторую комбинацию этого для реализации RAII?

Если нет, то как программисты создают свой код, чтобы не использовать RAII?

[Типичный пример, с которым я сталкиваюсь:

У меня есть трехмерная сетка, к ней привязан объект буфера вершин, когда Mesh больше не используется, я хочу освободить VBO.]

Спасибо!

1 Ответ

14 голосов
/ 19 января 2010

Если это всего лишь один раз, вы всегда можете просто написать макрос, который будет заключен в dynamic-wind, выполняя настройку и демонтаж до и после thunks (я предполагаю, что allocate-vertex-buffer-object и free-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")))

Это должно вывести:

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 см. Завещания и исполнители .

Следует помнить, что в Схеме динамический контекст может быть введен повторно; это отличается от большинства других языков, в которых вы можете выйти из динамического контекста в любой произвольной точке, используя исключения, но вы не можете войти снова. В моем примере выше я продемонстрировал наивный подход использования dynamic-wind для освобождения ресурсов при выходе из динамического контекста и перераспределения их при повторном входе. Это может быть подходящим для некоторых ресурсов, но для многих ресурсов это не будет подходящим (например, при повторном открытии файла вы теперь будете в начале файла при повторном входе в динамический контекст), и может иметь значительные накладные расходы.

У Тейлора Кэмпбелла (да, есть связь) есть статья в его блоге (запись 2009-03-28), посвященная этой проблеме, и несколько вариантов, основанных на точной семантике хочу. Например, он предоставляет форму unwind-protext, которая не будет вызывать процедуру очистки, пока больше не будет возможности повторно войти в динамический контекст, в котором доступен ресурс.

Итак, это охватывает множество различных доступных опций. Точного совпадения с RAII нет, так как Scheme - это совсем другой язык и имеет очень разные ограничения. Если у вас есть более конкретный вариант использования или более подробная информация о сценарии использования, который вы кратко упомянули, возможно, я смогу дать вам более конкретный совет.

...