Reputation: 11922
Suppose I have a list of arguments args
and a macro/syntax f
that takes a variable number of arguments. How do I apply f
to args
? Apparently apply
doesn't work here.
For example, suppose I have a list of values bs
and I want to know if they're all true, so I try (apply and bs)
but I get the error "and: bad syntax"
. The workaround I came up with is (eval `(and . ,bs))
but I'm wondering if there is some standard way to achieve this sort of thing.
A bunch of possible duplicates have been suggested, but most of them are just about the and
example. This suggested question seems to be the same as mine, but the answer there is not very helpful: it basically says "don't do this!".
So maybe the point is that in practice this "apply
+ macro" question only comes up for macros like and
and or
, and there is no useful general question? I certainly ran into this issue with and
, and don't have much Scheme experience, certainly no other examples of this phenomenon.
Upvotes: 1
Views: 461
Reputation:
This is an XY problem. Macros are part of the syntax of the language: they're not functions and you can't apply them to arguments. Conceptually, macros are like functions which map source code to other source code, and which are called at compile time, not run time: trying to use them at run time is a category error. (In Common Lisp macros are, quite literally, functions which map source code to other source code: in Scheme I'm not quite so clear about that).
So if you have a list and you want to know if all its elements are true, you call a function on the list to do that.
It's easy to write such a function:
(define (all-true? things)
(cond [(null? things)
#t]
[(first things)
(all-true? (rest things))]
[else #f]))
However Racket provides a more general function: andmap
: (andmap identity things)
will either return false if one of things
is not true, or it will return the value of the last thing in the list (or #t
if the list is empty). (andmap (lambda (x) (and (integer? x) (even? x))) ...)
will tell you if all the elements in a list are even integers, for instance.
There is also every
which comes from SRFI 1 and which you can use in Racket after (require srfi/1)
. It is mostly (exactly?) the same as andmap
.
One thing people sometimes try to do (and which you seem to be tempted to do) is to use eval
. It may not be immediately clear how awful the eval
'solution; is. It is awful because
Let's see how bad it is. Start with this:
> (let ([args '(#t #t #f)])
(eval `(and ,@args)))
#f
OK, that looks good, right? Well, what if I have a list which is (a a a b)
: none of the elements in that are false, so, let's try that:
> (let ([args '(a a b)])
(eval `(and ,@args)))
; a: undefined;
; cannot reference an identifier before its definition
Oh, well, can I fix that?
> (let ([args '(a a b)]
[a 1] [b 2])
(eval `(and ,@args)))
; a: undefined;
; cannot reference an identifier before its definition
No, I can't. To make that work, I'd need this:
> (define a 1)
> (define b 2)
> (let ([args '(a a b)])
(eval `(and ,@args)))
2
or this
> (let ([args '('a 'a 'b)])
(eval `(and ,@args)))
'b
Yes, those are quotes inside the quoted list.
So that's horrible: the only two cases it's going to work for is where everything is either defined at the top level as eval
has no access to the lexical scope where it is called, or a literal within the object which may already be a literal as it is here, because everything is now getting evaluated twice.
So that's just horrible. To make things worse, eval
evaluates Scheme source code. So forget about compiling, performance, or any of that good stuff: it's all gone (maybe if you have a JIT compiler, maybe it might not be so awful).
Oh, yes, and eval
evaluates Scheme source code, and it evaluates all of it.
So I have this convenient list:
(define args
'((begin (delete-all-my-files)
(publish-all-my-passwords-on-the-internet)
(give-all-my-money-to-tfb)
#t)
(launch-all-the-nuclear-missiles)))
It's just a list of lists of symbols and #t
, right? So
> (eval `(and @,args)
; Error: you are not authorized to launch all the missiles
; (but all your files are gone, your passwords are now public,
; and tfb thanks you for your kind donation of all your money)
Oops.
It would be nice to be able to say, if I have a list that I want to check some property of that doing so would not send all my money to some person on the internet. Indeed, it would be nice to know that checking the property of the list would simply halt, at all. But if I use eval
I can't know that: checking that every element of the list is (evaluates to) true may simply never terminate, or may launch nuclear weapons, and I can generally never know in advance whether it will terminate, or whether it will launch nuclear weapons. That's an ... undesirable property.
At the very least I would need to do something like this to heavily restrict what can appear in the list:
(define (safely-and-list l)
(for ([e (in-list l)])
(unless
(or (number? e)
(boolean? e))
(error 'safely-and-list "bad list")))
(eval `(and ,@l)))
But ... wait: I've just checked every element of the list: why didn't I just, you know, check they were all true then?
This is why eval
is never the right solution for this problem. The thing eval
is the right solution for is, well, evaluating Scheme. If you want to write some program that reads user input and evaluates, it, well, eval
is good for that:
(define (repl (exit 'exit))
(display "feed me> ")
(flush-output)
(let ([r (read)])
(unless (eqv? r exit)
(writeln (eval r))
(repl exit))))
But if you think you want to apply a macro to some arguments then you almost certainly have an XY problem: you want to do something else, and you likely don't understand macros.
Upvotes: 1