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 - another try/catch/finally inspired by Gerbil Scheme #10

Open
AlexKnauth opened this issue Jul 28, 2021 · 1 comment
Open

try - another try/catch/finally inspired by Gerbil Scheme #10

AlexKnauth opened this issue Jul 28, 2021 · 1 comment

Comments

@AlexKnauth
Copy link

AlexKnauth commented Jul 28, 2021

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

  1. Changes in supporting runtime function definitions.
    a. My try macro doesn't need a supporting with-catch function because it can use a combination of the syntax/parse technique Variants with Uniform Meanings and the existing Racket with-handlers form.
    b. Gerbil's supporting function with-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.

  1. Changes in the compile-time syntax transformation definitions.
    a. My catch and finally literals use a compile-time helper function not-allowed-as-an-expression to get a better error message, while Gerbil's version uses a simple empty defrules to get a generic Bad syntax error message.
    b. My try macro uses syntax-parse's ...+ to express one-or-more repetition, while Gerbil's version uses manual null? checks and stx-null? checks, seen in their generate-thunk helper function and their finally case.
    c. My try macro uses a syntax-parse syntax-class for recognizing body expressions that aren't catch or finally clauses, using the ~not pattern to exclude them. While Gerbil's version uses a named let loop with 2 extra nested syntax-case expressions (beyond the normal syntax-case at the top) to separate body expressions from catch and finally clauses.
    d. My try macro uses a syntax-parse syntax-class for handling catch clauses as Variants with Uniform Meanings allowing repetition with ellipses, while Gerbil's version uses the helper function generate-catch, with its own with-syntax, named let loop, match, and syntax-case expressions, in combination with yet another named let loop and another nested syntax-case expression (beyond the ones mentioned above) in the main body of the macro to separate the catch clauses from the finally clause.
    e. My try macro uses a syntax-parse syntax-class for handling the finally clause, while Gerbil's version uses a helper function generate-fini and 2 different finally cases in different syntax-case expressions in the main body of the macro. One of these is to separate body expressions from finally in the case when there are no catches in between, and the other is to separate catch clauses from finally. Their first finally case requires a manual stx-null? check to make sure nothing comes after finally, while their second finally case encodes that into a syntax-case pattern for a 1-element list. Syntax-classes allow my try macro to express this simply by putting the finally-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/

@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