cl-str icon indicating copy to clipboard operation
cl-str copied to clipboard

Adding :grammar to split and join

Open johnwcowan opened this issue 5 years ago • 2 comments

This proposal adds the keyword :grammar to the split and join functions. Here is its meaning for join:

  • :infix means an infix or separator grammar: insert the delimiter between list elements. An empty list will produce an empty string.
  • :strict-infix means the same as :infix if the string-list is non-empty, but will signal an error if given an empty list.
  • :suffix means a suffix or terminator grammar: insert the delimiter after every list element.
  • :prefix means a prefix grammar: insert the delimiter before every list element.

And here for split:

  • :infix is the same as above, but the empty string returns an empty list.
  • :strict-infix is the same as above, but the empty string signals an error.
  • :suffix and :prefix are the same as above.

Source: SRFI 152

johnwcowan avatar May 16 '20 01:05 johnwcowan

I demand code examples ;)

First thoughts:

  • It looks like :infix is the current default.
  • to make the function error on an empty list, I'd give a more explicit argument

You actually felt this need?

vindarel avatar May 20 '20 12:05 vindarel

From SRFI 152, but still should be readable:

(string-join '("foo" "bar" "baz")) => "foo bar baz" (string-join '("foo" "bar" "baz") "") => "foobarbaz" (string-join '("foo" "bar" "baz") ":") => "foo:bar:baz" (string-join '("foo" "bar" "baz") ":" :grammar :suffix) => "foo:bar:baz:"

;; Infix grammar is ambiguous wrt empty list vs. empty string: (string-join '() ":") => "" (string-join '("") ":") => ""

;; Suffix and prefix grammars are not: (string-join '() ":" :grammar :suffix)) => "" (string-join '("") ":" :grammar :suffix)) => ":"

An obvious application of :prefix is constructing an absolute path (Posix, Windows, URI, what have you) from a list of components. I'm not sure what to say about :suffix,

johnwcowan avatar May 20 '20 13:05 johnwcowan

Here would be a working example of join with grammar keyword:

(defun join (separator strings &key (grammar :infix))
  (let ((sep (if separator
                 (string separator)
                 " ")))
    (case grammar
      (:infix
       (with-output-to-string (out)
         (loop for (s . rest) on strings
               do (write-string s out)
               unless (null rest)
                 do (write-string sep out))))
      (:strict-infix
       (when (= 0 (length strings))
         (error "When grammar is :strict-infix, strings cannot be nil or a list with zero elements"))
       (funcall #'join sep strings :grammar :infix))
      (:suffix
       (with-output-to-string (out)
         (loop for (s . rest) on strings
               do (write-string (concat s sep) out))))
      (:prefix
       (with-output-to-string (out)
         (loop for (s . rest) on strings
               do (write-string (concat sep s) out)))))))

We need to decide whether we follow the Scheme convention of adding " " when separator is nil or if we use "". Also we could name the second parameter string-list (more descriptive?) instead of strings.

kilianmh avatar Mar 23 '23 12:03 kilianmh

I wouldn't add these changes. I don't find the grammar stuff self-explanatory enough. I think we should rather understand what's missing from the existing function, and add meaningful key arguments.

It looks like the added features, with join, are:

  • add a separator at the end
  • at the start
  • "strict" mode: signal an error on the empty list

that's it? Do we want to add "ensure-prefix,suffix" key args?

vindarel avatar Apr 03 '23 14:04 vindarel