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
#tBefore 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/