5

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!

Gwang-Jin Kim
  • 9,303
  • 17
  • 30
  • 1
    `(char-name #\Tab) ;; => "Tab"` ? Also `(symbol-name 'foo) ;; => "FOO"`. – Ehvince Jun 01 '18 at 18:56
  • 1
    Might like: how to use variables in format's control strings: https://stackoverflow.com/questions/48868555/in-common-lisp-format-how-does-recursive-formatting-work – Ehvince Jun 01 '18 at 19:05
  • 1
    You might like [str:join](https://github.com/vindarel/cl-str/#join-separator-list-of-strings), but it doesn't accept a character as separator (disclaimer: author of this simple lib). – Ehvince Jun 01 '18 at 19:06
  • 1
    You might want to use cl-interpol:https://stackoverflow.com/a/37102882/124319 and do `(format nil #?"~{~a~^$(separator)~}" '(a b c d))` where `separator` is lexically bound around the call. – coredump Jun 01 '18 at 19:12
  • @coredump: Yes! Exactly sth like that I was searching! Thank you very much!! – Gwang-Jin Kim Jun 01 '18 at 19:33

1 Answers1

8

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"
Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • Oh thank you!!! I was trying along for over an hour .... Thank you so much! That is exactly I wanted to do! So easy actually ... That is the super elegantly short `join` I was searching for! – Gwang-Jin Kim Jun 01 '18 at 22:25
  • Very elegant the solution to do `"~a~a~a"` first and then give the single parts of the string ... - no mess with escape characters - so nice! – Gwang-Jin Kim Jun 01 '18 at 22:29
  • 1
    Shortly I was loosing my faith in Lisp - that it is the best language ever - since couldn't do such an easy thing with the terseness which one is used to with Lisp - *now* its elegance shines again - Thank you @Sylwester! – Gwang-Jin Kim Jun 01 '18 at 22:31