Skip to content

try—a try/catch/finally for sarna #9

Description

@benknoble

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
#t

Before 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/

Activity

  1. benknoble commented on Jul 27, 2021

    @benknoble
    Author

    The syntax has undergone a slight adjustment (less parens).

    I've also added catch/match for using match patterns.

  2. benknoble commented on Jul 28, 2021

    @benknoble
    Author

    docs and impl have been improved, but the concepts remain fairly similar

  3. spdegabrielle commented on Jul 30, 2021

    @spdegabrielle
    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

  4. added a commit that references this issue on Oct 8, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions