rename takes two scalars, not a list

Perl gets us used to the idea that we can throw a list at a function and it all works out. The first item in the list becomes the first parameter, the second list item becomes the next parameter, and so on. We expect these two calls to some_sub to act the same:

my @args = qw( one two );

some_sub( @args );
some_sub( $args[0], $args[1] );

Perl doesn’t have complicated lists (Python does). You expect it to collect all of the elements of all of the scalars and arrays to construct a single flat list with no additional structure (ignoring the reference hacks to create data structures).

But it doesn’t work for rename. When I try to compile the code with -c, I get an error if I use a single array in the argument list?

$ perl -c -e 'rename( @ARGV )' one two
Not enough arguments for rename at -e line 1, near "@ARGV )
-e had compilation errors.

A slice doesn’t work

$ perl -c -e 'rename( @ARGV[0,1] )'
Not enough arguments for rename at -e line 1, near "] )
-e had compilation errors.

WTF? rename needs two arguments and @ARGV has two items, right?

No, @ARGV has no items. I’ve only completed the compile-time phase. perl hasn’t filled in @ARGV yet, but it knows that rename need exactly two arguments (not one or three or 37). It checks for the right number of arguments at compile time.

Why is this function different? It has a prototype, which is some extra information functions carry around to tell perl how to parse code. It’s a compile-time effect for parsing. It’s not at all a way to enforce types like you’d expect from other languages. You can use the prototype built-in to check it. You can give prototype a string that is the function name. If the string starts with CORE::, prototype uses the built-in function:

$ perl  -le 'print prototype( "CORE::rename" )'

The prototype for rename shows $$. As perl parses this, it expects two things, each which will be thought of as scalars. That means this nonsense compiles:

$ perl -c -e 'rename( @ARGV, @ARGV )'
-e syntax OK

But, it won’t use the items in either of those arrays. They will be converted to the number of elements in each array. That’s unlikely to be what you or anyone wants. Worse, it’s not what anyone expects with Perl’s general argument list flattening.

If you aren’t thinking about this, you’ll probably miss that the rename document ion doesn’t say it can take a list:

$ perldoc -f rename
            Changes the name of a file; an existing file NEWNAME will be
            clobbered. Returns true for success, false otherwise.

It has an OLDNAME and NEWNAME parameter, but no other calling sequence. For instance, utime, which needs two scalars and a list, is documented to take a list:

$ perldoc -f utime
    utime LIST
            Changes the access and modification times on each file of a list
            of files. The first two elements of the list must be the NUMERIC
            access and modification times, in that order. Returns the number
            of files successfully changed.

It’s prototype is just @ (instead of $$@):

$ perl  -le 'print prototype( "CORE::utime" )'

But then substr does what you think utime should do:

$ perl  -le 'print prototype( "CORE::substr" )'

As with many things in Perl, trying to make it all fit a pattern in madness. Just accept what it is.

Further reading

Leave a comment


Leave a Reply

You must be logged in to post a comment.