6

Running the following in ielm in emacs -Q:

ELISP> (defun wh/expand-file-name (name &optional default-directory)
  (expand-file-name name default-directory))
wh/expand-file-name
ELISP> (expand-file-name "http://example.com" nil)
"/home/wilfred/http:/example.com"
ELISP> (wh/expand-file-name "http://example.com" nil)
#("/scp:http:/example.com" 1 4
  (tramp-default t))

Why do these two functions not return the same value?

Drew
  • 75,699
  • 9
  • 109
  • 225
Wilfred Hughes
  • 6,890
  • 2
  • 29
  • 59

1 Answers1

8

In short: dynamic binding.

It is maybe a little unfortunate that default-directory was used as the argument to expand-file-name.

Note the docstring:

(expand-file-name NAME &optional DEFAULT-DIRECTORY)

Convert filename NAME to absolute, and canonicalize it. Second arg DEFAULT-DIRECTORY is directory to start with if NAME is relative (does not start with slash or tilde); both the directory name and a directory’s file name are accepted. If DEFAULT-DIRECTORY is nil or missing, the current buffer’s value of ‘default-directory’ is used.

By dynamically binding a nil value for default-directory around the call to expand-file-name, that becomes both the argument passed by your function and the fall-back value which is used "If DEFAULT-DIRECTORY is nil or missing". That is the difference between the two variants, as normally the fall-back value would be a string.

n.b. C-hv default-directory

You would get the same result with:

(let ((default-directory nil))
  (expand-file-name "http://example.com" nil))

If you change the name of that argument in your function, this will stop happening. e.g.:

(defun wh/expand-file-name (name &optional defaultdir)
  (expand-file-name name defaultdir))

Note that expand-file-name itself is written in C and is not subject to this same behaviour, which is why it gets away with using that name as an argument, but you do not.

phils
  • 48,657
  • 3
  • 76
  • 115