Repository navigation
Description
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
#tLicence
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/
Activity
benknoble commentedon Jul 27, 2021
The syntax has undergone a slight adjustment (less parens).
I've also added
catch/matchfor using match patterns.benknoble commentedon Jul 28, 2021
docs and impl have been improved, but the concepts remain fairly similar
spdegabrielle commentedon Jul 30, 2021
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
add try/catch/finally macros