-
-
Notifications
You must be signed in to change notification settings - 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 error
This 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
out
This 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. Mytry
macro doesn't need a supportingwith-catch
function because it can use a combination of thesyntax/parse
technique Variants with Uniform Meanings and the existing Racketwith-handlers
form.
b. Gerbil's supporting functionwith-unwind-protect
uses 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. Mycatch
andfinally
literals use a compile-time helper functionnot-allowed-as-an-expression
to get a better error message, while Gerbil's version uses a simple emptydefrules
to get a genericBad syntax
error message.
b. Mytry
macro uses syntax-parse's...+
to express one-or-more repetition, while Gerbil's version uses manualnull?
checks andstx-null?
checks, seen in theirgenerate-thunk
helper function and theirfinally
case.
c. Mytry
macro uses a syntax-parsesyntax-class
for recognizingbody
expressions that aren'tcatch
orfinally
clauses, using the~not
pattern to exclude them. While Gerbil's version uses a namedlet
loop with 2 extra nestedsyntax-case
expressions (beyond the normalsyntax-case
at the top) to separate body expressions fromcatch
andfinally
clauses.
d. Mytry
macro uses a syntax-parsesyntax-class
for handlingcatch
clauses as Variants with Uniform Meanings allowing repetition with ellipses, while Gerbil's version uses the helper functiongenerate-catch
, with its ownwith-syntax
, namedlet
loop,match
, andsyntax-case
expressions, in combination with yet another namedlet
loop and another nestedsyntax-case
expression (beyond the ones mentioned above) in the main body of the macro to separate thecatch
clauses from thefinally
clause.
e. Mytry
macro uses a syntax-parsesyntax-class
for handling thefinally
clause, while Gerbil's version uses a helper functiongenerate-fini
and 2 differentfinally
cases in differentsyntax-case
expressions in the main body of the macro. One of these is to separatebody
expressions fromfinally
in the case when there are nocatch
es in between, and the other is to separatecatch
clauses fromfinally
. Their firstfinally
case requires a manualstx-null?
check to make sure nothing comes afterfinally
, while their secondfinally
case encodes that into asyntax-case
pattern for a 1-element list. Syntax-classes allow mytry
macro to express this simply by putting thefinally-clause
pattern 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/