【问题标题】:Common Lisp add suffix to symbol for use in macrosCommon Lisp 为符号添加后缀以在宏中使用
【发布时间】:2019-04-26 11:51:16
【问题描述】:

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


所以,我正在编写一个用于操作四元数的简单库。我暂时使用了可能可行的最简单的表示形式,即组件列表,但我想定义一个不依赖于该表示形式的简单 API。

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

我希望能够让以下 sn-p 工作。

(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

【问题讨论】:

    标签: common-lisp


    【解决方案1】:

    补充一点乔的回答:

    符号是一种具有名称、值、属性列表的数据类型,它们可以被嵌入在一个包中(另一种 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.|)
    

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

    通常在代码 1 中使用 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
    

    【讨论】:

      【解决方案2】:

      common 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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-09-28
        • 2013-07-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多