3

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

但是我从 Rosetta 代码(日历任务)中发现了这个 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) "  ")))))))

我没有测试。

我的问题是,

为什么这在函数中是必需的,用例是什么?

它与宏有何不同?

4

3 回答 3

6

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

`(,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,在第一种情况下使用 quasiquoted 表达式将不起作用,但在第二种和第三种情况下,因为在这些情况下最后一个缺点需要是新鲜的。

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

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

(macroexpand '`(,a ,b ,@c))
; ==> (cons a (cons b c)) 
; ==> t
于 2018-02-04T20:54:26.570 回答
4

反引号

请参阅 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. 使用嵌套函数,如cons, list, list*, append, ... 这在要计算的元素很多时特别有用。
  2. 使用带有反引号运算符和,的列表模板进行评估。当存在具有固定对象和一些要计算的对象的嵌套列表时,这尤其有用。,,@,.

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

您还可以将模板视为反转:

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

警告

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

向量也是

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

CL-USER 20 > (let ((a 1)
                   (b 2)
                   (c '(3 4 5)))
               `#(,a
                  ,b
                  ,@c))
#(1 2 3 4 5)
于 2018-02-05T08:23:05.890 回答
2

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

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

QQ有两种辅助机制:

  1. 取消引号 - 表示 (,)

  2. unquote-splicing - 表示为 (,@)

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

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

请注意以下表达式的值的差异:报价、准报价。

输入:

'(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

于 2018-02-04T20:59:24.163 回答