http://qs1969.pair.com?node_id=384038

The try/catch example in the Prototype section of Programming Perl often causes confusion among programmers who are trying to understand prototypes.
sub try (&$) { my($try, $catch) = @_; eval { $try }; if ($@) { local $_ = $@; &$catch; } } sub catch (&) { $_[0] }
This is invoked like this:
try { die "phooey"; } catch { /phooey/ and print "unphhoey\n"; }
The usual questions at this point are "WHAT?" and "How in the heck does that even work?" So this is an explication in detail. Let's summarize how subroutine calls work first, and then tackle the prototype stuff after.
Remember that when Perl calls a subroutine, it evaluates any expressions supplied as arguments first and then passes the values of those expressions as the arguments to the subroutine. So in this case, try() wants two expressions as its arguments: one is required to evaluate to a reference to a subroutine, and the other is required to evaluate to a scalar. On the other hand, catch() requires only one: some thing that evaluates to a subroutine reference.

Remember that an anonymous sub is actually an expression; it has the side effect of generating code that can be executed later, but it's actually just an expression that returns a scalar value: the reference that points to the generated code.

So how do the try() and catch() subs work, and why does the syntax shown actually compile?

Let's look at catch() first. It's just an identity function:

sub identity { return $_[0]; }
This returns whatever the first argument evaluated to as the value of the subroutine. This is exactly what catch() does, though it states it slightly more compactly, and uses a (&) prototype to constrain its argument to be a subroutine reference. So why is this useful, and why does it use an & prototype?

The & prototype, when it comes first, does two things:

  1. It allows you to pass an anonymous sub without the sub keyword in front
  2. it allows you to leave off the comma following this first argument
So catch()'s prototype says "Give me a sub reference somehow. An anonymous sub without the sub keyword will be fine, and I require that you give me no other arguments." So a catch() call could be any of the following:
sub example { /foo/ and print "foo detected"; } catch \&example; catch sub { /foo/ and print "foo detected"; }; catch { /foo/ and print "foo detected"; }
This last one is the one we'll be using for our example.

Now remember that catch() just returns its first argument as its value. All of these argument forms shown above evaluate to the same kind of thing: a reference to a subroutine. A reference to a subroutine is always a scalar, so a call to catch will be acceptable to try() as its second parameter (the prototype being &$: a sub reference, and a scalar).

So now we know we've got something that works for the second argument of try(). Let's look at the first one. This is &, so it can accept the same kinds of things that catch() did as its first parameter: an actual reference to a sub (like \&example), an anonymous sub ( sub { ... }), or a "headless" anonymous sub (like { ... }).

Remember that the second option you have when using & as the first argument prototype lets you leave off the comma as well as the sub, so the total call looks like this:

try { # some code that dies } catch { # pattern match against $_ for die string }
Which is really, if you wrote it all out in the standard sub call syntax:
try( sub{ # code that dies }, catch(sub { # code that checks vs. $_ } ) );
So. To call try(), Perl will have to evaluate any expressions in its argument list and then pass them in.

The first argument, when evaluated, yields a reference to some code to be called. This is the first (headless ) anonymous sub, with no trailing comma.

The second argumentis a call to catch() that has to be evaluated. The catch() call, as mentioned before, simply returns its argument to the caller. Its argument is an anonymous sub, so the returned value is a reference to this (unevaluated) sub. This is a scalar, so it matches the $ in the &$ prototype.

Now we have the arguments to try(): the expected subroutine reference (the & in &$) from the code block right after the try() sub name, and the reference to the anonymous sub returned by catch() (the $ in &$). We're ready to call try() at this point.

Inside try(), we eval the first anonymous sub. Since it's inside an eval block, the code does whatever it does and (possibly) sets $@ if it dies without further disturbing the flow of control. If $@ is set after the eval, we localize $_ (this creates a new global variable that we'll reference via $_ during this block) and set it to $@. Then we just call the second anonymous sub, which expects to see the contents of $@ in $_, where we've conveniently just put them. Since this is inside the block that localized $_, we sill get the "new" $_. This code does whatever it wants to do, then returns. The block ends, and $_ is restored to whatever value it had previous to the if ($@) block, and try() exits.

So the concepts here that are important are:

  • subroutines evaluate their arguments before they are called.
  • sub { $_[0] } returns its first argument's value as its value when it is called.
  • sub {} is an expression, returning a reference to an nonymous sub while compiling the code at the point where the sub block is evaluated
  • local() defines a new copy of a variable, and blocks called by this one get the new copy when referring to the variable. When the block localizing the variable ends, the old variable is restored.
  • a leading & in a prototype lets you leave off the "sub" in an anonymous sub definition, and lets you leave off the comma following the block too.
Edited 8/04: errant "*" removed in try() prototype

Edited 8/09: Typos corrected