Subject: Re: merge-pathnames bug?

From: Joerg Hoehle <[email-masked]>
Date: Mon, 15 Feb 1999 14:53:35 +0100 (MET)

Hello,

Dorai Sitaram write:

> (setq x (pathname "../j.lsp"))
> (setq xdir (make-pathname :directory (pathname-directory x)))
> (setq y (pathname "a/b/c.lsp"))
> (merge-pathnames y xdir)

Unrelated to your bug-report, I'd like to say that the above is not a
portable way to extract the relevant information from one pathname in
order to build another one.  A pathname to a directory in the file
system may have more than PATHNAME-DIRECTORY.  There may be a DEVICE
or even a HOST.  The directory where CL was invoked may reside on a
different device, or *DEFAULT-PATHNAME-DEFAULTS* could cause the above
not to work as intended by you.

Therefore, I tend to use either MAKE-PATHNAME with :DEFAULTS key or
MERGE-PATHNAMES or a combination thereof, always supplying a pathname
as complete as possible as default, since there is no COPY-PATHNAME.
(COPY-PATHNAME ~= (MAKE-PATHNAME :DEFAULTS ...))

As you can't (SETF (PATHNAME-NAME/TYPE xdir) NIL), you'll have to
resort to the ugly
(setq xdir
    (make-pathname :host (pathname-host x)
	:device (pathname-device x)
	:directory (pathname-directory x)))
Sadly, you can't use
(make-pathname :name nil :type nil :defaults x) since the nil
components will be filled in by the defaulting behaviour.  You can't
supply "" either as you'd get strange and/or unportable results.

This would get you a good "directory" pathname, but no solution to
your problem yet.

The above returns:
> #P"a/b/c.lsp"
expected:
> #P"../a/b/c.lsp"

Well, it looks indeed like a bug.

CLHS says (while CLtL2 doesn't contain such a clause!):
--------
Pathname merging treats a relative directory specially. If
(pathname-directory pathname) is a list whose car is :relative, and
(pathname-directory default-pathname) is a list, then the merged
directory is the value of

 (append (pathname-directory default-pathname)
         (cdr  ;remove :relative from the front
           (pathname-directory pathname)))
--------
whereas CLISP does this only if default-pathname's directory is
absolute, not just a list.

Your question might be a candidate for a CLISP mini FAQ :-)

src/PATHNAME.D says:
# Roger Kehr <[email-masked]> asks why in CLISP
[Hi Roger!]
#
# (merge-pathnames (make-pathname :directory '(:relative "x"))
#                  (make-pathname :directory '(:relative "y")))
# => #"x/"
#
# where he expects to get #"y/x/".
#
# There are two reasons for this behaviour:
#
# 1. An informal one: I found the latter behaviour confusing and changed
#    CLISP to do it the former way. It seems to work better this way.
#
# 2. A formal one: MERGE-PATHNAMES is used to specify default components
#    for pathnames, so there is some analogy between (MERGE-PATHNAMES a b)
#    and (or a b). Obviously putting in the same default a second time
#    should do the same as putting it in once:
#
#      (or a b b) is the same as (or a b), so
#
#      (MERGE-PATHNAMES (MERGE-PATHNAMES a b) b) should be the same as
#      (MERGE-PATHNAMES a b).
#
#    (This question actually matters because in Common Lisp there is
#    no distinction between "pathnames with defaults merged-in" and
#    "pathnames with defaults not yet applied". For example, you don't
#    know whether COMPILE-FILE will merge in some defaults.)
#
#    Now, (MERGE-PATHNAMES (MERGE-PATHNAMES '#"x/" '#"y/") '#"y/")
#    and  (MERGE-PATHNAMES '#"x/" '#"y/")
#    are equal in CLISP's implementation, but not in implementations
#    that strictly follow the Common Lisp spec. In fact, the above
#    twice-default = once-default rule holds for all pathnames in CLISP.

As CLISP is heading towards stricter ANSI-CL conformance, I'd say that
there's some chance that the current 7 years old behaviour is now
changed to conform by the ANSI-CL letter.

This doesn't seem to be documented in _impnotes.txt.

I'd vote for the change too.

Regards,
	Jorg Hohle.
Maintainer of Amiga-CLISP