通用Lisp在符号中添加后缀以在宏中使用

时间:2018-11-23 23:41:13

标签: common-lisp

您如何在宏中引入带有后缀的可预测的新标识符?


因此,我正在编写一个用于处理四元数的简单库。我暂时使用的可能是最简单的表示形式,即组件列表,但我想定义一个不依赖该表示形式的简单API。

在定义算法时,我想使用可预测的名称(例如somesymbol-realpartsomesymbol-i来引用四元数的每个组成部分。

我希望能够使用以下代码段。

(let
     ((my-quat '(1 2 3 4)))
  (with-quaternion my-quat
           (assert-equalp 1 my-quat-realpart)
           (assert-equalp 2 my-quat-i)
           (assert-equalp 3 my-quat-j)
           (assert-equalp 4 my-quat-k)))

但是,我用来生成带后缀的符号的方法似乎会生成奇怪的区分大小写的符号,并带有大写的转义字符。

(defun add-suffix-to-symbol (sym suffix)
  (intern (concatenate 'string "" (string sym) "-" suffix)))

将符号转换为字符串的结果是,它以大写形式输出...这是完全有效的规范化。但是,由于某种原因,通过intern创建一个新符号可以保留大小写,因此我必须做类似以下的事情来引用with-quaternion引入的绑定。

(let
    ((my-quat '(1 2 3 4)))
  (with-quaternion my-quat
           (assert-equalp 1 |MY-QUAT-realpart|)
           (assert-equalp 2 |MY-QUAT-i|)
           (assert-equalp 3 |MY-QUAT-j|)
           (assert-equalp 4 |MY-QUAT-k|)))

如何创建与旧符号相同但带有后缀的新符号,以便可以在宏中使用它?

供参考,这里是所有代码。

(defun assert-equalp (e a)
  (assert (equalp e a)))

(defun quat-realpart (q)
  (first q))

(defun quat-i (q)
  (second q))

(defun quat-j (q)
  (third q))

(defun quat-k (q)
  (fourth q))

(assert-equalp '1 (quat-realpart '(1 2 3 4)))
(assert-equalp '2 (quat-i '(1 2 3 4)))
(assert-equalp '3 (quat-j '(1 2 3 4)))
(assert-equalp '4 (quat-k '(1 2 3 4)))

(defun add-suffix-to-symbol (sym suffix)
  (intern (concatenate 'string "" (string sym) "-" suffix)))

(print (add-suffix-to-symbol 'a "suffix"))

(defgeneric with-quaternion-impl (q-sym body))

(defmethod with-quaternion-impl ((q-sym symbol) body)
  (let
      ((q-realpart (add-suffix-to-symbol q-sym "realpart"))
       (q-i (add-suffix-to-symbol q-sym "i"))
       (q-j (add-suffix-to-symbol q-sym "j"))
       (q-k (add-suffix-to-symbol q-sym "k")))
    `(let
     ((,q-realpart (quat-realpart ,q-sym))
      (,q-i (quat-i ,q-sym))
      (,q-j (quat-j ,q-sym))
      (,q-k (quat-k ,q-sym)))
       (progn ,@body))))


(defmacro with-quaternion (q-sym &rest body)
  (with-quaternion-impl q-sym body))


(let
    ((my-quat '(1 2 3 4)))
  (with-quaternion my-quat
           (assert-equalp 1 |MY-QUAT-realpart|)
           (assert-equalp 2 |MY-QUAT-i|)
           (assert-equalp 3 |MY-QUAT-j|)
           (assert-equalp 4 |MY-QUAT-k|)))

(let
    ((my-quat '(1 2 3 4)))
  (with-quaternion my-quat
           (assert-equalp 1 my-quat-realpart)
           (assert-equalp 2 my-quat-i)
           (assert-equalp 3 my-quat-j)
           (assert-equalp 4 my-quat-k)))

clisp下运行时,它将打印以下符号,清楚地带有转义的大写字符。

|A-suffix|

并产生以下错误消息:

*** - PROGN: variable MY-QUAT-REALPART has no value

2 个答案:

答案 0 :(得分:6)

默认情况下,普通lisp中的符号以大写形式插入。明显的不区分大小写的原因是,除非您对条形码字符|My-case-sensitive-SYMBOL|使用特殊语法,否则您键入的所有内容都会在读取/检索时转换为大写。 my-case-insensitive-symbolMY-CASE-INSENSITIVE-SYMBOL指的是同一个插入符号,它以大写形式存储(尽管这是常见的Lisp,通常可以通过命令行选项和读取器宏来更改它)。该符号实际上根本不区分大小写,它只是以这种方式出现,因为您代码中的大多数符号都是由读者大写的,除非您通过将其括在条形字符中或专门为不寻常的读者配置环境而将它们免除选项。

以上所有内容的最终结果是,如果您想使用更熟悉的语法访问由宏生成的符号,请确保在插入之前将所有组件都大写,例如:

(add-suffix-to-symbol q-sym "I")

代替

(add-suffix-to-symbol q-sym "i")

另一种选择是传递要串联的符号而不是字符串,例如

(defun add-suffix-to-symbol (sym suffix)
  (intern (concatenate 'string "" (string sym) "-" (string suffix))))

(print (add-suffix-to-symbol 'FOO 'bar)) ; foo-bar
(print (add-suffix-to-symbol 'foo '|bar|)) ; |FOO-bar| because foo is converted to FOO at read time

答案 1 :(得分:3)

在Joe的回答中加点内容:

符号是一种具有名称,值,属性列表的数据类型,它们可以插入到程序包(另一个Lisp数据结构)中。

符号保留其名称字符串大小写

您可以从字符串中创建带有名称的符号,也可以要求输入符号名称。制作符号的功能是make-symbol

CL-USER 8 > (make-symbol "This is A Symbol!!!***")
#:|This is A Symbol!!!***|

CL-USER 9 > (symbol-name (make-symbol "This is A Symbol!!!***"))
"This is A Symbol!!!***"

如您所见,该字符串按原样使用,我们按提供的字符串将其取出。没有大小写转换。

为读者转义符号

要打印具有不同大小写,空格和/或特殊字符的符号,请使用|或单个\来转义该符号:

CL-USER 11 > '|foo BAR ***# <>|
|foo BAR ***# <>|

CL-USER 12 > '\f\o\o\ BAR\ ***#\ <>
|foo BAR ***# <>|

默认情况下,阅读器将符号名称的未转义输入大写

Lisp阅读器可以使用诸如find-symbolintern之类的功能来查找或创建符号。两者都可以将字符串作为输入,并且它们还区分大小写:

CL-USER 15 > (let ((symbol '|foo|))
               (eq (find-symbol "FOO") symbol))
NIL

但是阅读器本身(例如,通过readread-from-string使用)默认情况下不区分大小写。默认情况下,所有符号都大写:

CL-USER 21 > (symbol-name 'foo)
"FOO"

CL-USER 22 > (symbol-name 'FOO)
"FOO"

CL-USER 23 > (eq 'foo 'FOO)
T

默认情况下,我们可以检查打印机和阅读器是否都使用大写字母

CL-USER 35 > *print-case*
:UPCASE

CL-USER 36 > (readtable-case *readtable*)
:UPCASE

在宏中创建符号时,我们通常需要大写字符串

这意味着,当人们用字符串中的名称创建符号时,通常需要大写的字符串作为输入:

小写字母:

CL-USER 25 > (intern "zippy")
|zippy|
NIL

大写:

CL-USER 26 > (intern "ZIPPY")
ZIPPY
NIL

在数据中,有时我们需要混合大小写的符号:转义它们

有时候我们想处理不同的案例:例如,当案例由于用作数据而需要保存时:

CL-USER 27 > (defvar *parents* '(|Eva Luator| |Ben BitDiddle jr.|))
*PARENTS*

CL-USER 28 > *parents*
(|Eva Luator| |Ben BitDiddle jr.|)

使用格式创建大写符号名称

通常在代码一中使用format创建符号名称-可能比concatenate更为简洁。然后可以使用format控制字符串,其中的文本或部分文本通过使用~:@(~)大写:

CL-USER 33 > (format nil "~:@(~a-~a-~a~)" "my" "macro" "name")
"MY-MACRO-NAME"

CL-USER 34 > (intern (format nil "~:@(~a-~a-~a~)" "my" "macro" "name"))
MY-MACRO-NAME