I can't help myself... I think Perl 6 is easier to read than Python... so I toook the best practices here and added Perl 6 translations instead.
Okay, so I'm not really trying to one-up Python (well, maybe just a little). I think Python, especially Python 3, is a very able language and use it for a several projects. I also appreciate the work of Raymond Hettinger and Jeff Paine in putting together these best practices and summarizing them, respectively.
However, I think Perl 6 has a truly ingenious syntax that has taken 15+ years to assemble from lessons learned from 20+ years of language development in Perl preceding it and hundreds of years of experience from other languages. Perl 6 unashamedly steals language features from many others. I sincerely hope that languages will start learning from Perl 6 and stealing back (actually, I'm certain some already have). Many of these language features have been very thoughtfully debated and revised to work toward managing the trade-offs between readability, conciseness, and optimizability.
In the meantime, I wrote this gist to share some of the beauty in Perl 6 syntax and the love I have for it, of which many engineers are sadly unaware.
Cheers,
Sterling
P.S. I tried to run several of these snippets, but I have not tested them all. I will happily fix any errors you find.
for i in xrange(6):
print i**2
for ^6 -> $i { say $i² }
^6
creates a range from 0 to 5 (it is a shorthand for 0..^6
where the ^
marks the end as exclusive at that end). Ranges in Perl 6 produce elements in batches as requested by the for loop. This allows us to use a value like ^6 as both a range of numbers, but in a way that can be both memory optimized and process optimized.
colors = ['red', 'green', 'blue', 'yellow']
for color in colors:
print color
my @colors = <red green blue yellow>;
for @colors -> $color {
say $color;
}
Also note that Perl 6 provides an optimized way of making a list of strings, saving you from typing all those unnecessary quotes and commas.
colors = ['red', 'green', 'blue', 'yellow']
for color in reversed(colors):
print color
for @colors.reverse -> $color {
say $color;
}
# OR reverse @colors if you prefer; TMTOWTDI
colors = ['red', 'green', 'blue', 'yellow']
for i, color in enumerate(colors):
print i, '--->', color
my @colors = <red green blue yellow>;
for @colors.kv -> $i, $color {
say "$i ---> $color";
}
The .kv
turns the list into an alternating list of indices and values. If you specify more than one parameter to the block that iterates the for-loop, the for-loop itereate N at a time, where N is the number of parameters.
names = ['raymond', 'rachel', 'matthew']
colors = ['red', 'green', 'blue', 'yellow']
for name, color in izip(names, colors):
print name, '--->', color
my @names = <raymond rachel matthew>;
my @colors = <red green blue yellow>;
for @names Z @colors -> ($name, $color) {
say "$name ---> $color";
}
The Z
operator creates a new list of "tuples" from the arguments, pairwise. The new list is constructed as the for loop executes so it is both memory efficient and processor efficient.
colors = ['red', 'green', 'blue', 'yellow']
# Forward sorted order
for color in sorted(colors):
print colors
# Backwards sorted order
for color in sorted(colors, reverse=True):
print colors
my @colors = <red green blue yellow>;
# Forward sorted order
.say for @colors.sort;
# Backwards sorted order
.say for @colors.sort.reverse;
Calling .reverse
is just as fast as going forward because it just creates an iterator that starts from the end rather than the beginning. There's no need to have a special sort reversal option.
colors = ['red', 'green', 'blue', 'yellow']
print sorted(colors, key=len)
my @colors = <red green blue yellow>;
say @colors.sort({.chars});
An invention of the Perl community, Randal Schwartz in particular, is the Schwartzian Transform. Which is a generalized solution to a common sorting problem: I want to sort based on a calculated value without having to calculate that value more than N times for a length N list. If you pass a function to the Perl 6 sort method that only accepts a single argument, the return value will be used to perform that calculation once for each element, sort the list, then return the sorted list.
For non-Perl 6 people, a couple details: First, any use of {}
means that a code block is enclosed and returned. Secondly, A code block with no parameters (i.e., has no ->
in front) automatically receives a single implied parameter named $_
. Thirdly, a method called with no variable in front of it (e.g., .chars
) causes that method to be called on the variable named $_
. And fourthly, the word "length" may have any number of meanings when applied to strings, so Perl 6 avoids it and prefers .chars
to count the number of characters (which generally aligns to what humans think of as "number of characters").
blocks = []
for block in iter(partial(f.read, 32), ''):
blocks.append(block)
sub iter(&f, $sentinal) {
gather while f() -> $v {
last if $v eq $sentinal;
take $value;
}
}
my @blocks = do for iter({$f.read(32)}, '') -> $block { $block }
There's no built-in short-hand for creating an iterator from an arbitrary function and a sentinel that I'm aware of, but the gather
/take
syntax can be used to generate arbitrary iterators, so making a function that does that is no challenge.
def find(seq, target):
for i, value in enumerate(seq):
if value == target:
break
else:
return -1
return i
sub find($seq, $target) {
my ($r) = gather for $seq.kv -> $i, $value {
take $i andthen last if $value == $target;
}
$r // -1;
}
So, is the else
the result of athe break
or not? I'd always have to look it up.
d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}
for k, v in d.iteritems():
print k, '--->', v
my %d = <matthew blue rachel green raymond red>;
for %d.kv -> $k, $v {
say "$k ---> $v";
}
Is it just me or does Python have an obscure method to call for every optimization? Why not make you syntax sleek and simple and optimize the typical case instead? (Kudos for those that deserve them: This kind of optimization is much of what Python 3 does.)
names = ['raymond', 'rachel', 'matthew']
colors = ['red', 'green', 'blue']
d = dict(izip(names, colors))
# {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}
For python 3: d = dict(zip(names, colors))
my @names = <raymond rachel matthew>;
my @colors = <red green blue>;
my %d = @names Z=> @colors;
Remember the Z
operator? If you want it to create something other than a list of tuples, you can just give it the operator you want to use to join the thing together. So, you can do a quick zip-sum of two lists with Z+
, for example. In this case, we want to create a list of pairs which naturally form a Hash (basically the same as what Python calls a dict).
d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}
for k in d:
print k
for k in d.keys():
if k.startswith('r'):
del d[k]
my %d = <matthew blue rachel green rayment red>;
for %d.keys -> $k {
say $k;
}
for %d.keys -> $k {
%d{$k}:delete if $k.starts-with('r');
}
Iterators are a wonderful thing. The regular syntax of Perl 6 basically employs them everywhere.
colors = ['red', 'green', 'red', 'blue', 'green', 'red']
d = {}
for color in colors:
d[color] = d.get(color, 0) + 1
# Slightly more modern but has several caveats, better for advanced users
# who understand the intricacies
d = collections.defaultdict(int)
for color in colors:
d[color] += 1
my $d = bag(<red green red blue green red>);
A Bag
in Perl 6 (or BagHash
if you need one you can update later since Bag
is immutable) is just a set that can count. No additional effort required.
names = ['raymond', 'rachel', 'matthew', 'roger',
'betty', 'melissa', 'judith', 'charlie']
d = collections.defaultdict(list)
for name in names:
key = len(name)
d[key].append(name)
my @names = <raymond rachel matthew roger
betty melissa judith charlie>
my %d = @names.classify({.chars});
"Grouping"? I think you mean classifying.
twitter_search('@obama', retweets=False, numtweets=20, popular=True)
twitter-search('@obama', :!retweets, :20numtweets, :popular);
Perl 6 adopted Ruby-style named arguments, so :!retweets
is identical to retweets => False
, :20numtweets
is identical to numtweets => 20
, and :popular
is identical to popular => True
.
p = 'Raymond', 'Hettinger', 0x30, '[email protected]'
# A common approach / habit from other languages
fname = p[0]
lname = p[1]
age = p[2]
email = p[3]
fname, lname, age, email = p
my @p = 'Raymond', 'Hettinger', 0x30, '[email protected]';
my ($fname, $lname, $age, $email) = @p;
def fibonacci(n):
x = 0
y = 1
for i in range(n):
print x
t = y
y = x + y
x = t
def fibonacci(n):
x, y = 0, 1
for i in range(n):
print x
x, y = y, x + y
sub fibonacci($n) {
my ($x, $y) = (0, 1);
for ^$n {
say $x;
($x, $y) = ($y, $x + $y);
}
}
Aren't we supposed to prefer xrange
? Though, seriously, you'd never want to write that in Perl 6. Instead, do this:
sub fibonacci($n) { for (0, 1, * + * ... *)[^$n] { .say } }
# NOTE: The "influence" function here is just an example function, what it does
# is not important. The important part is how to manage updating multiple
# variables at once.
x, y, dx, dy = (x + dx * t,
y + dy * t,
influence(m, x, y, dx, dy, partial='x'),
influence(m, x, y, dx, dy, partial='y'))
my ($x, $y, $dx, $dy) = (
$x + $dx * $t,
$y + $dy * $t,
influence($m, $x, $y, $dx, $dy, :partial<x>),
influence($m, $x, $y, $dx, $dy, :partial<y>)
);
names = ['raymond', 'rachel', 'matthew', 'roger',
'betty', 'melissa', 'judith', 'charlie']
print ', '.join(names)
my @names = <raymond rachel matthew roger
betty melissa judith charlie>;
say @names.join(', ');
# OR, if you prefer: join ', ', @names
names = ['raymond', 'rachel', 'matthew', 'roger',
'betty', 'melissa', 'judith', 'charlie']
del names[0]
# The below are signs you're using the wrong data structure
names.pop(0)
names.insert(0, 'mark')
names = collections.deque(['raymond', 'rachel', 'matthew', 'roger',
'betty', 'melissa', 'judith', 'charlie'])
# More efficient with collections.deque
del names[0]
names.popleft()
names.appendleft('mark')
my @names = <raymond rachel matthew roger
betty melissa judith charlie>;
@names[0]:delete;
@names.shift;
@names.unshift('mark');
@cache
def web_lookup(url):
return urllib.urlopen(url).read()
use experimental :cached;
sub web-lookup($url) is cached {
HTTP::UserAgent.new.get($url).content;
}
I have a fundamental problem with a generic cache decorator: what kind of cache you want is application specific, but the feature exists if you want it. The Perl community is somewhat divided on whether we want it or not, so it's experimental for now.
with open('data.txt') as f:
data = f.read()
my $f = 'data.txt'.IO.open;
LEAVE $f.close;
my $data = $f.read();
The LEAVE
phaser can be used to call any code that needs to finalize before leaving the current block.