Nicer pythonic `join` in common-lisp

2020-02-07 03:21发布

In Edi Weitz's cl cookbook, for the pythonic join, this function is suggested:

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

However, somehow I was thinking, there must be a way to express join in another way, maybe using format's capabilities ...

In Seibel's book, (in the chapter about format) we find joining of strings in a list to a single string with the separator ", " by:

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

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

Which is a pythonic join and which is very terse; the ~^ directive makes that ", " is added only until just before the last element and not added when no element is following.

However, here, the separator string ", " is part of the format directive.

A tricky case is e.g. (defvar sep #\Tab). If the representation of sep "#\Tab" literally can be placed as a separator in midst of this format directive, resulting in:

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

We would have reached the goal.

Obviously, one has to use a macro to generate the format directive ... I tried things like (princ-to-string sep) but this gives "#\\Tab" and not "#\Tab".

E.g.

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

But when trying:

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

This results of course is sth not desired: "a#\\Tabb#\\Tabc", since

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

But I don't see how to achieve this step to the desired macro ... Has anybody an enlightening about this?

Kind of a metaprogramming on metaprogramming problem ...

Okay, now I see, that @Rainer Joswig has already posted in What's the canonical way to join strings in a list?

a solution to this problem. However, if there would be a way, to represent "#\\Tab" as "#\Tab", one could come to a more compact definition. But somehow the Lisp reader seems in a string always to recognize "\Tab" as one letter. Is it possible to write a function to do that?

Remark

In R, there exist especially for metaprogramming, functions like as.name("myvar") which generate the symbolmyvar out of the string "myvar". and expressions like deparse(substitute(x)) which takes the symbol x and creates out of it a literal string "x". Deparse steps back the execution of print() commands, whereby escaping special symbols. deparse(deparse(substitute(x))) would e.g. generate "\"x\"" - While parse(text = ... ) around this expression would make out of it "x" again parse(text = deparse(deparse(substitute(x)))). How such things could be achived in common-lisp? e.g.(a-special-function #\Tab) resulting in (literal): "#\Tab" as a string?

Epilogue

Thank you @Sylwester!! He solved it without macro. And very elegantly!

1条回答
家丑人穷心不美
2楼-- · 2020-02-07 03:29

Your problem is that you try to add #\Tab to the format, but that is just how you do it with literals. If you just inserted a tab character or a string consisting of it in the format string it will do what you want:

(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"
查看更多
登录 后发表回答