In macro, is object a macro or a procedure?

Hi all,

In the following example, a syntax error is raised because of (apply op args) can't work when op is a macro like and.
How can I force the macro to go through the second branch when apply doesn't apply?

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

(define-syntax (thingy stx)
  (syntax-parse stx
    [(_ (op:expr arg:expr ...))
     #'(let ([vals (list arg ...)])
         (list (apply op vals)  ; can't apply `and` here
               (map cons (list 'arg ...) vals)))
     ]
    [(_ ex:expr)
     #'ex]))


(thingy (+ 3 4)) ; fine
(thingy (and #true #false)) ; fails with `and: bad syntax`

It’s possible to create something like my-and which is still a macro, but can be used as an identifier macro (and therefore would work with apply). I guess you want stuff like my-and to go through the first branch?

Here’s one possible implementation.

#lang racket

(require syntax/parse/define)

(define-syntax-parser my-and
  [(_ x ...) #'(and x ...)]
  [_:id #'(λ args (andmap values args))])

'my-and-works
(my-and #f #t)
(map my-and '(#t #t #f #f) '(#t #f #t #f))

(define-for-syntax (id-use? t)
  (define proc (syntax-local-value t (λ () #f)))
  (cond
    [proc
     (with-handlers ([exn:fail? (λ (_) #f)])
       (proc t)
       #t)]
    [else #t]))

(define-syntax (thingy stx)
  (syntax-parse stx
    [(_ (op:expr arg:expr ...))
     #:when (id-use? #'op)
     #'(let ([vals (list arg ...)])
         (list (apply op vals)
               (map cons (list 'arg ...) vals)))
     ]
    [(_ ex:expr)
     #'ex]))

'thingy-works
(thingy (+ 3 4)) ; fine
(thingy (and #true #false))
(thingy (my-and #true #false))


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

(define-syntax (thingy stx)
(syntax-parse stx
[(_ (op:expr arg:expr ...))
#:with (val ...) (generate-temporaries #'(arg ...))
#'(let ([val arg] ...)
(list (op val ...) ; can't apply `and` here
(map cons (list 'arg ...) (list val ...))))
]
[(_ ex:expr)
#'ex]))

(thingy (+ 3 4)) ; fine
(thingy (and #true #false)) ; fails with `and: bad syntax`

1 Like

Thanks Sorawee!

I'm not sure I want something like my-and actually, because some branches of the and may not be meant to be evaluated.

However, something like id-use? is what I'm looking for. Though I'm hoping to not have to use with-handlers if possible.

Thanks Matthias!

Unfortunately, I don't think I'm allowed to evaluate the arguments of the and beforehand, because it defies the evaluation order that and creates.

For example, (thingy (or (not (vector? v)) (vector-ref v 0))) will fail if v is not a vector, but shouldn't.

So I think I need to make the macro fall back to the second branch instead.

The evaluation order of my revision is the same as yours (if yours worked).

Absolutely, but mine doesn't work either! :smiley:

That's why I want to push cases where I can't apply to the second branch —
but I guess the "Can I use apply?" is a proxy for "is it safe to evaluate the inner expressions before the outer one?".

Your revision helped me realize this more clearly.

I still think we should be able to do better than syntax-local-value here.

In particular, when writing (= and 3), DrRacket complains with a syntax error and: bad syntax in: and, which means that it is possible to determine that and cannot be in non-application position at compile time, supposedly without evaluating it.

But I don't know how to 'catch' this in a macro.

So, what I think and is doing here, is determining that the arguments passed to it is not a syntax-list (it is bare?):

;; from qq-and-or.rkt
(define-syntaxes (and)
  (let-values ([(here) (quote-syntax here)])
    (lambda (x)
      (if (not (stx-list? x))
          (raise-syntax-error #f "bad syntax" x)
          (void))
      ...)
    ...))

I guess you might be able to use that to check whether something is a macro or not? But I am speaking in ignorance.


Edit: So iets?

#lang racket

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

(begin-for-syntax
  (define (check stx)
    (with-handlers ([exn:fail? (λ _ #false)])
      (values (syntax-local-eval stx)))))

(define-syntax (thingy stx)
  (syntax-parse stx
    [(_ (op:expr arg:expr ...))
     #:when (check #'op)
     #'(let ([vals (list arg ...)])
         (list (apply op vals) (map cons (list 'arg ...) vals)))]

    [(_ ex:expr)
     #'ex]))

(thingy (+ 3 4)) ; fine
(thingy (and #true #false)) ; good to go?
;=>
; '(7 ((3 . 3) (4 . 4)))
; #f

Edit: I should have paid more attention to @sorawee's answer, since he did basically the same thing. TIL.

1 Like

Thanks!

Yeah, that's pretty good, but I'm hoping to avoid the syntax-local-eval (which is kind of worse than syntax-local-value in sorawee's answer). It seems to me that a simpler check should be possible — but I'm not sure what.

Oh, actually it appears I was mistaken: syntax-local-value may be the right thing to do, just not in the way sorawee used it first.

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

(define-syntax (thingy stx)
  (syntax-parse stx
    [(_ (op:id arg:expr ...))
     #:when (not (syntax-local-value #'op (λ () #f))) ; is this a syntax object?
     #'(let ([vals (list arg ...)])
         (list (apply op vals)  ; can't apply `and` here
               (map cons (list 'arg ...) vals)))
     ]
    [(_ ex:expr)
     #''second-branch]))

(thingy true) ; 'second-branch

(thingy (and (= 1 1) (= 2 3))) ; 'second-branch

(thingy (= 2 3)) ; '(#f ((2 . 2) (3 . 3)))
1 Like

I think syntax-local-value will work, but it may be more conservative than you would like: for example, it would put anything with the keyword-procedure optimization in the has-a-transformer-binding case.

You might want to local-expand the expression and look for #%plain-app (or maybe you also want to recognize #%app specially, or maybe add #%expression somewhere, or …).

2 Likes