2010-01-19 2 views
7

È comunque necessario implementare l'acquisizione delle risorse è l'inizializzazione in schema?RAII in Scheme?

So che RAII non funziona bene nei linguaggi GC (poiché non abbiamo idea che l'oggetto sia stato distrutto). Comunque, Scheme ha cose carine come continuazioni, vento dinamico e chiusure. C'è un modo per usare una combinazione di queste per implementare RAII?

In caso negativo, in che modo gli sviluppatori progettano il codice per non utilizzare RAII?

[Un esempio comune mi imbatto in è il seguente:

ho una mesh 3D, ho un oggetto Vertex Buffer atached ad esso, quando la mesh non è più utilizzato, voglio il VBO liberato .]

Grazie!

+0

Ciao, anon. Mi chiedo se la mia risposta ti ha soddisfatto, o se stai cercando qualcos'altro. –

+0

Penso che la tua risposta sia buona come viene data dal suo schema. A un certo livello, dobbiamo sapere quando il modello "muore" e rinuncia al suo vbo. Tuttavia, in RAII + GC, non abbiamo bisogno di sapere questo in anticipo, possiamo dire "Modello, non so quando morirai, ma so che quando lo fai, rinuncerai al VBO ". Non possiamo fare abbastanza tardi perché lo schema è gc-ed; quello che speravo in origine ... era un qualche tipo di mack macro intelligente che intercalava automaticamente un certo tipo di ref-count in, che fornirebbe quel tipo di RAII + Refcounting. – anon

+0

Per aggiungere ulteriormente a questo, si consideri la seguente situazione: creiamo un modello, non sappiamo quando viene eliminato, ma sappiamo che è reso molto; quindi gli diamo un VBO; passalo molto; ... e quando nessuno lo usa, libera il VBO. Non c'è un singolo posto nel codice dove so "Ora posso liberare il modello". – anon

risposta

14

Se questo è solo un one-off, si può sempre e solo scrivere una macro che si avvolge attorno dynamic-wind, facendo il setup e teardown nella prima e dopo thunk (sto supponendo che allocate-vertex-buffer-object e free-vertex-buffer-object sono il vostro costruttore e distruttori qui):

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

Se questo è un modello che si utilizza un sacco, per i diversi tipi di oggetti, è possibile scrivere una macro per generare questo tipo di macro; e probabilmente vorrete allocare una serie di questi alla volta, quindi potreste voler avere una lista di associazioni all'inizio, invece di una singola.

Ecco una versione più generale; Io non sono davvero sicuro circa il nome, ma dimostra l'idea di base (a cura di fissare ciclo infinito in versione originale):

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

e si dovrebbe utilizzare questo come segue:

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

Ecco un esempio che illustra funzionare, compresi uscire e rientrare nel campo di applicazione utilizzando continuazioni (questo è un esempio alquanto, scuse se il flusso di controllo è un po 'difficile da seguire):

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

Questo dovrebbe stampare:

 
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 è semplicemente l'abbreviazione di call-with-current-continuation; usa il modulo più lungo se il tuo Schema non ha quello più corto.

Aggiornamento: Come chiarito nei commenti, si sta cercando un modo per gestire le risorse che possono essere restituite da un particolare contesto dinamico. In questo caso, dovrai utilizzare un finalizzatore; un finalizzatore è una funzione che verrà chiamata con te oggetto una volta che il GC ha dimostrato che non può essere raggiunto da nessun'altra parte. I finalizzatori non sono standard, ma i sistemi Scheme più maturi li hanno, a volte con nomi diversi. Ad esempio, in Schema PLT, vedi Wills and Executors.

Si dovrebbe tenere presente che in Schema, un contesto dinamico può essere reinserito; questo differisce dalla maggior parte degli altri linguaggi, in cui è possibile uscire da un contesto dinamico in qualsiasi punto arbitrario utilizzando le eccezioni, ma non è possibile reinserire.Nel mio esempio precedente, ho dimostrato un approccio ingenuo all'uso di dynamic-wind per deallocare le risorse quando si esce dal contesto dinamico e riassegnarle se si inserisce nuovamente. Questo potrebbe essere appropriato per alcune risorse, ma per molte risorse non sarebbe appropriato (ad esempio, riaprendo un file, ora sarai all'inizio del file quando reinserisci il contesto dinamico), e potresti avere overhead significativo.

Taylor Campbell (sì, c'è una relazione) ha an article in his blag (la voce del 2009-03-28) che risolve questo problema e presenta alcune alternative basate sulla semantica esatta che si desidera. Ad esempio, fornisce un modulo unwind-protext che non chiamerà la procedura di pulitura finché non sarà più possibile immettere nuovamente il contesto dinamico in cui la risorsa è accessibile.

Quindi, questo copre molte diverse opzioni disponibili. Non esiste una corrispondenza esatta con RAII, poiché Schema è una lingua molto diversa e ha vincoli molto diversi. Se hai un caso d'uso più specifico, o più dettagli sul caso d'uso che hai citato brevemente, potrei essere in grado di fornirti qualche consiglio più specifico.