-
-
Couldn't load subscription status.
- Fork 3
Description
Macro
A try/catch/finally macro inspired by Gerbil Scheme's try macro in :std/sugar.
Source code: https://github.com/AlexKnauth/try-catch-finally/blob/main/try-catch-finally-lib/main.rkt
Documentation: https://docs.racket-lang.org/try-catch-finally/index.html
#lang racket/base
(provide try catch finally => _)
(require syntax/parse/define (for-syntax racket/base))
(begin-for-syntax
(define (not-allowed-as-an-expression stx)
(raise-syntax-error #f "not allowed as an expression" stx))
(define-syntax-class finally-clause #:literals [finally]
[pattern (finally e:expr ...+) #:with post-thunk #'(λ () e ...)])
(define-syntax-class catch-clause #:literals [catch => _]
[pattern (catch pred:expr => handler:expr)]
[pattern (catch (pred:expr x:id) b:expr ...+)
#:with handler #'(λ (x) b ...)]
[pattern (catch (x:id) b:expr ...+)
#:with pred #'void
#:with handler #'(λ (x) b ...)]
[pattern (catch _ b:expr ...+)
#:with pred #'void
#:with handler #'(λ (x) b ...)])
(define-syntax-class body #:literals [finally catch]
[pattern {~and :expr {~not {~or (finally . _) (catch . _)}}}]))
(define-syntax catch not-allowed-as-an-expression)
(define-syntax finally not-allowed-as-an-expression)
(define-syntax-parser try
[(_ b:body ...+ f:finally-clause)
#'(call-with-try-finally (λ () b ...) f.post-thunk)]
[(_ b:body ...+ c:catch-clause ...)
#'(with-handlers ([c.pred c.handler] ...) b ...)]
[(_ b:body ...+ c:catch-clause ... f:finally-clause)
#'(call-with-try-finally
(λ () (with-handlers ([c.pred c.handler] ...) b ...))
f.post-thunk)])
;; call-with-try-finally : [-> X] [-> Any] -> X
;; Calls value-thunk, then post-thunk, with post-thunk guaranteed to be run
;; even if execution exits value-thunk through an exception or continuation
(define (call-with-try-finally value-thunk post-thunk)
(call-with-continuation-barrier
(λ () (dynamic-wind void value-thunk post-thunk))))Please explain the purpose of the macro.
Example
An example with try/catch:
> (try
(raise-syntax-error #f "a syntax error")
(catch (exn:fail:syntax? e)
(displayln "got a syntax error")))
got a syntax errorThis runs a computation that raises a syntax exception, and catches that with a handler written after the main body, rather than written before as in with-handlers.
An example with try/finally:
> (let/cc up
(try
(displayln "at before")
(up (void))
(displayln "at after")
(finally (displayln "out"))))
at before
outThis runs a computation that exits due to a continuation jump, and still runs the "post-body" set up in the finally.
Before and After
- Code Cleaning : Please share the code that you used to write before creating your macro. Briefly explain how the code works.
Similarly to #9 and #12, this also can replace uses of with-handlers with try/catch, and replace many uses of dynamic-wind with try/finally.
However, part of the reason I wanted to provide the same functionality as Gerbil Scheme was to compare my syntax-parse implementation to their procedural / syntax-case implementation, and therefore:
- Macro Engineering : Please share the old macro that you revised. Briefly explain the changes.
Gerbil Scheme's try implementation is here:
https://github.com/vyzo/gerbil/blob/fa9537be0848e54d2c68165503b9cc48babb9334/src/std/sugar.ss#L32-L97
With supporting function definitions here:
https://github.com/vyzo/gerbil/blob/17fbcb95a8302c0de3f88380be1a3eb6fe891b95/src/gerbil/runtime/gx-gambc0.scm#L1625-L1636
- Changes in supporting runtime function definitions.
a. Mytrymacro doesn't need a supportingwith-catchfunction because it can use a combination of thesyntax/parsetechnique Variants with Uniform Meanings and the existing Racketwith-handlersform.
b. Gerbil's supporting functionwith-unwind-protectuses mutable state to make a "one use" closure dynamically check that it's only called once. While my version,call-with-try-finally, uses a continuation-barrier as suggested by SamPh on Discord, to accomplish the same without mutable state.
Their supporting runtime functions take 11 sloc while mine take only 3 sloc, mostly due to Racket features such as with-handlers and continuation-barriers that Gerbil's version doesn't have.
- Changes in the compile-time syntax transformation definitions.
a. Mycatchandfinallyliterals use a compile-time helper functionnot-allowed-as-an-expressionto get a better error message, while Gerbil's version uses a simple emptydefrulesto get a genericBad syntaxerror message.
b. Mytrymacro uses syntax-parse's...+to express one-or-more repetition, while Gerbil's version uses manualnull?checks andstx-null?checks, seen in theirgenerate-thunkhelper function and theirfinallycase.
c. Mytrymacro uses a syntax-parsesyntax-classfor recognizingbodyexpressions that aren'tcatchorfinallyclauses, using the~notpattern to exclude them. While Gerbil's version uses a namedletloop with 2 extra nestedsyntax-caseexpressions (beyond the normalsyntax-caseat the top) to separate body expressions fromcatchandfinallyclauses.
d. Mytrymacro uses a syntax-parsesyntax-classfor handlingcatchclauses as Variants with Uniform Meanings allowing repetition with ellipses, while Gerbil's version uses the helper functiongenerate-catch, with its ownwith-syntax, namedletloop,match, andsyntax-caseexpressions, in combination with yet another namedletloop and another nestedsyntax-caseexpression (beyond the ones mentioned above) in the main body of the macro to separate thecatchclauses from thefinallyclause.
e. Mytrymacro uses a syntax-parsesyntax-classfor handling thefinallyclause, while Gerbil's version uses a helper functiongenerate-finiand 2 differentfinallycases in differentsyntax-caseexpressions in the main body of the macro. One of these is to separatebodyexpressions fromfinallyin the case when there are nocatches in between, and the other is to separatecatchclauses fromfinally. Their firstfinallycase requires a manualstx-null?check to make sure nothing comes afterfinally, while their secondfinallycase encodes that into asyntax-casepattern for a 1-element list. Syntax-classes allow mytrymacro to express this simply by putting thefinally-clausepattern at the end of the main syntax-pattern, after the previous patterns and their ellipses.
Their syntax definitions take 62 sloc while mine take 28 sloc, with syntax/parse features such as syntax-classes and better ellipsis support making it much shorter, as well as with-handlers allowing the output to be simpler and allowing the ellipsis repetitions of syntax-classes to do more of the work. I also subjectively find mine easier to read, though I am biased on that of course.
Licence
I confirm that I am submitting this code under the same MIT License that the Racket language uses. https://github.com/AlexKnauth/try-catch-finally/blob/main/LICENSE.txt
and that the associated text is licensed under the Creative Commons Attribution 4.0 International License
http://creativecommons.org/licenses/by/4.0/