Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

try—a try/catch/finally for sarna #9

Open
benknoble opened this issue Jul 27, 2021 · 3 comments
Open

try—a try/catch/finally for sarna #9

benknoble opened this issue Jul 27, 2021 · 3 comments

Comments

@benknoble
Copy link

benknoble commented Jul 27, 2021

See also https://github.com/benknoble/try-make-sarna-happy and the docs on the package server (when ready).

Macro

#lang racket/base

(provide try)

(require (for-syntax racket/base)
         syntax/parse/define)

(begin-for-syntax
  (define-syntax-class catch-clause
    #:attributes ((pred 1) (name 1) (body 2))
    #:datum-literals (catch)
    (pattern (catch ([(pred:expr name:id) body:expr ...+] ...))))

  (define-syntax-class finally-clause
    #:attributes ((body 1))
    #:datum-literals (finally)
    (pattern (finally body:expr ...+))))

;; Calls value-thunk, then post-thunk, with post-thunk guaranteed to be run
;; even if execution exits value-thunk through an exception or continuation
;;
;; value-thunk is prevented from re-entry and continutation shenanigans by a
;; continuation-barrier
;;
;; thanks to Alex Knauth & SamPh on Discord
(define (call-with-try-finally value-thunk post-thunk)
  (call-with-continuation-barrier
    (λ () (dynamic-wind void value-thunk post-thunk))))

(define-syntax-parser try
  [(_ {~and body:expr {~not _:catch-clause} {~not _:finally-clause}} ...+
      {~optional c:catch-clause}
      {~optional f:finally-clause})
   #'(call-with-try-finally
       (λ ()
         (with-handlers ((~? (~@ [c.pred (λ (c.name) c.body ...)] ...)))
           body ...))
       (~? (λ () f.body ...) void))])

(module+ test
  (require racket
           rackunit)

  (check-equal?
    (try 1)
    1)

  (check-equal?
    (try (/ 1 0)
         (catch ([(exn:fail? e) (exn-message e)])))
    "/: division by zero")

  (check-equal?
    (with-output-to-string
      (thunk
        (check-equal?
          (try 1
               (finally (displayln "cleaning up")))
          1)))
    "cleaning up\n")

  (check-equal?
    (with-output-to-string
      (thunk
        (check-equal?
          (try (/ 1 0)
               (catch ([(exn:fail? _) 0]))
               (finally (displayln "cleaning up")))
          0)))
    "cleaning up\n"))

with-handlers and dynamic-wind can be used to implement catch and finally, respectively, but in the traditional Lisp-like format they read "backwards"—handlers are shown long before the actual "main code." For dynamic-wind this may not be terrible (see go's defer func for a similar finally clause that comes ahead of the code). In general, though this is hard to read.

try flips the script: it presents the body code first, a series of exception-handlers in an optional catch, and an optional finally block.

Example

Here are some examples from the scribble documentation:

> (try
    (/ 10 0)
    (catch ([(exn? e) (exn-message e)])))
"/: division by zero"

> (let ([resource (get-handle)])
    (try
      (use-might-break resource)
      (catch ([(exn? e) (displayln (exn-message e))]))
      (finally
        (close resource)))
    (is-closed? resource))
use-might-break: something went wrong
#t

Before and After

  • Code Cleaning : Please share the code that you used to write before creating your macro. Briefly explain how the code works.

It tidies up a common pattern and makes it read in a forward direction. In this it is similar to the threading library but for exceptions.

There are many before/after examples in the docs.

; before
> (let ([resource (get-handle)])
    (dynamic-wind
      void
      (λ () (with-handlers ([exn? (λ (e) (displayln (exn-message e)))])
              (use-might-break resource)))
      (λ () (close resource)))
    (is-closed? resource))
use-might-break: something went wrong
#t

; after
> (let ([resource (get-handle)])
    (try
      (use-might-break resource)
      (catch ([(exn? e) (displayln (exn-message e))]))
      (finally
        (close resource)))
    (is-closed? resource))
use-might-break: something went wrong
#t

Licence

I affirm that I am submitting this code under the same MIT License that the Racket language uses. https://github.com/racket/racket/blob/master/racket/src/LICENSE-MIT.txt
and that the associated text is licensed under the Creative Commons Attribution 4.0 International License http://creativecommons.org/licenses/by/4.0/

@benknoble
Copy link
Author

The syntax has undergone a slight adjustment (less parens).

I've also added catch/match for using match patterns.

@benknoble
Copy link
Author

docs and impl have been improved, but the concepts remain fairly similar

@spdegabrielle
Copy link
Contributor

Thank you for your contribution!

If you haven’t already please take the time to fill in the form https://forms.gle/Z5CN2xzK13dfkBnF7

Bw
Stephen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants