Skip to content

Commit

Permalink
Started to write idioms section; perhaps halfway through.
Browse files Browse the repository at this point in the history
  • Loading branch information
chromatic committed Mar 5, 2010
1 parent c43fda4 commit ab1c8e5
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 7 deletions.
2 changes: 2 additions & 0 deletions sections/functions.pod
Expand Up @@ -75,6 +75,8 @@ You can, of course, pass multiple I<types> of arguments to a function:

=head2 Function Parameters

Z<function_parameters>

X<parameters>
X<functions; parameters>
X<@_>
Expand Down
231 changes: 224 additions & 7 deletions sections/idioms.pod
Expand Up @@ -2,16 +2,233 @@

Z<idioms>

=for author
Any language--programming or natural--develops I<idioms>, or common patterns of
expression. The earth revolves, but we speak of the sun rising or setting. We
talk of clever hacks and nasty hacks and slinging code.

As you learn Perl 5 more clearly, you will begin to see and understand common
idioms. They're not quite language features--you don't I<have> to use
them--and they're not quite large enough that you can encapsulate them away
behind functions and methods. Instead, they're mannerisms. They're ways of
writing Perl with a Perlish accent.

=head3 The Object as C<$self>

X<$self>
X<variables; $self>
X<objects; invocant>
X<methods; invocant>

Perl 5's object system (L<moose>) treats the invocant of a method as a mundane
parameter. The invocant of a class method (a string containing the name of the
class) is that method's first parameter. The invocant of an object or instance
method, the object itself, is that method's first parameter. You are free to
use or ignore it as you see fit.

Idiomatic Perl 5 uses C<$class> as the name of the class method and C<$self>
for the name of the object invocant. This is a convention not enforced by the
language itself, but it is a convention strong enough that useful extensions
such as C<MooseX::Method::Signatures> assume you will use C<$self> as the name
of the invocant by default.

=head3 Named Parameters

X<parameters; named>
X<arguments; named>

Without a module such as C<signatures> or C<MooseX::Multimethods>, Perl 5's
argument passing mechanism is simple: all arguments flatten into a single list
accessible through C<@_> (L<function_parameters>). While this simplicity is
occasionally too simple--named parameters can be very useful at times--it does
not preclude the use of idioms to provide named parameters.

The list context evaluation and assignment of C<@_> allows you to unpack named
parameters pairwise. Even though this function call is equivalent to passing a
comma-separated or C<qw//>-created list, arranging the arguments as if they
were true pairs of keys and values makes the caller-side look like the function
supports named parameters:

=begin programlisting

make_ice_cream_sundae(
whipped_cream => 1,
sprinkles => 1,
banana => 0,
ice_cream => 'mint chocolate chip',
);

=end programlisting

The callee side can unpack these parameters into a hash and treate the hash as
if it were the single argument:

=begin programlisting

sub make_ice_cream_sundae
{
B<my %args = @_;>

my $ice_cream = get_ice_cream( $args{ice_cream}) );
...
}

=end programlisting

=begin sidebar

I<Perl Best Practices> suggests passing a hash reference instead. This has one
benefit of performing hash construction checking on the caller side, where it's
most likely you'll make mistakes and another benefit of minimizing copying and
memory use. The former benefit is compelling, if somewhat less common in
practice.

=end sidebar

This technique works well with C<import()> (L<import>); you can process as many
parameters as you like before slurping the remainder into a hash:

=begin programlisting

sub import
{
B<my ($class, %args) = @_;>
my $calling_package = caller();

...
}

=end programlisting

Note how this idiom falls naturally out of list assignment; that makes this
idiom Perlish.

=head3 The Schwartzian Transform

Z<schwartzian_transform>

People new to Perl sometimes overlook the importance of lists and list
processing as a fundamental component of expression evaluationN<People
explaining its importance in this fashion do not help>. Put more simply, the
ability for Perl programmers to chain expressions which evaluate to
variable-length lists gives them countless ways to manipulate data effectively.

X<Schwartzian transform>
X<map; Schwartzian transform>
X<sort; Schwartzian transform>

The I<Schwartzian transform> is an elegant demonstration of that principle as
an idiom handily borrowed from the Lisp family of languages.

Suppose you have a Perl hash which associates the names of your co-workers with
their phone extensions:

$self
=begin programlisting

named parameters
- =>
- slurpy hash
my %extensions =
(
4 => 'Jerryd',
5 => 'Rudy',
6 => 'Juwan',
7 => 'Brandon',
10 => 'Joel',
21 => 'Marcus',
24 => 'Andre',
23 => 'Martell',
52 => 'Greg',
88 => 'Nic',
);

Schwartzian transform
- complex sorting
=end programlisting

Suppose you want to print a list of extensions and co-workers sorted by their
names, not their extensions. In other words, you need to sort a hash by its
keys. Sorting the values of the hash in string order is easy:

=begin programlisting

my @sorted_names = sort values %extensions;

=end programlisting

... but that loses the association of names with extensions. The beauty of the
Schwartzian transform is that it solves this problem almost trivially. All you
have to do is transform the data before and after sorting it to preserve the
necessary information. This is most obvious when explained in multiple steps.
First, convert the hash into a list of data structures which contain the vital
information in sortable fashion. In this case, converting the hash pairs into
two-element anonymous arrays will help:

=begin programlisting

my @pairs = map { [ $_, $extensions{$_} ] } keys %extensions;

=end programlisting

=begin sidebar

Reversing the hash I<in place> would work if no one had the same name. In this
case, that is no problem, but defensive coding anticipates data changes.

=end sidebar

C<sort> gets the list of anonymous arrays and can compare the second elements
(the names) with a stringwise comparison:

=begin programlisting

my @sorted_pairs = sort { $a->[1] cmp $b->[1] } @pairs;

=end programlisting

Given C<@sorted_pairs>, a second C<map> operation can convert the data
structure to a more usable form:

=begin programlisting

my @formatted_exts = map { "$_->[1], ext. $_->[0]" } @sorted_pairs;

=end programlisting

... and now you can print the whole thing:

=begin programlisting

say for @formatted_exts;

=end programlisting

Of course, this uses several temporary variables (with admittedly bad names).
It's a worthwhile technique and good to understand, but the real magic is in
the combination:

=begin programlisting

say for
map { " $_->[1], ext. $_->[0]" }
sort { $a->[1] cmp $b->[1] }
map { [ $_ => $extensions{$_} ] }
keys %extensions;

=end programlisting

Read the expression from right to left, in the order of evaluation. For each
key in the extensions hash, make a two-item anonymous array containing the key
and the value from the hash. Sort that list of anonymous arrays by their
second elements, the values from the hash. Create a nicely formatted string of
output from those sorted arrays.

The Schwartzian transform is this pipeline of C<map>-C<sort>-C<map> where you
transform a data structure into another form easier for sorting and then
transform it back into your preferred form for modification.

In this case the transformation is relatively simple. Consider the case where
calculating the right value to sort is expensive in time or memory, such as
calculating a cryptographic hash for a large file. In that case, the
Schwartzian transform is also useful because you can perform those expensive
operations once (in the rightmost C<map>), compare them repeatedly from a
de-facto cache in the C<sort>, and then remove them in the leftmost C<map>.

=for author

localization
- $/
Expand Down
2 changes: 2 additions & 0 deletions sections/modules.pod
Expand Up @@ -61,6 +61,8 @@ X<use>
X<keywords; use>
X<import()>

Z<import>

When you load a module with the C<use> keyword, Perl loads it from disk, then
calls its C<import()> method, passing any arguments you provided to the C<use>
keyword. This occurs at compilation time:
Expand Down

0 comments on commit ab1c8e5

Please sign in to comment.