【问题标题】:backquote, unquote and unquote-splicing in normal functions正常函数中的反引号、取消引用和取消引用拼接
【发布时间】:2018-07-14 16:30:48
【问题描述】:

我仍在了解宏,虽然我认为我了解“反引号”、“取消引用”和“取消引用拼接”的基础知识,但我认为它们仅在宏中使用/有用。

但是我从 Rosetta 代码(calender task)中发现了这个 Common Lisp 代码,

(defun month-strings (year month)
  "Collect all of the strings that make up a calendar for a given
    MONTH and YEAR."
  `(,(date-calc:center (month-to-word month) (length *day-row*))
    ,*day-row*
     ;; We can assume that a month calendar will always fit into a 7 by 6 block
     ;; of values. This makes it easy to format the resulting strings.
      ,@ (let ((days (make-array (* 7 6) :initial-element nil)))
           (loop :for i :from (date-calc:day-of-week year month 1)
                 :for day :from 1 :to (date-calc:days-in-month year month)
                :do (setf (aref days i) day))
           (loop :for i :from 0 :to 5
             :collect
             (format nil "~{~:[  ~;~2,d~]~^ ~}"
                 (loop :for day :across (subseq days (* i 7) (+ 7 (* i 7)))
                  :append (if day (list day day) (list day))))))))

这里的反引号、取消引号和取消引号拼接用于普通函数,它可以创建字符串列表。

虽然我不使用 Scheme,但 Racket 解决方案也有类似的东西,

(define days
    (let ([? (if (= mn 12) (λ(x y) y) (λ(x y) x))])
      (round (/ (- (find-seconds 0 0 12 1 (? (+ 1 mn) 1) (? yr (+ 1 yr))) s)
                60 60 24))))
  (list* (~a mname #:width 20 #:align 'center) "Su Mo Tu We Th Fr Sa"
         (map string-join
              (nsplit 7 `(,@(make-list pfx "  ")
                          ,@(for/list ([d days])
                              (~a (+ d 1) #:width 2 #:align 'right))
                          ,@(make-list (- 42 pfx days) "  ")))))))

我没有测试。

我的问题是,

为什么在函数中需要这样做,用例是什么?

它与宏有何不同?

【问题讨论】:

  • 反引号是用于制作 s 表达式(基于嵌套列表的结构)的语法糖。 Lisp 源代码由 s 表达式组成,因此反引号对于宏(输入和输出是代码的函数,采用 s 表达式的形式)很有用。许多 Lisp 数据是由 s-expressions 组成的,因此反引号对于操作数据的函数很有用(当数据是 s-expressions 的形式时)

标签: list scheme common-lisp backquote


【解决方案1】:

quasiquote、unquote 和 unquote-splicing 只是引用数据、listcons 组合的语法糖。想象一下:

`(,a b c)    ; == (cons a '(b c))
`(a b ,c)    ; == (list 'a 'b c)
`(a b ,@c d) ; == (cons 'a (cons 'b (append c '(d))))  

这些都是一些琐碎的小例子,所以你可以想象右手边可能会变得非常复杂,但很高兴知道 quasiquote 魔法在需要时会产生新的缺点,并在尾部保持文字不变。因此,使用nconc 的准引用表达式在第一种情况下不起作用,但在第二种和第三种情况下,因为在这些情况下最后一个缺点需要是新鲜的。

如果您有一个创建列表结构的函数,quasiquote 将使代码更加清晰和简洁,因为表单看起来更像结果。它与宏没有什么不同,因为两者都创建列表结构。宏的不同之处在于结果发生了什么。在函数中返回值并在宏代码中被替换。

您可以检查使用 macroexpand 的宏后会发生什么:

(macroexpand '`(,a ,b ,@c))
; ==> (cons a (cons b c)) 
; ==> t

【讨论】:

  • macroexpand 的东西并没有真正起作用或通常没有用处。由于反引号形式的表示是未定义的,因此未定义它应该读入宏形式。
  • '只是语法糖' -> 从概念上讲,但不是字面意思。
  • @RainerJoswig 与list 一样,是嵌套cons 的抽象:-) '`(,a b c) 可能会直接变成(list a 'b 'c),在阅读器中完成。完全可以,然后macroexpand 将返回nil,因为它没有做任何事情,但是您仍然有一个表单,在评估时会生成一个包含至少一个新缺点的列表。如果我要求 3 lispers 在不使用 quasiquote 的情况下产生结果,也是一样的。有些可能不会扩展到使用 CL 表单,但我还没有找到不使用的实现
  • 它允许实现使用特定的内部构造,因此例如(list a)(system:bq-list a) 之间存在差异。一些使用函数(您不能将其宏扩展为 Lisp 原语),另一些在内部使用宏。例如,它允许将其打印为反引号表达式。此外,可以将反引号表达式读取为更复杂的内容,例如将逗号运算符读取为结构。因此,macroexpand 将返回一个包含“不透明”结构的列表......
  • @RainerJoswig 是的,但你是说system:bq-list 可能不是宏,而是系统具有不同显示规则的特殊功能?看起来很恶心。无论如何,在这种情况下,除非您查找 system:bq-list 或阅读源代码,否则它不会有太大帮助。我使用的许多实现最终都使用 CL 命名空间中的表单,即使这不是必需的。
【解决方案2】:

反引号

Backquote 上查看 CLHS。

示例

示例与此代码类似:

CL-USER 14 > (let ((a 1)
                   (b 2)
                   (c '(3 4 5)))
               `(,a                ; a gets evaluated and put in
                 ,b                ; b gets evaluated and put in
                 ,@c))             ; c gets evaluated and spliced in
(1 2 3 4 5)

上述代码的效果与使用函数list*类似。

CL-USER 15 > (let ((a 1)
                   (b 2)
                   (c '(3 4 5)))
               (list* a b c))     
(1 2 3 4 5)

您使用哪个版本主要是个人喜好问题。

list* 创建一个包含第一个值的列表并将它们放在最后一个值的前面,这应该是一个有用的列表。

列表创建

创建列表有多种方式和样式。这里的两个:

  1. 使用嵌套函数,例如conslistlist*append,...这在要计算的元素很多时特别有用。
  2. 使用列表的模板与反引号运算符和,,@,. 进行评估。当嵌套列表包含固定对象和一些要计算的对象时,这尤其有用。

因此,当您考虑填写列表模板时,反引号的方式很有用。每当您想要创建(嵌套)列表时,这些列表是基于丢失了常量结构(意思是对象和嵌套)的模板,那么这是一种方法。这不仅限于宏 - 它是构建列表的通用机制。

你也可以认为模板是一种倒置:

  1. 函数默认求值,常量元素需要引用
  2. 默认情况下,反引号模板不计算,变量元素需要取消引用

警告

反引号表达式本身不需要是纯列表。反引号表达式的内部表示是未定义的,实现实际上不同。

向量也是如此

请注意,这也适用于向量:

CL-USER 20 > (let ((a 1)
                   (b 2)
                   (c '(3 4 5)))
               `#(,a
                  ,b
                  ,@c))
#(1 2 3 4 5)

【讨论】:

    【解决方案3】:

    Quasi-Quote (QQ) 是Scheme 中的列表构造函数。

    它比引号 (')、list 或 cons 更灵活,因为它允许将符号与表达式求值混合。

    QQ有两种辅助机制:

    1. 不加引号 - 表示为 (,)

    2. 取消引号拼接 - 表示为 (,@)

    当使用 QQ 时,会启动一个引用上下文。取消引用允许我们暂时转义引用上下文并在取消引用之后立即评估表达式。取消引用拼接既可以从引用上下文中转义整个列表,也可以“展开”列表。 考虑:

    (define b 5)
    (define s (list 1 2))
    

    请注意以下表达式的值的差异:Quote、Quasi-Quote。

    输入:

    '(a b c)
    `(a b c)
    

    输出

    > (a b c)    
    > (a b c)
    

    输入

    '(a ,b c)    
    `(a ,b c)
    

    输出

    > (a ,b c)   
    > (a 5 c)
    

    输入

    '(a ,s c)    
    `(a ,s c)
    

    输出

    > (a ,s c)   
    > (a (1 2) c)
    

    输入

    '(a ,@s c)   
    `(a ,@s c)
    

    输出

    > (a ,@s c)  
    > (a 1 2 c)
    

    来源:我参加的编译课程,The Common Lisp Cookbook - Macros and Backquote

    【讨论】:

    • 值得注意的是,虽然问题引用了方案和这些示例在方案中有效,但问题还引用了 Common Lisp,因为在反引号之外使用逗号是非法的。跨度>
    • 我不太确定这是从哪里来的。 1) 我并不是真正的匿名——这是我的真名。 2) 在方案中,如果你输入'(a ,b),你会得到(a ,b)(a (unquote b)) 的语法糖。如果我在 (sbcl) Common Lisp 中尝试相同的操作,我会收到错误:Comma not inside backquoteSB-INT:SIMPLE-READER-ERROR 类型的条件。
    • 我会告诉陌生人或同事。我不认为这句话是大满贯或侵略性的。如果问题只是关于方案,那么这将是一个非常好的例子。在这方面,scheme 和 Common Lisp 之间的差异很小而且很微妙,因此对于一个与语言之间差异无关的问题,并不真正需要一个新的答案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-05
    • 2016-11-18
    • 2014-10-12
    • 2021-10-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多