2

In an Emacs package I have a defcustom, say custom-string, that is defined as an atom of type string like so:

(defcustom custom-string "foo"
  "A customizable string."
  :type 'string)

I need to generalize the variable so that it can be a sequence of strings, but if the user has already chosen a single string it will be fine to continue using that string as before. (For example, the variable could be a list of paths to search. One path is sufficient, but some users may want to use multiple paths.) I'd like to update the package in a way that doesn't break existing configurations. I see two ways forward, but I don't know which is preferable or if there is some other standard approach for this:

  1. Deprecate custom-string with make-obsolete-variable and and create a new variable called custom-strings which is of type (repeat string). In this case, if the user has defined the atomic variable, then it can be detected and used to initialize the list variable. This approach looks like this:

    (make-obsolete-variable 'custom-string 'custom-strings "2017-11-10")
    
    (defcustom custom-strings
      (if (boundp 'custom-string)
          (list custom-string)
        '("first" "second" "third"))
      "Customizable list of strings."
      :type '(repeat string))
    

    The benefits of this approach are that the user doesn't have to change his or her configuration and the new variable name is plural, an indication that it is a sequence.

  2. Keep the variable name the same but change the defcustom type to (repeat string). In the code where the variable is used, add a conditional to check if it is a string or a list.

    (if (stringp custom-string)
        ... ; handle single string
      ...) ; handle list case
    

    This approach also has the benefit of allowing the user init file to remain unchanged, and furthermore it leaves the variable name the same so that documentation, tutorials, etc. don't need to be updated. But it may be confusing that it is a list with a singular variable name (i.e., -string not -strings). I also worry that this might break existing configurations for users who use the customize interface, where it has been stored as a string. (I don't use customize, so I'm not sure how this is handled.)

I have used the first approach before in markdown-mode, when I changed markdown-css-path to markdown-css-paths so that users could link multiple stylesheets. However, this time I have several variables to change at once and I'd like this to be as unobtrusive as possible. I haven't been able to find examples from other packages. Which of these is the most "Emacs-like" way of proceeding? Or is there another, better solution I haven't thought of?

2
  • Be aware that what you are calling a "path" here is what Emacs calls an "absolute file name". Emacs uses "path" for a list of file/directory names, in particular for the kind of list that is the value of environment variable PATH.
    – Drew
    Commented Nov 10, 2017 at 15:36
  • @Drew: Thanks for the clarification. I'm getting into more trouble by trying to give a simple example without getting bogged down in the specific details. The actual scenario is providing a character or list of characters (as fallbacks, in case some aren't displayable under the user's font) to prettify markup in markdown-mode. I expect users to mostly pick one character, but in the package I want to give a longer list of possibilities. Based on your answer, I think the second approach may be best. Those interested can see github.com/jrblevin/markdown-mode/issues/264 for more details. Commented Nov 10, 2017 at 15:46

1 Answer 1

3

There is no more or less emacsy way, here, in my opinion. Rather, what you do with respect to (at least) the two approaches you outline depends on what your intention is - what behavior you want to provide users now and in the future.

This part of your question is unclear, to me: You say that existing uses of a single string should continue to work. But you don't say whether this intention is only temporary or general (ongoing).

And you even raise those two possibilities explicitly, asking for a recommendation of one of the other. But they represent different intentions. You need to decide what design (behavior for users) you want.

  • Your first approach declares the use of a single string as deprecated/obsolete, which indicates that you might well remove that possibility at some point, so users should start migrating to use only a list of strings.

  • Your second approach does not favor a list of strings over a single string, and it shows no indication that you might remove the use of a single string at some point.

Decide what your intention is and choose accordingly.

This kind of comes down to deciding whether your initial design of using a single string value was a design mistake. If you think that is something useful for users, so that it was not just a mistake and you want to continue to provide that possibility, then your second approach probably makes more sense. If it was a mistake then your first approach might make more sense.

With respect to the second possibility: allowing both single string and list of strings now and in the expected future: I'd suggest using the defcustom :type constructor choice, and allow both possibilities directly in the :type definition:

(defcustom foo "init-string"
  "..."
  :type '(choice
          (string :tag "Single string value")
          (repeat :tag "List of string values"
           string)))
6
  • You beat me with that solution;-). The only thing left to say is a word about a possible unifying use-case: (let ((foo (if (stringp foo) (list foo) foo))) ...)
    – Tobias
    Commented Nov 10, 2017 at 15:22
  • @Tobias: Yes, that's how the code would handle the option value. But that doesn't speak to how the option would be defined (in Lisp and by users).
    – Drew
    Commented Nov 10, 2017 at 15:25
  • Thanks for these thoughts and suggestions. I edited the question to give an example (e.g., search paths, for which there may be one or many). There is no inherent preference between a string or list: that depends on the user. Probably through lack of foresight or planning it was a mistake on my part :) Commented Nov 10, 2017 at 15:27
  • If there is no inherent preference for you (your design), so that you want to leave up to user preference, then use a choice defcustom. That's what choice is for.
    – Drew
    Commented Nov 10, 2017 at 15:34
  • @Drew: You wrote: "Yes, that's how the code would handle the option value. But that doesn't speak to how the option would be defined (in Lisp and by users)." I know that. It's just a nice add-on that either way you define the option the code changes are not so large. If one has many usages one could even define a macro (defmacro as-list (x) (if (listp x) x (list x))) and use (let ((foo (as-list foo))) ...).
    – Tobias
    Commented Nov 10, 2017 at 15:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.