December 2010 Archives

Obfuscation: Comparing the size of two arrays

~~@x ~~ ~~@y

is true if @x and @y have the same number of elements.

This rather elegant obfuscation uses the smart match operator as well as double bitwise negation.

One-liner to count the number of lines in a file

There is a cute Perl one-liner to count the number of lines in a file:

perl -nE'}{say$.' foo.txt

Let's see how perl parses this one-liner:

$ perl -MO=Deparse -nE'}{say$.' foo.txt 
BEGIN {
    $^H{'feature_unicode'} = q(1);
    $^H{'feature_say'} = q(1);
    $^H{'feature_state'} = q(1);
    $^H{'feature_switch'} = q(1);
}
LINE: while (defined($_ = <ARGV>)) {
    ();
}
{
    say $.;
}
-e syntax OK

So -E turns on the special features (we only use say here) and -n provides the boilerplate code. As for $., perldoc perlvar says that it is the current line number for the last filehandle accessed.

vim: add a 'use' statement without moving the cursor

You're writing Perl code in vim and have just typed a package name - maybe you want to create an object of this class:

some_statement;
my $o = Some::Class->new;
do_something_with($o);

You obviously need to write use Some::Class at the top. So you either move the cursor near the top and add the line, then jump to the previous line number, or maybe you split the window, move to the new viewport, make the change, then close that viewport.

There is a better way. Here is a vim function for adding a use statement to the top of your file. I prefer it to go below my last existing use statement, except for special cases like use strict, use constant etc. If there isn't any such regular use statement, I want the new use statement to appear below the ABSTRACT (a Dist::Zilla-related comment) or below the package statement. Failing that, I want the use statement to appear below any existing use statement. If there isn't one of those either, below the shebang line. If we don't even have that, just put it at the top of the file.

Your aesthetic requirements may vary, so change the function accordingly.

This is my first real Vim script function that I've written, so excuse the 'baby Vim'.

" perl: add 'use' statement
"
" make sure you have
"   setlocal iskeyword=48-57,_,A-Z,a-z,:
" so colons are recognized as part of a keyword

function! PerlAddUseStatement()
    let s:package = input('Package? ', expand('<cword>'))
    " skip if that use statement already exists
    if (search('^use\s\+'.s:package, 'bnw') == 0)
        " below the last use statement, except for some special cases
        let s:line = search('^use\s\+\(constant\|strict\|warnings\|parent\|base\|5\)\@!','bnw')
        " otherwise, below the ABSTRACT (see Dist::Zilla)
        if (s:line == 0)
            let s:line = search('^# ABSTRACT','bnw')
        endif
        " otherwise, below the package statement
        if (s:line == 0)
            let s:line = search('^package\s\+','bnw')
        endif
        " if there isn't a package statement either, put it below
        " the last use statement, no matter what it is
        if (s:line == 0)
            let s:line = search('^use\s\+','bnw')
        endif
        " if there are no package or use statements, it might be a
        " script; put it below the shebang line
        if (s:line == 0)
            let s:line = search('^#!','bnw')
        endif
        " if s:line still is 0, it just goes at the top
        call append(s:line, 'use ' . s:package . ';')
    endif
endfunction

map ,us :<C-u>call PerlAddUseStatement()<CR>

Scalar context gotchas

On Twitter, Curtis Poe (@OvidPerl) posted some interesting and unintuitive Perl code; I've slightly reformatted it and changed some values for the sake of the following discussion.

use Data::Dumper;
sub boo { 4,5,6 }
my @x = ( boo() || 5,8,7);
print Dumper \@x;

What do you think this prints?

Let's look at some simpler examples of code:

$ perl -le'@x = (4,5,6,7,8); $y = @x; print $y'
5

An array like @x, in scalar context, evaluates to the number of elements in that array. In this case, @x contains five elements.

$ perl -le'$y = (4,5,6,7,8); print $y'
8

A list, as opposed to an array, returns the last element in scalar context - in this case, 8.

Let's see how perl parses this line. We can use the B::Deparse module with the -p option, which adds extra parentheses to make the structure clearer.

$ perl -MO=Deparse,-p -le'$y = (4,5,6,7,8); print $y'
BEGIN { $/ = "\n"; $\ = "\n"; }
($y = ('???', '???', '???', '???', 8));
print($y);
-e syntax OK

As you can see, the unused constant values have been optimized away.

perldoc perldata says this about arrays and lists in scalar context:

If you evaluate an array in scalar context, it returns the length of the array. (Note that this is not true of lists, which return the last value, like the C comma operator, nor of built-in functions, which return whatever they feel like returning.)

And finally:

$ perl -le'$y = (4..8); print $y'

This prints an empty line. You might have thought that this is the same as the line with the list above, but the range operator is somewhat different. In list context, it does indeed return a list of values counting from the left value to the right value. However, in scalar context, the range operator behaves like a flip-flop, starting out with a false value, which stringifies to an empty string.

perldoc perlop says this about the range operator:

In scalar context, ".." returns a boolean value. The operator is bistable, like a flip-flop, and emulates the line-range (comma) operator of sed, awk, and various editors. Each ".." operator maintains its own boolean state, even across calls to a subroutine that contains it. It is false as long as its left operand is false. Once the left operand is true, the range operator stays true until the right operand is true, AFTER which the range operator becomes false again.

After this discussion, you probably know what the program at the beginning of this post prints. Let's look at how perl parses it:

$ perl -MO=Deparse,-p test.pl 
use Data::Dumper;
sub boo {
    (4, 5, 6);
}
(my(@x) = ((boo() || 5), 8, 7));
print(Dumper((\@x)));
test.pl syntax OK

If we run the program, it prints:

$ perl test.pl 
$VAR1 = [
          6,
          8,
          7
        ];

The expression (boo() || 5) is the same as ((4, 5, 6) || 5) and since a list returns the last value in scalar context, this is the same as (6 || 5), which is 6.

Install dependencies of a Dist::Zilla-based distribution

For a distribution that is built using Dist::Zilla, it is very easy to install all the dependencies:

dzil listdeps | cpanm

dzil listdeps will almost build the distribution so it can determine the prerequisites and then list them each on one line. This list can be passed directly to cpanm.

Using dzil to install with cpanm

If you are creating a distribution using Dist::Zilla, you can install it using "dzil install". However, that will use the standard CPAN shell.

To make this work with cpanm, use:

dzil install --install-command "cpanm ."

Now if only Dist::Zilla allowed aliases like git did... I'd like to have something like this in ~/.dzil/config.ini:

[alias]
    instm = install --install-command "cpanm ."

About hanekomu

user-pic