![]() |
|
go ahead... be a heretic | |
PerlMonks |
Creating Nested Functionsby ikegami (Patriarch) |
on Jul 10, 2008 at 01:42 UTC ( [id://696592]=perltutorial: print w/replies, xml ) | Need Help?? |
Some BackgroundAs you probably know, Perl will successfully compile nested named subroutines, but they probably won't behave as intended. Perl even tries to warn you:
The fix is obviously to use an anonymous sub instead of a named one.
However, that fails spectacularly when recursion is introduced. Problem 1 — Referencing the Wrong VariableThis problem is almost always easy to spot if use strict; is in effect.
Remember that a my only makes the declared symbol available in statements following the one containing the my. This can be fixed trivially by splitting the assignment into two statements.
Problem 2 — Memory LeakThere's a subtle lesson we should have learned from the first problem: If the sub references the lexical (by capturing it), and that same lexical references the sub, then it's a cyclic structure that cannot be freed by Perl's garbage collecting mechanism.
$var is not being freed because the anonymous sub is not being freed.
Let's illustrate: ################################################ ## Before outer exits ## &outer | +=============+ v --+----->[ Reference ] +==============+ / | +=============+ [ outer's pad ] / | [ refcount: 2 ] +=============+ +==============+ / | [ pointer: ------>[ Object ] [ $var: ------- | +=============+ +=============+ [ $helper: ------- | [ refcount: 1 ] +==============+ \ | +=============+ \ | \ | +=============+ -----+-->[ Reference ] | | +=============+ | | [ refcount: 2 ] +=============+ +==============+ | | [ pointer: ------>[ Helper Sub ] [ helper's pad ] | | +=============+ +=============+ +==============+ | | [ refcount: 1 ] [ $var: ------------+ | [ pad: -----+ [ $helper: ---------------+ +=============+ | +==============+ | ^ | | | +-----------------------------------------------------------+ ################################################ ## After outer exits ## +=============+ +----->[ Reference ] | +=============+ (outer still | [ refcount: 1 ] +=============+ exists, but | [ pointer: ------>[ Object ] it's not | +=============+ +=============+ referencing | [ refcount: 1 ] anything in | +=============+ this graph ) | | +=============+ | +-->[ Reference ] | | +=============+ | | [ refcount: 1 ] +=============+ +==============+ | | [ pointer: ------>[ Helper Sub ] [ helper's pad ] | | +=============+ +=============+ +==============+ | | [ refcount: 1 ] [ $var: ------------+ | [ pad: -----+ [ $helper: ---------------+ +=============+ | +==============+ | ^ | | | +-----------------------------------------------------------+ Nothing has a refcount of zero, so nothing can be freed. Solution — Dynamic ScopingThe solution to both problems is the same: Don't use a lexical variable.
Package variables aren't captured, so &helper's reference count isn't affected by the call in the inner function. ################################################ ## Before outer exits ## &outer | v +=================+ [ outer's pad ] +=================+ +=============+ [ $var: --------------+--->[ Reference ] +=================+ | +=============+ | [ refcount: 2 ] +=============+ | [ pointer: ------>[ Object ] | +=============+ +=============+ +=================+ | [ refcount: 1 ] [ *helper{SCALAR} ] | +=============+ +=================+ | +=============+ [ pointer: --------------->[ Reference ] +=================+ | +=============+ | [ refcount: 1 ] +=============+ | [ pointer: ------>[ Helper Sub ] +=================+ | +=============+ +=============+ [ helper's pad ] | [ refcount: 1 ] +=================+ | [ pad: -----+ [ $var: ===-----------+ +=============+ | +=================+ | ^ | | | +--------------------------------------------------------+ ################################################ ## After outer exits ## and local restores ## *helper{SCALAR} ## +=============+ +--->[ Reference ] | +=============+ (outer still | [ refcount: 1 ] +=============+ exists, but | [ pointer: ------>[ Object ] it's not | +=============+ +=============+ referencing | [ refcount: 1 ] anything in | +=============+ this graph ) | | +=============+ | [ Reference ] | +=============+ | [ refcount: 0 ] +=============+ +=================+ | [ pointer: ------>[ Sub ] [ helper's pad ] | +=============+ +=============+ +=================+ | [ refcount: 1 ] [ $var: --------------+ [ pad: -----+ +=================+ +=============+ | ^ | | | +--------------------------------------------------------+ There is no cycle, so everything will be freed in turn, starting with the reference with a refcount of zero. Alternative SolutionsI won't delve into these, so feel free to provide links which discuss these in more details. For each alternative solution, I'll post the equivalent to the solution I've already presented.
Y Combinatorambrus pointed out that Y Combinator can also achieve this goal. This is the most worthwhile alternative.
While it's a great tool to completely anonymize a recursive function, and while it might even be required in functional languages, it adds overhead in a situation where reducing overhead is important, and I think it's unnecessarily complex to solve the problem of nesting functions in Perl. Weak ReferenceOne could weaken the reference to the inner function using Scalar::Util::weaken.
However, the Weak Reference solution is much more complex than the Dynamic Scoping solution and has no advantage that I can see. SummaryUsing local *helper = sub {}; over my $helper = sub {}; not only provides a cleaner calling syntax, it can be used for recursive functions without accidentally referencing the wrong variable or causing a memory leak.
Back to
Tutorials
|
|