3

This may be a more general programming question that just for Emacs Lisp, but I'm wondering when a function should signal an error vs. return nil.

Raising an error is "failing loudly", which is usually a good thing for debugging, etc. But for a function like file-name-directory, which I often use together with buffer-file-name, it often seems unnecessary that I should need to check that buffer-file-name is non-nil so that file-name-directory does not signal an error. In this case, it should be reasonable that file-name-directory of nil is nil.

Drew
  • 75,699
  • 9
  • 109
  • 225
Tianxiang Xiong
  • 3,848
  • 16
  • 27
  • Instead of checking (LBYL), you could use an EAFP model instead. Although EAFP is unusual in Elisp becuase it usually requires more code, it definitely has some merits. – PythonNut Jan 30 '16 at 19:29

2 Answers2

3

If you think that is a reasonable behavior for file-name-directory then consider filing an enhancement request: M-x report-emacs-bug.

On the other hand, imagine that most of the cases where file-name-directory is called, it is passed a value that is beyond the control of the immediate code. That is, file-name-directory can be called in any context, and it could be supposed that in most contexts an argument value of nil would indeed represent an error and should be raised as such.

It's always a programmer choice - a judgment of which behavior is most useful. Presumably, the Emacs developers who designed file-name-directory figured that a non-string argument should in general raise an error.

Remember too that it is trivial for code that calls file-name-directory to control the behavior, so that it is different in the case of a nil (or other non-string) argument. It could be as trivial as this:

(defun my-file-name-directory (file)
  "`file-name-directory', except return `nil' for `nil' argument."
  (and file  (file-name-directory file)))

Or it could handle the wrong-type-argument error anyway you like, including special-casing nil to just return nil (as in the above code). To do that, just wrap the call in condition-case and handle the error that would normally be raised anyway you like. This code does exactly the same as the above code, but you can see that you could handle other wrong types individually, by testing them (numberp, arrayp, symbolp, etc.)

(defun my-file-name-directory (file)
  "`file-name-directory', except return `nil' for `nil' argument."
  (condition-case err
      (file-name-directory file)
    (wrong-type-argument (and file  (error "%s" (error-message-string err))))))
Drew
  • 75,699
  • 9
  • 109
  • 225
  • The usual idiom is `(ignore-errors (file-name-directory file))`. – jch Jan 31 '16 at 02:57
  • 1
    @jch: No. Not if you want to *handle* specific errors, which is what I covered here. If all you want is to avoid raising an error then in this case all you need is `(and file (file-name-directory-file))`, as mentioned above. (And even just to ignore all errors you cannot use `ignore-errors` in older Emacs versions that do not provide it.) – Drew Jan 31 '16 at 03:09
1

This is not a direct answer to your question, but for the special use case you mention, a different method might be to define

(defmacro maybe (fun val)
    "Evaluate VAL and if the result is non-nil, run FUN on the value.
Otherwise, return nil."
  (let ((tmp (gensym)))
    `(let ((,tmp ,val)) (if ,tmp (,fun ,val) nil))))

and to call this using (maybe file-name-directory (buffer-file-name)).

(Very loosely inspired by the Maybe monad of haskell.)

Harald Hanche-Olsen
  • 2,401
  • 12
  • 15