1

My goal is to evaluate an expression at compile time, like some simple things as (+ 1 1). But, I would like an integer "2" to be compiled in, instead of a full "1+1" operation to be done at runtime, without using a named macro. This makes my source code clearer showing how I got the value "2" while not wasting CPU time repeating the same "1+1" operation. Here is a simple example illustrating the basic idea but not for real cases, say, my ideal goal defun function looks like this:

(defun test ()
   (+ 1 2 3))

I would like literal "2" to be evaluated at compile time so I use eval-when-compile:

(defun test ()
   (+ 1 
      (eval-when-compile (+ 1 1)) 
      3))

However, it turns out becoming:

(defun test ()
   (+ 1 '2 3))

It's of course okay for this simple case but this extra quote is causing problems for more complicated cases.

Defining a named macro works:

(defmacro 1+1 () `,(+ 1 1))
(defun test ()
   (+ 1 (1+1) 3))

It will produce my ideal result with no quote prepending the integer "2":

(defun test ()
    (+ 1 2 3))

Is there a simple way like the above eval-when-compile to achieve this, in a defun? Something like an unnamed macro to get rid of the named helper 1+1, or should I call this "lambda macro"?

Drew
  • 29,895
  • 7
  • 74
  • 104
Luke Lee
  • 261
  • 3
  • 11
  • 1
    Could you change the example to one which is actually demonstrating a problem? – phils Oct 11 '18 at 10:59
  • 1
    Another simplified example like generating the documentation string for an elisp function: `(defun test() (eval-when-compile (concat "doc" "string")) (+ 1 2))` with `(documentation 'test)` we'll get "nil". The generated doc-string for function _test_ actaully becomes `'"docstring"` -- a quote prefixed the `"docstring"`. – Luke Lee Oct 11 '18 at 12:24
  • Well you can't do that anyway. If you want to specify a non-string docstring you need to use the symbol property. Code which, if it did what you wanted, would *only* work when compiled, is also not a good example. – phils Oct 11 '18 at 12:37
  • On the contrary, I **want** a string doc-string to be generated. My example _still_ shows how `eval-when-compile` would fail. However, a macro that precomputes `(concat "doc" "string")` could correctly generate a string doc-string `"docstring"` instead of the incorrect quoted "docstring" `'"docstring"`. – Luke Lee Oct 11 '18 at 13:36
  • Yes, but for *uncompiled* elisp the docstring parameter of `defun` *must* be a string -- it cannot be some other form which simply evaluates to a string, because then it will not be seen as a docstring, and will instead become part of the BODY of the function, evaluated whenever it is called (which in principle might be anything between harmless and disastrous). Again, if `eval-when-compile` worked the way you want it to work, your usage here would *only* work for byte-compiled code, which I think is a terrible goal that you shouldn't be aiming for. – phils Oct 11 '18 at 19:23
  • Let me show you the rest of the example `(defmacro testdoc () \`,(concat "doc" "string")) (defun test () (testdoc) (+ 1 2))`. No matter byte-compile this or not, the `test` function has a correct "docstring" as its documentation. I am asking *if* there is a way to achieve this without a **named** macro. – Luke Lee Oct 12 '18 at 04:19

3 Answers3

1

I don't know of a way to create anonymous macros, but macrolet facilitates a temporary scope for named macros, which you could use like so:

(defun foo ()
  (macrolet ((docstring () (concat "foo" "bar")))
    (docstring))
  'foo)

Note that if this is genuinely just for docstrings, you could instead do this:

(defun foo () 'foo)
(put 'foo 'function-documentation (concat "foo" "bar"))
phils
  • 71,335
  • 11
  • 153
  • 198
1

Inspired by the answer of @phils, I finally able to define anonymous macros and run them. I define a helper macro named immediate to help solving this; it basically defines an anonymous macro function temporarily using macrolet, then runs it:

(defmacro immediate (body)
  "Define an anonymous macro and run it immediately"
  (let ((f (make-symbol "immed")))
    `(macrolet ((,f () ,body))
       (,f))))

With immediate, I can rewrite the sample code in my original question this way:

(defun test ()
  (immediate (concat "Docstring" " of " "test" ))
  (+ 1
     (immediate (+ 1 1))
     3))

The test function will be defined exactly as what I expected it to be:

(defun test ()
  "Docstring of test"
  (+ 1 2 3))

(The name immediate is inspired by the immediate words from the Forth language.)

Luke Lee
  • 261
  • 3
  • 11
0

You are thinking about constant folding. It is no requirement to do so, but the fastest compilers does it. Here is an example from SBCL:

* (defun test () (+ 1 2 3))

TEST
* (disassemble #'test)

; disassembly for TEST
; Size: 22 bytes. Origin: #x100189010C
; 0C:       498B4D60         MOV RCX, [R13+96]                ; no-arg-parsing entry point
                                                              ; thread.binding-stack-pointer
; 10:       48894DF8         MOV [RBP-8], RCX
; 14:       BA0C000000       MOV EDX, 12
; 19:       488BE5           MOV RSP, RBP
; 1C:       F8               CLC
; 1D:       5D               POP RBP
; 1E:       C3               RET
; 1F:       0F0B0F           BREAK 15                         ; Invalid argument count trap
NIL

You see no additions done, but you see the constant 12 which is the pointer address for the fixnum 6

Here in CLISP:

[2]> (disassemble #'test)

Disassembly of function test
(CONST 0) = 6
0 required arguments
0 optional arguments
No rest parameter
No keyword parameters
2 byte-code instructions:
0     (const 0)                           ; 6
1     (skip&ret 1)
nil

Thus from this if you use a modern Common Lisp implementation which isn't someones attempt at comedy you should not need to think about these matters.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • Thanks, but my example is just a simplified case showing the idea, not just for constant folding. The result could be a string, a list or something else. I modified my question a bit accordingly. – Luke Lee Oct 11 '18 at 09:13
  • @Sylwester, FYI elisp is no different in this scenario. You can update your answer with: `(disassemble (byte-compile '(defun test () (+ 1 (eval-when-compile (+ 1 1)) 3))))` – phils Oct 11 '18 at 11:05