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. :-)