Camelia

From Iterative to Functional Perl 6 Code

Suppose you want to search for all files with a certain name, but you're not sure about the extension -- it could be a .pod or .pm or .pl, and there are several possible paths too.

So, you generate all possible, and for each you check if they exist in the file system:

Note! This site is about Perl 6, the future version of Perl.
If you are looking for a solution for the current production version of Perl 5, please check out the Perl 5 tutorial.

use v6;

my @paths    = < /tmp /var/tmp >;
my $filename = 'temp123';
my @ext      = <pod pm pl>;
for @paths -> $p {
    for @ext -> $e {
        my $file = "$p/$filename.$e";
        say $file if $file.IO.e;
    }
}

Ok, nothing exciting so far. Now you want all existing files in an array. The usual approach would be to create an array outside the loops, and push to it inside the loop. But there's a more elegant solution in Perl 6:

use v6;

my @paths    = < /tmp /var/tmp >;
my $filename = 'temp123';
my @ext      = <pod pm pl>;
my @existing-files := gather for @paths -> $p {
    for @ext -> $e {
        my $file = "$p/$filename.$e";
        take $file if $file.IO.e;
    }
}

The gather statement collects all the values that take is called with. And more than that, it does the collection lazily. That is, the loops are only run if somebody accesses elements of @existing-files. So if you only ever need the first element, no further accesses to the file system are performed after the first one -- and you didn't even have to build that stopping into your code directly.

The laziness is also the reason for using the bind operator := on @existing-files; assignment is mostly eager, and ruins the effect of laziness.

I am fond of the functional programming style, and often want to get rid of explicit loops. Perl 6 makes this easy with so-called meta operators. The X operator builds the cross product of two or more lists, and you can combine it with the concatenation operator ~ to concatenate the elements into strings right away:

use v6;

my @paths    = < /tmp /var/tmp >;
my $filename = 'temp123';
my @ext      = <pod pm pl>;
my @existing-files := grep { .IO.e },
                  (@paths X~ '/' X~ $filename X~ '.' X~ @ext);

Or, if you want to do the concatenation of / and . to the filename directly:

my @existing-files := grep { .IO.e },
                  (@paths X~ "/$filename." X~ @ext);

As a final touch-up, Perl 6 provides a nice syntax for creating closures that only consist of method calls:

my @existing-files := grep *.IO.e,
                  (@paths X~ "/$filename." X~ @ext);

The method call on the Whatever Star * is transformed at compile time into a closure that calls a method of the same name on the argument you pass to it.

So now the code is free of temporary variables (for which you have to find a name, which always annoys me), and is shorter than the initialization leading up to it.


Moritz Lenz is a long-time Perl programmer, blogs about Perl 6 and is a core developer of the Rakudo Perl 6 Compiler.


The Perl 6 Tricks and Treats newsletter has been around for a while. If you are interested to get special notification when there is new content on this site, it is the best way to keep track:
Email:
Full name:
This is a newsletter temporarily running on my personal site (szabgab.com) using Mailman, till I implement an alternative system in Perl 6.
Moritz Lenz
Written by Moritz Lenz

Published on 2012-07-18



Comments

In the comments, please wrap your code snippets within <pre> </pre> tags and use spaces for indentation.
comments powered by Disqus

Perl 6 Tricks and Treats newsletter

Register to the free newsletter now, and get updates and news.
Email:
Name: