reddit / emacs
How do I make a macro that evaluates an argument only once?
flora_best_maid · 2022-01-27 07:58
Let's say I have the following macro
(defmacro dup (f x)
"Call F by applying X twice."
(if (symbolp f)
(list f x x)
(append f (list x x))))
It does work:
(dup concat "me") ; "meme"
(dup (concat "nice ") "me") ; "nice meme"
However, it has some undesireable behaviour:
(macroexpand '(dup some-function (long (composition x))))
; (some-function (long (composition x)) (long (composition x)))
How do I make a macro that evaluates X once? I understand I could do it with a function, but I like that I don't have to quote things in a macro.
Least_Isopod_4061 · 2022-01-27 08:14
Am I missing something or can't you bind it at the start?
(defmacro dup (f x)
(let ((y (eval x)))
(if (symbolp f)
(list f y y)
(append f (list y y)))))
flora_best_maid · 2022-01-27 08:50
Because it has different undesired behaviour:
(macroexpand '(dup append (list "1" "2" "3")))
; (append ("1" "2" "3") ("1" "2" "3"))
Least_Isopod_4061 · 2022-01-27 09:16
Oh I see, you don't want to actually evaluate it. I think this works with all your examples
(defmacro dup (f x)
`(let ((y ,x))
,(if (symbolp f)
`(,f y y)
(append f '(y y)))))
deaddyfreddy · 2022-01-27 11:09
> I understand I could do it with a function, but I like that I don't have to quote things in a macro.
"the first rule is: don't use a macro where a function will do."
flora_best_maid · 2022-01-27 12:46
That's indeed what I want. It seems obvious now. Thanks!
Gangsir · 2022-01-27 13:48
>I understand I could do it with a function, but I like that I don't have to quote things in a macro.
Use things for their intended purposes. That's like saying "I need to hammer nails, but I prefer using a drill because I hate hitting things".
Lisp is annoying sometimes with syntax. Gotta deal.
00-11 · 2022-01-27 17:33
+1. This is also the second rule. And the third.
arthurno1 · 2022-01-27 19:19
> How do I make a macro that evaluates X once?
You let-bind it to a variable. You eval x when binding it, and use the binding in rest of your macro.
> I understand I could do it with a function
How would function change the fact that you are evaling symbol every time you use it? It is a good practice to eval you macro args once, however, every time you use a variable, system vill have to eval it to get it's value. That is regardless of if it is a macro or function. Lisp works with symbols, it is not a statically compiled language like C.
arthurno1 · 2022-01-27 19:24
> Use things for their intended purposes.
What is the intended purpose?
> Lisp is annoying sometimes with syntax.
Don't we invent tools to deal with annoying things? Isn't it the purpose of programming languages to start with?
gepardcv · 2022-01-27 22:37
Read _On Lisp_: http://www.paulgraham.com/onlisp.htm
Elisp is shittier than Common Lisp (the language used in the book), but it's close enough and most (all?) basic techniques from the book should work with only a few changes. The topic of macro argument evaluation, and its pitfalls, is covered in extensive detail.
fragglestickcar0 · 2022-01-27 23:04
> Elisp is shittier than Common Lisp
As much as I embrace your contemptuous world view, I generally avoid
shitting on people unless I know I can write something better. In the
case of elisp, I'm certain I couldn't.
BobSanchez47 · 2022-01-27 23:40
There’s a section of the book *Let over Lambda* which deals with this. [Here is the link](https://letoverlambda.com/index.cl/guest/chap3.html#sec_5). Read it and play around with it; it explains things extremely well.
In most cases, if you only want something evaluated once, you should just use a function. For example, you could use
```
(defun dup (f x)
(apply f x x))
```
though this would unfortunately require you to use the `#’` everywhere.
If you insist on doing things with macros, the correct code would be as follows:
```
(defmacro dup (f x)
(let ((g (gensym)))
`(let ((,g ,x))
(,f ,g ,g))))
```
If you try `(macroexpand ‘(dup (f (print 1))))`, you should get something like
```
(let ((GENSYM:42 (print 1)))
(f GENSYM:42 GENSYM:42))
```
Here, `GENSYM:42` is a fresh variable name generated by the compiler which is guaranteed not to clash with any previously existing names.
BobSanchez47 · 2022-01-27 23:48
This doesn’t work at all. For example, consider the following code:
```
(defun square (x)
(dup * x))
```
You’ll get a compile-time error since `x` can’t be evaluated at compile time.
c256 · 2022-01-28 05:53
That seems like an awful lot of quoting to avoid quoting. :-)