Nicer pythonic `join` in common-lisp
Asked Answered
L

1

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!

Leucoderma answered 1/6, 2018 at 16:27 Comment(5)
(char-name #\Tab) ;; => "Tab" ? Also (symbol-name 'foo) ;; => "FOO".Keratinize
Might like: how to use variables in format's control strings: #48869055Keratinize
You might like str:join, but it doesn't accept a character as separator (disclaimer: author of this simple lib).Keratinize
You might want to use cl-interpol:https://mcmap.net/q/412496/-lisp-string-formatting-with-named-parameters and do (format nil #?"~{~a~^$(separator)~}" '(a b c d)) where separator is lexically bound around the call.Retinitis
@coredump: Yes! Exactly sth like that I was searching! Thank you very much!!Leucoderma
L
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"
Laster answered 1/6, 2018 at 22:20 Comment(3)
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!Leucoderma
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!Leucoderma
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!Leucoderma

© 2022 - 2024 — McMap. All rights reserved.