Recursion is one of those computer science ideas that seems so difficult to understand before you get it, and then seems so easy after you understand it that you can't remember not understanding it.
A anonymous function is one of those computer science ideas that makes no sense until it makes sense, and then you understand the beauty (and the horror) of the Von Neumann architecture. (Long story short: computers don't care about the names of things. People do.)
When you combine those two ideas, you get things like the Y combinator, in which you jump through hoops to create an anonymous function which recurses to itself while remaining anonymous.
If you're the kind of pragmatic joker that Perl tends to attract, you might say to yourself "Wow, those Scheme hackers are crazy. Just do it in one step of Perl 5 like this:"
my $func;
$func = sub
{
my $factor = shift;
return $factor > 1
? $func->( $factor - 1 ) * $factor
: 1;
};
... which preserves the anonymity of the function and allows recursion at the cost of a memory leak. (Weakrefs fix this, but are you going to remember to do that?)
(At this point, Python fans will say "Just stick a name on that function. There's no reason not to name everything." They may have a point; I went through a phase when I was a kid where I used my parents's embossing label maker whenever I could. The problem with that is that the adhesive leaves a sticky residue on everything. No amount of enforced whitespace can fix that.)
Perl 5.16 fixes this situation.
If you use 5.016;
or use feature 'current_sub';
,
you enable the __SUB__
builtin, which is a reference to the
function currently executing.
Suppose you have a tree structure representing articles, something like:
my $tree =
[
{ state => 'READ', id => 1 },
{ state => 'UNREAD', id => 2 },
[
{ state => 'READ', id => 3 },
{ state => 'READ', id => 4 },
[
{ state => 'UNREAD', id => 5 },
{ state => 'READ', id => 6 },
],
],
];
Suppose you want to traverse this structure looking for articles in the
UNREAD
state. In Perl 5.16, you might write something like:
sub traverse
{
my ($root, $comparator) = @_;
my @items;
for my $element (@$root)
{
if (ref $element eq 'ARRAY')
{
push @items, __SUB__->( $element, $comparator );
}
else
{
push @items, $element if $comparator->( $element );
}
}
return @items;
}
... as a starting point. Sure, there's no need in the code as it
exists now to use __SUB__
instead of the hard-coded
traverse()
, but consider two pragmatic arguments for anonymous
recursive functions. First, the two-step code to make a lexical recursive
function (first, declare the lexical variable; in the next statement, use that
lexical binding to recurse) has the danger of memory leak and it looks
odd. The Y combinator code in Perl is worse. Clarity suffers.
Second, the drawback of using a named function is that that function is available in the namespace. If you write mostly object oriented code (as I do) with hlper functions (as I do), the fact that Perl borrowed Python's misfeature of exposing everything in a namespace as a method means that any named function may be exploitable as a method.
Anonymous functions ameliorate that danger.
Anonymous recursive functions (where recursion is appropriate) are a tool wielded with wisdom.
Anonymous recursive functions with trivial syntactic support (the implementation in the core is next to trivial) are a boon to Perl's pragmatism.
(If you're using an older version of Perl 5, see Sub::Current for a workalike.)
For completeness: I went through the trouble of unravelling the derivation of the Y combinator in Perl.
That will leak the $func coderef because it holds a reference to itself.
The recursive call seems likely to trigger an infinite loop... maybe $func->($factor - 1) would be better.
The consideration about the memory leak is very interesting, am I right to assume that it is worked around by the Y combinator?
Of course! I didn't want to show a Y combinator in Perl here because it's a little mind bending until it clicks and because the point is you don't need the Y combinator in Perl now.
Flavio: yes.
Oh, and chromatic: "a little"? :-)
But your code doesn't even work. See Flavio's comment.
You're right, thanks. I set this entry aside, then came back and hit publish before I finished editing.