common-lisp 中更好的 pythonic `join`

Nicer pythonic `join` in common-lisp

在Edi Weitz的cl cookbook中,对于pythonic join,建议使用这个函数:

(defun join (separator list)
  (with-output-to-string (out)
    (loop for (element . more) on list
          do (princ element out)
          when more
            do (princ separator out))))

然而,不知何故我在想,一定有一种方法可以用另一种方式来表达join,也许可以使用format的能力...

在 Seibel 的书中,(在关于 format 的章节中)我们发现使用分隔符将列表中的字符串连接到单个字符串 ", " 来自:

(defvar l '("a" "b" "c"))

(format nil "~{~A~^, ~}" l)
;; "a, b, c"

这是一个 pythonic 连接,非常简洁; ~^ 指令使得 ", " 仅添加到最后一个元素之前,并且在没有元素跟随时不添加。

然而,在这里,分隔符字符串 ", " 是格式指令的一部分。

一个棘手的例子是(defvar sep #\Tab)。 如果 sep "#\Tab" 的表示字面上可以作为分隔符放在此格式指令中间,则结果为:

(format nil "~{~A~^#\Tab~}" l)

我们本来可以达到目标的。

显然,必须使用宏来生成格式指令... 我尝试了 (princ-to-string sep) 之类的东西,但这给出了 "#\Tab" 而不是 "#\Tab".

例如

(defmacro join (sep l)
  `(format nil ,(format nil "~{~A~}" `("\~\{\~A\~\^" ,(write-to-string sep) "\~\}")) l))

但是尝试时:

(join #\Tab '("a" "b" "c"))

这个结果当然是不希望的:"a#\Tabb#\Tabc",因为

(macroexpand-1 '(join #\Tab '("a" "b" "c")))
;; results in:
(FORMAT NIL "~{~A~^#\Tab~}" L)
;; instead of:
(FORMAT NIL "~{~A~^#\Tab~}" L)

但我看不出如何将这一步实现到所需的宏... 有人对此有启发吗?

一种关于元编程问题的元编程...

好的,现在我明白了,@Rainer Joswig 已经在 What's the canonical way to join strings in a list?

这个问题的解决方案。但是,如果有办法将 "#\Tab" 表示为 "#\Tab",则可以得出更紧凑的定义。 但不知何故,Lisp reader 似乎在一个字符串中总是将 "\Tab" 识别为一个字母。是否可以编写一个函数来做到这一点?

备注

在 R 中,存在专门用于元编程的函数,例如 as.name("myvar"),它可以从字符串 "myvar" 中生成符号 myvar。 以及像 deparse(substitute(x)) 这样的表达式,它采用符号 x 并从中创建一个文字字符串 "x"。 Deparse 后退 print() 命令的执行,从而转义特殊符号。 deparse(deparse(substitute(x))) 例如生成 "\"x\"" - 虽然围绕此表达式的 parse(text = ... ) 会再次生成 "x" parse(text = deparse(deparse(substitute(x))))。 如何在 common-lisp 中实现这样的事情? 例如 (a-special-function #\Tab) 导致(文字):"#\Tab" 作为字符串?

结语

谢谢@Sylwester!!他不用宏就解决了。而且非常优雅!

您的问题是您尝试将 #\Tab 添加到格式中,但这正是您使用文字的方式。如果您只是在格式字符串中插入一个制表符或由它组成的字符串,它将执行您想要的操作:

(defun join (l &key (sep ", "))
  (format nil (format nil "~a~a~a" "~{~a~^" sep "~}") l))

(join '(1 2 3)) 
; ==> "1, 2, 3"
(join '(1 2 3) :sep #\Tab) 
; ==> "1    2   3"