lisp宏中的符号操作
Symbol manipulation in lisp macro
我正在为 Lisp 语言编写玩具解释器,其中我有以下 CL 代码:
(defun mal-list (&rest args)
(make-mal :type 'list
:value args))
(register-fun '|list| #'mal-list)
(defun mal-list? (arg)
(eq (mal-type arg) 'list))
(register-fun '|list?| #'mal-list?)
但是,我宁愿简单地写这样的东西:
(defmal list (&rest args)
(make-mal :type 'list
:value args))
(defmal list? (arg)
(eq (mal-type arg) 'list))
我试图编写一个宏来执行此操作,但我对带条的符号有疑问(我很困惑这是什么!)。这是我试过的:
(defmacro defmal (name args &body body )
(let ((funsym (intern (format nil "~{~a~}" `(mal- ,name)))))
`(register-fun `|,name| (defun ,funsym ,args ,@body))))
没有解决,因为 `|,name|
字面上的意思是 |,name|
,而不是 |list|
我猜这是一个 XY 问题,但我不确定如何解决这个问题。
|...|
语法只是 Lisp 打印机打印名称中包含需要转义字符的符号的方式之一(并且 reader 可以 阅读 名称中含有这些字符的符号):
(print (intern "foo"))
;=> |foo|
还有其他方法,包括转义单个字符:
(print '|FOO|)
;=> FOO
(print '\f\o\o)
;=> |foo|
您要做的只是创建一个名称包含小写字母的符号。这很简单,如上所示。不过,您的部分问题是您输入的是一个名称全是大写字母的符号,因此您需要先小写:
CL-USER> (symbol-name 'FOO)
;=> "FOO"
CL-USER> (intern (symbol-name 'FOO))
;=> FOO
CL-USER> (string-downcase (symbol-name 'FOO))
;=> "foo"
CL-USER> (intern (string-downcase (symbol-name 'FOO)))
;=> |foo|
其实,因为string-downcase取string designators,不只是字符串,可以直接传入符号:
CL-USER> (intern (string-downcase 'BaR))
;=> |bar|
所以,在所有字符串处理之后,我们可以转到宏。
听起来您正在寻找这样的东西:
(defmacro defmal (name lambda-list &body body)
(let ((mal-name (intern (concatenate 'string "MAL-" (symbol-name name))))
(mal-norm (intern (string-downcase name))))
`(progn
(defun ,mal-name ,lambda-list
,@body)
(register-function ',mal-norm #',mal-name))))
CL-USER> (pprint (macroexpand-1 '(defmal list? (arg)
(eq (mal-type arg) 'list))))
(PROGN
(DEFUN MAL-LIST? (ARG) (EQ (MAL-TYPE ARG) 'LIST))
(REGISTER-FUNCTION '|list?| #'MAL-LIST?))
在生成符号名称时避免使用 format 通常是个好主意,因为特定的输出可能会发生变化,具体取决于其他变量。例如:
(loop for case in '(:upcase :downcase :capitalize)
collect (let ((*print-case* case))
(format nil "~a" 'foo)))
;=> ("FOO" "foo" "Foo")
相反,您可以使用 concatenate 与字符串(或符号的符号名称)。因为 reader 也可以有不同的区分大小写设置,有时我什至会这样做(但不是每个人都喜欢这样):
(concatenate 'string (symbol-name '#:mal-) (symbol-name name))
这样,如果 reader 做了任何不寻常的事情(例如,保留大小写,因此 mal-
的符号名称是 "mal-
),您可以将其保留在您自己的也生成符号。
除了, consider using a function from the Alexandria图书馆:
format-symbol
就像 format
, but inside with-standard-io-syntax
。这里,t
代表当前包,name是小写的:
(format-symbol t "mal-~(~A~)" name)
=> |mal-list|
symbolicate
在当前包中连接和实习生:
(symbolicate '#:mal- name)
如果您当前的可读表是否保留大小写,您可以以 |MAL-LIST|
或 |mal-list|
结束。为了完整起见,请注意 readtable-case
可以设置为 following values::upcase
、:downcase
、:preserve
或 :invert
(这个我觉得相当有趣)。
我正在为 Lisp 语言编写玩具解释器,其中我有以下 CL 代码:
(defun mal-list (&rest args)
(make-mal :type 'list
:value args))
(register-fun '|list| #'mal-list)
(defun mal-list? (arg)
(eq (mal-type arg) 'list))
(register-fun '|list?| #'mal-list?)
但是,我宁愿简单地写这样的东西:
(defmal list (&rest args)
(make-mal :type 'list
:value args))
(defmal list? (arg)
(eq (mal-type arg) 'list))
我试图编写一个宏来执行此操作,但我对带条的符号有疑问(我很困惑这是什么!)。这是我试过的:
(defmacro defmal (name args &body body )
(let ((funsym (intern (format nil "~{~a~}" `(mal- ,name)))))
`(register-fun `|,name| (defun ,funsym ,args ,@body))))
没有解决,因为 `|,name|
字面上的意思是 |,name|
,而不是 |list|
我猜这是一个 XY 问题,但我不确定如何解决这个问题。
|...|
语法只是 Lisp 打印机打印名称中包含需要转义字符的符号的方式之一(并且 reader 可以 阅读 名称中含有这些字符的符号):
(print (intern "foo"))
;=> |foo|
还有其他方法,包括转义单个字符:
(print '|FOO|)
;=> FOO
(print '\f\o\o)
;=> |foo|
您要做的只是创建一个名称包含小写字母的符号。这很简单,如上所示。不过,您的部分问题是您输入的是一个名称全是大写字母的符号,因此您需要先小写:
CL-USER> (symbol-name 'FOO)
;=> "FOO"
CL-USER> (intern (symbol-name 'FOO))
;=> FOO
CL-USER> (string-downcase (symbol-name 'FOO))
;=> "foo"
CL-USER> (intern (string-downcase (symbol-name 'FOO)))
;=> |foo|
其实,因为string-downcase取string designators,不只是字符串,可以直接传入符号:
CL-USER> (intern (string-downcase 'BaR))
;=> |bar|
所以,在所有字符串处理之后,我们可以转到宏。
听起来您正在寻找这样的东西:
(defmacro defmal (name lambda-list &body body)
(let ((mal-name (intern (concatenate 'string "MAL-" (symbol-name name))))
(mal-norm (intern (string-downcase name))))
`(progn
(defun ,mal-name ,lambda-list
,@body)
(register-function ',mal-norm #',mal-name))))
CL-USER> (pprint (macroexpand-1 '(defmal list? (arg)
(eq (mal-type arg) 'list))))
(PROGN
(DEFUN MAL-LIST? (ARG) (EQ (MAL-TYPE ARG) 'LIST))
(REGISTER-FUNCTION '|list?| #'MAL-LIST?))
在生成符号名称时避免使用 format 通常是个好主意,因为特定的输出可能会发生变化,具体取决于其他变量。例如:
(loop for case in '(:upcase :downcase :capitalize)
collect (let ((*print-case* case))
(format nil "~a" 'foo)))
;=> ("FOO" "foo" "Foo")
相反,您可以使用 concatenate 与字符串(或符号的符号名称)。因为 reader 也可以有不同的区分大小写设置,有时我什至会这样做(但不是每个人都喜欢这样):
(concatenate 'string (symbol-name '#:mal-) (symbol-name name))
这样,如果 reader 做了任何不寻常的事情(例如,保留大小写,因此 mal-
的符号名称是 "mal-
),您可以将其保留在您自己的也生成符号。
除了
format-symbol
就像format
, but insidewith-standard-io-syntax
。这里,t
代表当前包,name是小写的:(format-symbol t "mal-~(~A~)" name) => |mal-list|
symbolicate
在当前包中连接和实习生:(symbolicate '#:mal- name)
如果您当前的可读表是否保留大小写,您可以以
|MAL-LIST|
或|mal-list|
结束。为了完整起见,请注意readtable-case
可以设置为 following values::upcase
、:downcase
、:preserve
或:invert
(这个我觉得相当有趣)。