Ben Greenman
Ben Greenman

Reputation: 2002

racket: macro expand inside match pattern

Is there any way to detect if a macro is expanding inside a pattern match?

Here's an example macro that I'd like to write, but it fails inside a match-define:

#lang racket/base
(require racket/match (for-syntax racket/base syntax/parse))

(struct point (x y))

(define-syntax (friendly-point stx)
  (syntax-parse stx
   [(_ arg* ...)
    #'(begin (printf "Now making a point\n") (point arg* ...))]
   [_ #'(begin (printf "Hello point\n") point)]))

(define p (friendly-point 1 2))
 ;; Prints "Now making a point"

(match-define (friendly-point x y) p)
;; ERROR

Upvotes: 4

Views: 365

Answers (2)

Alexis King
Alexis King

Reputation: 43902

Yes. Instead of using an ordinary syntax transformer created with define-syntax, use define-match-expander to create a macro that can cooperate with match.

(require (for-syntax syntax/parse))

(define-match-expander positive
  (syntax-parser
    [(_ n)
     #'(? positive? n)]))

(match 3
  [(positive n) (~a n " is positive")])
; => "3 is positive"

The define-match-expander form is flexible: it can be used to create macros that may only be used inside of match, but it can also be used to create macros that expand differently depending on how they are used by providing two transformer functions, one for each context. This allows you to have “context-sensitive” identifiers which work as both functions and as match expanders.

(require (for-syntax syntax/parse)
         (prefix-in base: racket/base))

(define-match-expander syntax
  (syntax-parser
    [(_ x)
     #'(? syntax? (app syntax->datum x))])
  (make-rename-transformer #'base:syntax))

(match (syntax (1 2 3))
  [(syntax n) (~a n " is a syntax list")])

If you need even more flexibility, you can forego define-match-expander entirely and define a custom struct with the prop:match-expander structure type property. This can be combined with prop:procedure to achieve the two-argument functionality described above, but it can also hold state, and it can be paired with other structure type properties such as prop:rename-transformer to allow the same identifier to function in many, many different contexts.

Upvotes: 6

Leif Andersen
Leif Andersen

Reputation: 22342

What you are looking for is define-match-expander. It allows you to make a macro that is expanded inside of a pattern matching context. (Also, because it takes in two thunks, you can have a variant that is also used when not in a matching context. Next, I should point out that

Next, you can have a printing side effect inside of the template of a define-match-expander, but you can have in the macro itself. (Note though that the side effect will not occur if your module has already been expanded. This is explain in more detail in this paper.

So, using match expanders, including a second function for use outside of match, you get the following code:

#lang racket/base

(require racket/match (for-syntax racket/base syntax/parse))

(struct point (x y))

(define-match-expander friendly-point
  (lambda (stx)
    (syntax-parse stx
      [(_ arg* ...)
       (printf "Now matching a point\n")
       #'(point arg* ...)]
      [_ #'point]))
  (lambda (stx)
    (syntax-parse stx
      [(_ args* ...)
       #'(begin (printf "Now making a point\n") (point args* ...))])))

(define p (friendly-point 1 2))
 ;; Prints "Now making a point"

(match-define (friendly-point x y) p)
;; Works fine now

Upvotes: 5

Related Questions