Is it possible to identify a procedure at expansion time?

When writing macros, is it possible to identify procedures at expansion time? I think the answer is no, but I thought I'd check.

In my try-catch module, the 'catch' clause expands out into a with-handlers clause. In order to reduce boilerplate I made it wrap the action expression into a procedure for you with an unhygienically-added e as the value of the raised value so that you can write:

(try [(raise 'boom)]
     [catch (symbol? (displayln e))]

In retrospect, this wasn't a great design choice since it's easy to forget and write (λ (e) (displayln e)), which obviously doesn't do what you want. I'd prefer not to break backwards compatibility so it would be great if there was a way to have the macro say "if they passed one expression and it's a procedure, call it. Otherwise, wrap it and call that." Thinking about it, I think that this is not possible, and there's an issue with the various workarounds I've come up with, such as evaluating the expression twice.

One solution that would work is to create a catch* variant that doesn't do the wrapping, but I disprefer that option.

Anyway, lesson learned: Don't get cute, just KISS.

Any thoughts on where to go with this?

To be honest, I don't see anything wrong with this interface at all! (well, I actually kinda dislike that excessive parentheses around (symbol? (displayln e)) but that's my personal taste). And personally I see no need to complicate stuff by supporting (lambda (e) ...).

But if you really want to support it, you can do the following. Note that procedure? is done at run-time.

#lang racket

(require syntax/parse/define)

(define-syntax-parse-rule (try [e ...+] [(~literal catch) [p? body]])
  #:with e-id (datum->syntax this-syntax 'e)
  (with-handlers ([p? (λ (e-id)
                        ;; evaluate body only once
                        (define x body)
                        (cond
                          [(procedure? x) (x e-id)]
                          [else x]))])
    e ...))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(try [(raise 'boom)]
     [catch [symbol? (printf "example 1: ~a\n" e)]])


(let ()
  (define (handler e-v)
    (printf "example 2: ~a\n" e-v))

  (try [(raise 'boom2)]
       [catch [symbol? handler]]))


(try [(raise 'boom3)]
     [catch [symbol? (λ (e) (printf "example 3: ~a\n" e))]])


(let ()
  (define (e e-v)
    (printf "example 4: ~a\n" e-v))

  (try [(raise 'boom4)]
       [catch [symbol? e]]))


(let ()
  (define (e e-v)
    (printf "example 5: ~a\n" e-v))

  (let ([e2 e])
    (try [(raise 'boom5)]
         [catch [symbol? e2]])))

(try [(raise 6)]
     [catch [number?
             (let ([e (add1 e)])
               e)]])
1 Like

Cool, I'm glad you like it!

I looked at doing something like what you're suggesting, but it has an issue when the handler uses multiple value return.

(try [(raise 'boom)]
     [catch [symbol? (values 'x 'y)]])

; result arity mismatch;                                                                       
;  expected number of values not received                                                      
;   expected: 1                                                                                
;   received: 2                                                                                

I don't see a solution absent adding syntax to notify the macro of how many values are being returned, which feels clumsy.

(Also, I note that try-catch does a lot more than shown in my original example; I was trying to keep things simple. Full docs: try-catch )

#lang require
(try [shared "this code is visible throughout the other clauses"]
     [pre "this always runs before body even if you jump in and out via continuations. it is out of scope elsewhere"]
     ["body"]
     [post "this always runs after body even if you jump in and out via continuations.  it is out of scope elsewhere"]
     [catch (procedure? "procedure handler")
            (symbol? "symbol handler")
            (any/c  "other handler")]
     [cleanup "this code runs iff the body completes successfully"])

I looked at doing something like what you're suggesting, but it has an issue when the handler uses multiple value return.

Here’s a revision that works with multiple values:

#lang racket

(require syntax/parse/define)

(define-syntax-parse-rule (try [e ...+] [(~datum catch) [p? body]])
  #:with e-id (datum->syntax this-syntax 'e)
  (with-handlers ([p? (λ (e-id)
                        (call-with-values
                         (lambda () body)
                         (case-lambda
                           [(x)
                            (cond
                              [(procedure? x) (x e-id)]
                              [else x])]
                           [xs (apply values xs)])))])
    e ...))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(try [(raise 'boom)]
     [catch [symbol? (values 'x 'y)]])