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.
~~@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.
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.
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>
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.
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
.
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 ."