是否可以定义未命名的宏?(“lambda”宏?)

是否可以定义未命名的宏?(“lambda”宏?),lambda,emacs,macros,elisp-macro,Lambda,Emacs,Macros,Elisp Macro,我的目标是在编译时计算表达式,就像一些简单的事情一样(+1)。但是,我希望编译一个整数“2”,而不是在运行时执行完整的“1+1”操作,而不使用命名宏。这使我的源代码更清晰地显示了我是如何获得值“2”的,同时又不会浪费CPU时间重复相同的“1+1”操作。下面是一个简单的示例,说明了基本思想,但不适用于实际情况,例如,我的理想目标defun函数如下所示: (defun test () (+ 1 2 3)) 我希望在编译时对文字“2”进行评估,因此在编译时使用eval: 然而,事实证明: (d

我的目标是在编译时计算表达式,就像一些简单的事情一样(+1)。但是,我希望编译一个整数“2”,而不是在运行时执行完整的“1+1”操作,而不使用命名宏。这使我的源代码更清晰地显示了我是如何获得值“2”的,同时又不会浪费CPU时间重复相同的“1+1”操作。下面是一个简单的示例,说明了基本思想,但不适用于实际情况,例如,我的理想目标defun函数如下所示:

(defun test ()
   (+ 1 2 3))
我希望在编译时对文字“2”进行评估,因此在编译时使用eval:

然而,事实证明:

(defun test ()
   (+ 1 '2 3))
这个简单的例子当然没问题,但这个额外的引文会给更复杂的例子带来问题

定义命名宏有效:

(defmacro 1+1 () `,(+ 1 1))
(defun test ()
   (+ 1 (1+1) 3))
它将产生我的理想结果,而不在整数“2”前加引号:


在编译时,有没有一种简单的方法像上面的eval那样在defun中实现这一点?类似于一个未命名的宏来摆脱命名的helper 1+1,或者我应该称之为“lambda宏”?

您正在考虑常量折叠。不需要这样做,但最快的编译器可以做到这一点。以下是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
您看不到任何添加操作,但可以看到常量
12
,它是fixnum
6

在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

因此,如果您使用的是现代通用Lisp实现,而不是有人试图搞笑,那么您不需要考虑这些问题

我不知道如何创建匿名宏,但是
macrolet
为命名宏提供了一个临时作用域,您可以这样使用:

(defun foo()
(macrolet((docstring()(concat“foo”bar)))
(文档字符串))
‘福’
请注意,如果这真的只是针对docstring,您可以这样做:

(defun foo()'foo)
(放置“foo”函数文档(concat“foo”条)

受@phils答案的启发,我终于能够定义匿名宏并运行它们。我定义了一个名为immediate的助手宏来帮助解决这个问题;它基本上是使用macrolet临时定义一个匿名宏函数,然后运行它:

(defmacro immediate (body)
  "Define an anonymous macro and run it immediately"
  (let ((f (make-symbol "immed")))
    `(macrolet ((,f () ,body))
       (,f))))
使用immediate,我可以用以下方式重写原始问题中的示例代码:

(defun test ()
  (immediate (concat "Docstring" " of " "test" ))
  (+ 1
     (immediate (+ 1 1))
     3))
测试函数的定义与我预期的完全相同:

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

(immediate这个名字的灵感来源于Forth语言中的immediate单词。)

谢谢,但我的例子只是一个简单的例子,展示了这个想法,而不仅仅是为了不断折叠。结果可能是字符串、列表或其他内容。我相应地修改了我的问题。@Sylvester,仅供参考,elisp在这种情况下没有什么不同。您可以更新您的答案:
(反汇编(字节编译(defun test()(+1(编译时求值(+1))3))
您可以将示例更改为实际演示问题的示例吗?另一个简化示例,如为elisp函数生成文档字符串:
(defun test()(编译时求值(concat“doc”“string”)(+1 2))
使用
(documentation“test)
我们将得到“nil”。为函数测试生成的文档字符串实际上变成了
““docstring”
”——在
“docstring”前面加了一个引号
。无论如何,你不能这样做。如果你想指定一个非字符串的docstring,你需要使用symbol属性。如果它做了你想做的,只有在编译时才能工作的代码也不是一个好例子。相反,我希望生成一个字符串doc string。我的示例仍然显示了编译时如何使用
eval
l、 但是,预计算
(concat“doc”“string”)
的宏可以正确生成字符串doc string
“docstring”
,而不是不正确引用的“docstring”
““docstring”
。是的,但是对于uncompiled elisp,
defun
的docstring参数必须是一个字符串——它不能是简单计算为字符串的其他形式,因为这样它就不会被视为docstring,而是会成为函数体的一部分,在调用它时进行计算(这在原则上可能是无害的,也可能是灾难性的)。同样,如果编译时
eval
按照您希望的方式工作,那么您在这里的用法只适用于字节编译的代码,我认为这是一个可怕的目标,您不应该瞄准它。就是这样!macrolet就是答案。谢谢!
(defun test ()
  (immediate (concat "Docstring" " of " "test" ))
  (+ 1
     (immediate (+ 1 1))
     3))
(defun test ()
  "Docstring of test"
  (+ 1 2 3))