Sharing scope in setup/cleanup for dynamic-wind?

24 views
Skip to first unread message

David Storrs

unread,
May 18, 2021, 2:08:07 PM5/18/21
to Racket Users
dynamic-wind is nice because it guarantees that the pre- and postconditions for a chunk of code will be run regardless of continuation jumps, exceptions, etc.  The only issue I have is that the three thunks do not share scope, making it difficult to do setup/teardown workflows.  I feel like I should be able to make this work with nested dynamic-wind or with-handlers but haven't been able to get there without losing the guarantee of running.  Is there a way?

Example of what I'd like to do:

(my-dynamic-wind
  (thunk (define conn (connect-to-server)))
  (thunk (send-message conn "foo"))
  (thunk (finalize-connection conn)))

Sam Tobin-Hochstadt

unread,
May 18, 2021, 2:34:57 PM5/18/21
to David Storrs, Racket Users
It's not quite as convenient, but here's a version of your program
that should work:

(let ([conn #f])
(dynamic-wind
(lambda () (set! conn (connect-to-server))
(lambda () (send-message conn "foo"))
(lambda () (finalize-connection conn))))

Sam
> --
> You received this message because you are subscribed to the Google Groups "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/CAE8gKodTcogsfeiYKu5iyFbL4H%2Ba3dzuL7rLTfYERD%2BauFD9xQ%40mail.gmail.com.

David Storrs

unread,
May 18, 2021, 3:42:03 PM5/18/21
to Sam Tobin-Hochstadt, Racket Users
Thank you.  Is there a way to do it without the mutation?  I was hoping to use this for macro generation that simplifies combining with-handlers and dynamic-wind with setup/teardown code.

(try [pre
      (define db (connect-to-db))
      (define conn (connect-to-server))]
     [(send-message conn (query-value db "invalid SQL"))]
     [catch ([exn:fail:contract? (lambda (e) (displayln "contract"))]
             [exn:fail:network?  (lambda (e) (displayln "network"))]
             [(and/c exn:fail? (compose1 (curry regexp-match #rx"query") exn-message))
              (lambda (e) (displayln "database"))])]  ; this should run
     [finally
      (finalize-db-connection db)
      (finalize-server-connection conn)])

The goal is to guarantee that the connections are created/released every time we go in and out of the code (usually via an exception) and also to put the happy path first so that it's easier to see what the code should do before getting into the weeds of error handling.

(I probably should have put these details in the original message but I was trying to keep it simple so as not to make people burn brain cycles.)

Sam Tobin-Hochstadt

unread,
May 18, 2021, 3:51:59 PM5/18/21
to David Storrs, Racket Users
I think the key question is what you want to happen if you would need
to re-run the "pre" thunk, because you re-enter the code via a
continuation.

In many cases, you don't want to support that at all, and then it's
pretty easy (although you still need mutation):

(let* ([conn (connect-to-server)]
[started? #f])
(dynamic-wind
(lambda () (when started? (error 'fail)) (set! started? #t))
(lambda () (send-message conn "hi"))
(lambda () (finalize-connection conn))))

You could just not do the check in the pre thunk, in which case you'd
get worse error messages but probably nothing else wrong.

Sam
> To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/CAE8gKofbcigfJDFgU8AXVqomQYqmgGT_OEXwAx7m8BQyyoCHqQ%40mail.gmail.com.

Philip McGrath

unread,
May 18, 2021, 4:09:38 PM5/18/21
to Sam Tobin-Hochstadt, David Storrs, Racket Users
On Tue, May 18, 2021 at 3:52 PM Sam Tobin-Hochstadt <sa...@cs.indiana.edu> wrote:
I think the key question is what you want to happen if you would need
to re-run the "pre" thunk, because you re-enter the code via a
continuation.

In many cases, you don't want to support that at all …

Then you can use `call-with-continuation-barrier`, right?

(let* ([conn (connect-to-server)])
  (dynamic-wind
   void
   (λ ()
     (call-with-continuation-barrier
      (λ ()
        (send-message conn "hi"))))
   (λ ()
     (finalize-connection conn))))

David Storrs

unread,
May 19, 2021, 11:31:19 AM5/19/21
to Philip McGrath, Sam Tobin-Hochstadt, Racket Users
I think I don't understand cwcb well enough to get this, but the connect call is not in the pre thunk so it's not guaranteed to happen...right?

--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.

George Neuner

unread,
May 19, 2021, 10:40:31 PM5/19/21
to David Storrs, racket...@googlegroups.com
Coming late to this ...


On 5/19/2021 11:31 AM, David Storrs wrote:
On Tue, May 18, 2021 at 4:09 PM Philip McGrath <phi...@philipmcgrath.com> wrote:
On Tue, May 18, 2021 at 3:52 PM Sam Tobin-Hochstadt <sa...@cs.indiana.edu> wrote:
I think the key question is what you want to happen if you would need
to re-run the "pre" thunk, because you re-enter the code via a
continuation.

In many cases, you don't want to support that at all …

Then you can use `call-with-continuation-barrier`, right?

(let* ([conn (connect-to-server)])
  (dynamic-wind
   void
   (λ ()
     (call-with-continuation-barrier
      (λ ()
        (send-message conn "hi"))))
   (λ ()
     (finalize-connection conn))))

I think I don't understand cwcb well enough to get this, but the connect call is not in the pre thunk so it's not guaranteed to happen...right?

The connect will occur once before the dynamic-wind is entered.  However, because it is outside dynamic-wind rather than in the pre thunk - connect will /not/ be executed again if the value thunk is reentered.

call-with-continuation-barrier, in effect, puts a sandbox around the contained thunk which prevents continuations captured within the thunk from being invoked outside of it.  IOW, you can jump around /inside/ the thunk, and you can jump out of the thunk ... but if you jump out, you /can't/ jump back in.

As used in the example, the barrier around the value thunk turns dynamic-wind into the moral equivalent of Lisp's unwind-protect:  modulo exceptions, pre and value will be executed at most once, and post is guaranteed to be executed regardless of whether pre or value finish.

Hope this helps,
George

Reply all
Reply to author
Forward
0 new messages