Thursday, 8 May 2008

Renaming files in Common Lisp.

So I ran across this while going through my morning catchup routine and thought it could do with some clearing up.

The part of the post I'm actually interested is this.

When Haskell can compete on those types of problems, it'll be easier to induce people to learn it. (Same with CL, my fav language....)

Now, bonus points for proclaiming CL as being his favorite language, but minus 10 billion for continuing the meme that CL is somehow unsuited for writing[1] these kinds of simple software (for whatever definition of simple you have).

One of the examples given is "I have a bunch of files, and I want to rename them all according to some pattern." and as it so happens translate-pathname[2] makes this wonderfully simple.

(defun rename-files (from to)
(dolist (file (directory from))
(rename-file file (translate-pathname file from to))))

And thats it, 3 lines of code, or for our simple testing purposes

(defun show-rename-files (from to)
(dolist (file (directory from))
(format t "Renaming ~A to ~A~%" file
(translate-pathname file from to))))

(show-rename-files "/usr/share/pixmaps/*.xpm" "/usr/share/pixmaps/backup-*.xpm")

The funny thing is that this isn't secret knowledge but is pulled straight from the Hyperspec (see the examples).

[1]: Please note, I said 'writing' them, not 'creating a 2k binary' of them, please people CL /= Unix.
[2]: Granted, the behavior of translate-pathname isn't specified in detail by the spec but that doesn't mean we cannot use it.

This post brought to you by Lispworks 5.1, clisp 2.41 and SBCL 1.0.12


e40 said...

I added a link on reddit to your post.

Alan said...

Thanks for this post! I think it illustrates my concerns.

As a comparison, let's look at a perl example.

The first thing I notice is that translate-pathname's docs call out that using a wildcard within a string is implementation-defined. The portion of the source pathname that is copied into the output is implementation-defined. When reading the docs, and trying to figure out how to use it, that's a little scary. But I fired up clisp, and it seemed to work as naively expected, so that's good. Incidentally, I checked the clisp impnotes, and the info on what kind of wildcarding it supports or what it copies over was not listed.

In contrast, while Perl is defined by its implementation, there's only one. You never need to worry about whether your documentation will work with your implementation (at least for this use case).

As a side note, notice that Perl makes it easy to use its full regex functionality to call out the rename pattern. If you need to do that with CL, you have a much more involved example which does not use translate-pathname (and you have to rejigger the directory calls). Also note that the CL version can't rename files from one directory to another, but the perl can.

The next thing I notice is that the Perl example shows how you can bundle that little code snippet into a script you can execute easily from your shell. How do you do that with CL? Oh, it's implementation-defined....

None of this is impossible to overcome. But it doesn't come remedied out of the box. Part of the point of my original post was that the reason that Perl and Python get so many people coming in to try things out is that doing simple but useful tasks is made very easy. Tiny perl scripts or one-liners is a gateway drug: you have a problem, you google it, and you see a very short perl example on how to fix it. Copy that down to your system, run it, and your problem is gone. Next time you have a problem, you're likely to remember how easy Perl made it, you'll try it again. Eventually, you end up learning a lot about the language even though you didn't set out to learn the language in the first place.

That's where CL falls down. Lots of people use CL for short simple tasks like this... but those that do have usually built up a small library of their own to make it really easy to solve small problems. Things like handling script arguments or standard in, common directory operations, etc. With Perl and Python, that stuff comes with the implementation. If we could somehow get a fairly simple library like that made a defacto standard and bundled with a bunch of implementations (rather than requiring people to write it themselves), we'd go a long way towards helping address this.

pjb said...

Well, CL:RENAME-FILE is not so good an example, because it is specified to keep over the file type. So, if the implementation is conformant, there is no way to rename a unix file removing all the dots:

C/USER[144]> (rename-file "/tmp/mod030.pdf" "/tmp/MOMO")
#P"/tmp/MOMO.pdf" ;
#P"/tmp/mod030.pdf" ;
C/USER[145]> (rename-file "/tmp/mod130.pdf" "/tmp/MOMO.")
#P"/tmp/MOMO." ;
#P"/tmp/mod130.pdf" ;

You have to use implementation specific extensions such as: #+clisp(LINUX:rename "momo.pdf" "momo").

So now the question is not whether CL can do it, but whether you can do it with clisp, or sbcl, or...

Sean Ross said...

pjb: Since rename file does pathname merging if the destination pathname has an :unspecific pathname type then the type will not be defaulted, although it does seem than CLISP does not allow :unspecific as a type (which is nonconformant behaviour)