tags:

views:

1079

answers:

13

What? Perl Beautiful? Elegant? He must be joking!

It's true, there's some ugly Perl out there. And by some, I mean lots. We've all seen it.

Well duh, it's symbol soup. Isn't it?

Yes there are symbols. Just like 'math' has 'symbols'. It's just that we programmers are more familiar with the standard mathematical symbols. We grew to accept the symbols from our mother languages, whether that be ASM, C, or Pascal. Perl just decided to have a few more.

Well, I think we should get rid of all the unnecessary symbols. Makes the code look better.

The language for doing so already exists. It's called Lisp. (and soon, perl 6.)

Okay, smart guy. Truth is, I can already invent my own symbols. They're called functions and methods. Besides, we don't want to reinvent APL.

Oh, fake alter ego, you are so funny! It's really true, Perl can be quite beautiful. It can be quite ugly, as well. With Perl, TIMTOWTDI.

So, what are your favorite elegant bits of Perl code?

+7  A: 

A favorite example of mine is Perl's implementation of a factorial calculator. In Perl 5, it looks like so:

use List::Util qw/reduce/;
sub factorial {
    reduce { $a * $b } 1 .. $_[0];
}

This returns false if the number is <= 1 or a string and a number if a number is passed in (rounding down if a fraction).

And looking forward to Perl 6, it looks like this:

sub factorial {
    [*] 1..$^x
}

And also ( from the blog in the link above ) you can even implement this as an operator:

sub postfix:<!>(Int $x) {
    [*] 1..($x || 1)
}

and then use it in your code like so:

my $fact5 = 5!;
Robert P
I believe there's a slightly longer way to implement the factorial function as "!" that can be appended to a number for the mathematical notation 12!, but I'm not on top of Perl 6 so I don't remember it.
Chris Lutz
Also, nitpick: Gives the incorrect value for 0, which should be 1.
Robert P
You could handle that with a simple if($x == 0) return 1; or even the ternary operator ($x == 0 ? 1 : [*] 1..$x). I don't know Perl 6, but as far as I know either of those should work.
Chris Lutz
perl 6 ternary operator is ?? !! instead of ? :, but yes.
Robert P
Using ?? and !! makes it look like your Perl code is going "WTF?" and it amuses me.
Chris Lutz
0! ?? does not compute?
popcnt
sub factorial { [*] 1..($^x || 1) }
Brad Gilbert
@Brad - That looks really nice, but without specifying that the argument is an Int, we run the risk of people finding that the factorial of "" is 1, which would raise a few WTF?s if I found it out.
Chris Lutz
I just love that `sum( 1..3 )` can be written `[+] 1..3`
Brad Gilbert
+6  A: 

If you have a comma separated list of flags, and want a lookup table for them, all you have to do is:

my %lookup = map { $_ => 1 } split /,/, $flags;

Now you can simply test for which flags you need like so:

if ( $lookup{FLAG} ) {
    print "Ayup, got that flag!";
}
Robert P
The map guarentees that each flag will always be 1.
Robert P
Ah, right. Sorry, I completely saw that there was a line of code before that, but didn't read it. Apologies.
Chris Lutz
A trivial modification for a program that takes command-line arguments is to change "split(...)" to just "@ARGV". That way, you can access each flag with %lookup{-e} for the "-e" flag. Any files from the command-line will be passed in, but I doubt your program will check for them, so it's okay.
Chris Lutz
A: 

Poorer typists like me who get cramps hitting the shift key too often and have an almost irrational fear of using a semicolon started writing our Perl code in python formatted files. :)

e.g.

>>> k = 5
>>> reduce(lambda i,j: i*j, range(1,k+1),1)
120
>>> k = 0
>>> reduce(lambda i,j: i*j, range(1,k+1),1)
1
popcnt
reduce { $a * $b } 1 .. $k, 1; uses one less shift than your Python example ;-P
draegtun
BTW... I didn't down vote u however it can't really be a surprise to you that your answer was voted down.
draegtun
I hate touching the shift key, too. That's why I don't *multiply* anything anymore. Thinking of workarounds, __rmult__, required the shift key for *4* underscores and 'rmult' in any elegant language still needed *parens*--so I just gave up coding.
Axeman
@Axeman - In Perl, you don't even need the parenthesis. ;P
Chris Lutz
@popcnt - Adding a conditional to check for 0! is trivial, and in Perl using if's postfix notation doesn't need the shift key at all (except for the $varname) - just do "return 0 if $var == 0;" and you've got it covered, with only 1 shift more than before.
Chris Lutz
@Chris Lutz: adding an extra check kinda nullifies the "elegance" somewhat then, huh? also, 0! == 1, so that is why I like pointing out the trickiness of it.
popcnt
I still like perl. with TMTOWTDI, python is one way. it took me a while to get started, but the poor typist sarcasm is unfortunately based in truth, and i do not have to emit or process the extra cognitive burden in perl syntax when i do my python.
popcnt
@Axeman: would the __rmult__ be equiv to postfix:<*> in latest perl?
popcnt
@draegtun: reduce is a python builtin, so I was cheating somewhat intentionally. add 4 shifts for this -- use List::Util qw/reduce/; :)
popcnt
@popcnt - A good suggestion was made in the comments for another post - instead of a check, do "[*] 1 .. ($x || 1)", which will use $x or 1 if $x is 0. Makes the check quite elegant.
Chris Lutz
@popcnt - Sorry about that if you were sincere. It's just you see enough of these "How do I deframbulate the zeppelin in Perl?" messages get answered "use Python." Eliminating shifts makes sense if you need to do it, but it hardly seemed a *general* concern about "Elegant Perl".
Axeman
Cont: I think it's a wash anyway. Perl can quote like q// instead of Shift+' and you don't always need Shift+9...Shift+10 (aka '(...)'), but just like Python, it makes the code more readable.
Axeman
@popcnt - I voted down because, even though I understood you were joking, it wasn't appropriate, and it came off as a little asinine. I thought it was funny, but I also thought it was annoying. However, my -1 is to old to change now, so I can't undo it. Besides, it's community wiki, so it's okay.
Chris Lutz
LOL... don't get me started on those """heavy shifting Python Docstrings now""" ;-)
draegtun
@popcnt: Thats only 2 shifts! But yes I cheated a bit as well by intentionally keeping "use List::Util qw/reduce/;" out ;-) BTW... reduce is no longer a builtin in Python 3 (part of functools library).
draegtun
The real question isn't how many shifts you must make, but how many keystrokes. :)
Robert P
@popcnt: Actually while I don't recommend it u can even get rid of those two shifts if they're a PITA... use List'Utils qw/reduce/; ;-)
draegtun
@RobertP: Yep. So to recap the Perl "reduce" has less keystrokes, uses less shifts, its DRY, easier to read and even far more elegant (IMHO). Slam-dunk ;-)
draegtun
@Chris Lutz: nice on handling the 0! case. If it hasn't already been, I think you should update the perl factorial code examples that started this thread
popcnt
why are the python people trying to golf against perl? If you can golf with python, can you write poetry too ( http://www.perlmonks.org/?node_id=237465) , and run some of the largest streaming media websites on the internet?
singingfish
+21  A: 

Perl facilitates the use of lists/hashes to implement named parameters, which I consider very elegant and a tremendous aid to self-documenting code.

my $result = $obj->method(
    flux_capacitance       => 23,
    general_state          => 'confusion',
    attitude_flags         => ATTITUDE_PLEASANT | ATTITUDE_HELPFUL,
);
chaos
I gotta say, this is one of my favorite features too. Cool fact: this is coming to .NET too. :)
Robert P
+13  A: 

Have a list of files the user wants your program to process? Don't want to accidentally process a program, folder, or nonexistent file? Try this:

@files = grep { -T } @files;

And, like magic, you've weeded out all the inappropriate entries. Don't want to ignore them silently? Add this line before the last one:

warn "Not a file: $_" foreach grep { !-T } @files;

Prints a nice warning message for every file that it can't process to standard error. The same thing without using grep would look like this:

my @good;
foreach(@files) {
  if(-T) {
    push @good, $_;
  } else {
    warn "Not a file: $_";
  }
}

grep (and map) can be used to make code shorter while still keeping it very readable.

Chris Lutz
+1  A: 

Adding to the love of map and grep, we can write a simple command-line parser.

my %opts = map { $_ => 1 } grep { /^-/ } @ARGV;

If we want, we can set each flag to it's index in @ARGV:

my %opts = map { $ARGV[$_] => $_ } grep { $ARGV[$_] =~ /^-/ } 0 .. $#ARGV;

That way, if a flag has an argument, we can get the argument like this:

if( defined( $opts{-e} ) ) {
  my $arg = $ARGV[ $opts{-e} ];
  # do -e stuff for $arg
}

Of course, some people will cry that we're reinventing the wheel and we should use getopt or some variant thereof, but honestly, this was a fairly easy wheel to reinvent. Plus, I don't like getopt.

If you don't like how long some of those lines are, you can always use intermediate variables or just convenient line breaks (hey, Python fanatics? You hear that? We can put one line of code across two lines and it still works!) to make it look better:

my %opts = map  { $ARGV[$_] => $_   }
           grep { $ARGV[$_] =~ /^-/ } 0 .. $#ARGV;
Chris Lutz
The Python boyz iz silly.
Axeman
I use map and grep as much as I can (instead of foreach loops).My gripe with them is that they don't name the $_ argument, which is bad if the code needs to refer to multiple levels of $_.
reinierpost
+5  A: 
Ali A
Unfortunately, the WMD editor broke the code. There's another variation that works at: http://www.perlmonks.org/index.pl?node_id=45213
spoulson
I agree that it is a masterpiece, but I'll appreciate it more when I can actually understand it.
Chris Lutz
Chris Lutz: that's the most self documenting code that it's possible to write. http://search.cpan.org/perldoc?Acme::EyeDrops
singingfish
Nice. I need this module.
Chris Lutz
Eh, it's neat, but not really elegant I think.
Robert P
I replaced < with < and > with >. Hopefully that will fix the errors.
Brad Gilbert
@Robert P: it's elegent, just in an incredibly twisted way.
singingfish
+16  A: 

I'm surprised no one mentioned the Schwartzian Transform.

my @sorted =
  map  { $_->[0] }
  sort { $a->[1] <=> $b->[1] }
  map  { [ $_, expensive_func($_) ] }
@elements;

And in the absence of a slurp operator,

my $file = do { local $/; readline $fh };
Mark Canlas
That (the S.T.) is so freakin' gorgeous.
chaos
And made obsolete by Perl 6 where it's a builtin!
Chris Dolan
It only fixes the fact that perl sort doesn't have a key function that can be cached, just a compare function. Python's new key argument to sort does just that. The ST is beautiful regardless, but people coming from other language will just think that Perl sort is broken for needing such a weird incantation for such a common operation.
Mathieu Longtin
That's actually called `decorate-sort-undecorate`, and originated in Lisp.
Pedro Silva
+8  A: 

Three-line classes with constructors, getter/setters and type validation:

{
    package Point;
    use Moose;

    has ['x', 'y'] => (isa => 'Num', is => 'rw');
}

package main;
my $point = Point->new( x => '8', y => '9' );

$point->x(25);
brunov
Moose really makes Perl the ideal OOP platform (IMHO).
draegtun
BTW... u can also write it like... has ['x', 'y'] => (isa => 'Num', is => 'rw');
draegtun
Thanks draegtun, your modifications make it sexier ;)
brunov
+7  A: 

The "or die" construct:

open my $fh, "<", $filename
    or die "could not open $filename: $!";

The use of qr// to create grammars:

#!/usr/local/ActivePerl-5.10/bin/perl

use strict;
use warnings;
use feature ':5.10';

my $non_zero         = qr{[1-9]};
my $zero             = qr{0};
my $decimal          = qr{[.]};
my $digit            = qr{$non_zero+ | $zero}x;
my $non_zero_natural = qr{$non_zero+ $digit*}x;
my $natural          = qr{$non_zero_natural | $zero}x;
my $integer          = qr{-? $non_zero_natural | $zero}x;
my $real             = qr{$integer (?: $decimal $digit)?}x;

my %number_types = (
    natural => qr/^$natural$/,
    integer => qr/^$integer$/,
    real    => qr/^$real$/
);

for my $n (0, 3.14, -5, 300, "4ever", "-0", "1.2.3") {
    my @types = grep { $n =~ $number_types{$_} } keys %number_types;
    if (@types) {
     say "$n is of type", @types == 1 ? " ": "s ", "@types";
    } else {
     say "$n is not a number";
    }
}

Anonymous subroutines used to factor out duplicate code:

my $body = sub {
    #some amount of work
};

$body->();
$body->() while $continue;

instead of

#some amount of work
while ($continue) {
    #some amount of work again
}

Hash based dispatch tables:

my %dispatch = (
    foo => \&foo,
    bar => \&bar,
    baz => \&baz
);

while (my $name = iterator()) {
    die "$name not implemented" unless exists $dispatch{$name};
    $dispatch{$name}->();
}

instead of

while (my $name = iterator()) {
    if ($name eq "foo") {
        foo();
    } elsif ($name eq "bar") {
        bar();
    } elsif ($name eq "baz") {
        baz();
    } else {
        die "$name not implemented";
    }
}
Chas. Owens
+19  A: 

My favourite pieces of elegant Perl code aren't necessarily elegant at all. They're meta-elegant, and allow you to get rid of all those bad habits that many Perl developers have slipped into. It would take me hours or days to show them all in the detail they deserve, but as a short list they include:

  • autobox, which turns Perl's primitives into first-class objects.
  • autodie, which causes built-ins to throw exceptions on failure (removing most needs for the or die... construct). See also my autodie blog and video).
  • Moose, which provide an elegant, extensible, and correct way of writing classes in Perl.
  • MooseX::Declare, which provides syntaxic aweseomeness when using Moose.
  • Perl::Critic, your personal, automatic, extensible and knowledgeable code reviewer. See also this Perl-tip.
  • Devel::NYTProf, which provides me the most detailed and usable profiling information I've seen in any programming language. See also Tim Bunce's Blog.
  • PAR, the Perl Archiver, for bundling distributions and even turning whole programs into stand-alone executable files. See also this Perl-tip.
  • Perl 5.10, which provides some stunning regexp improvements, smart-match, the switch statement, defined-or, and state variables.
  • Padre, the only Perl editor that integrates the best bits of the above, is cross-platform, and is completely free and open source.

If you're too lazy to follow links, I recently did a talk at Linux.conf.au about most of the above. If you missed it, there's a video of it on-line (ogg theora). If you're too lazy to watch videos, I'm doing a greatly expanded version of the talk as a tutorial at OSCON this year (entitled doing Perl right).

All the best,

Paul

pjf
+2  A: 

I absolutely love Black Perl (link to version rewritten to compile under Perl 5). It compiles, but as far as I can tell it doesn't actually do anything.

That's what you get for a language written by a linguist from a pragmatic perspective rather than from a theoretical perspective.

Moving on from that, you can think about the Perl that people complain about as pidgin Perl (perfectly useful, but not expressive, and beware of trying to express anything complex in it), and the stuff that @pjf is talking about as "proper" Perl, the language of Shakespeare, Hemingway, Hume and so on. [edit: err, though easier to read than Hume and less dated than Shakespeare.] [re-edit and hopefully less alcoholic than Hemingway]

singingfish
It's spelled "Hemingway".
AmbroseChapel
+1  A: 

This file parsing mechanism is compact and easy to customize (skip blank lines, skip lines starting with X, etc..).

open(H_CONFIG, "< $file_name") or die("Error opening file: $file_name! ($!)");
while (<H_CONFIG>)
{
   chomp;         # remove the trailing newline
   next if $_ =~ /^\s*$/; # skip lines that are blank
   next if $_ =~ /^\s*$/; # skip lines starting with comments
   # do something with the line
}

I use this type of construct in diverse build situations - where I need to either pre or post process payload files (S-records, etc..) or C-files or gather directory information for a 'smart build'.

dls