dcsimg

Many Happy Returns

Randal Schwartz explains why you should consider Contextual::Return for your next tricky return value problem.

Two months ago, I mentioned Contextual::Return and showed how to use it to create a dual variable:

use Contextual::Return;
my $result = NUM { 13 } STR { "Permission Denied" };
if ($result == 13) { ... } # true
if ($result =~ /denied/i) { ... } # also true!

Such a result is fascinating to me. First, I had not put together all the pieces of how this kind of a value was possible using relatively straightforward Perl. Second, the syntax for specifying the behavior is rather remarkable at first glance, and yet understandable once I spent a bit of time pawing through the code. But before we drill down into the implementation, let’s back up a minute, and look at the kinds of problems that Contextual::Return intends to solve.

Many built-in Perl functions have related-but-distinct return values when invoked in either a scalar context or a list context. For example, the grep function returns a count of successful items in a scalar context, but the items themselves in a list context. Similarly, localtime returns a time string in a scalar context, or the elements that make up the time in a list context.

You can write Perl subroutines that emulate this behavior by paying attention to wantarray (which really should be called “wantlist”):

sub my_funky_func { …
  return @some_list if wantarray; # list context
  return $some_scalar if defined wantarray 
    and not wantarray; # scalar context
  ## "not defined wantarray" is 
  ## "void context", don't return anything
  print "funky_func is done!\n"
}

The trouble is that the tri-state return of wantarray is a bit obscure, so a quick use of Contextual::Return provides names for the three states:

use Contextual::Return;
sub my_funky_func { …;
  return @some_list if LIST;
  return $some_scalar if SCALAR;
  # VOID would be true here
  print "funky_func is done!\n";
}

But Contextual::Return does so much more. With a bit of syntax magic, you can create the multiway branch automatically:

sub my_funky_func { …
  return
    LIST { @some_list }
    SCALAR { $some_scalar }
    VOID { print "funky_func is done!\n" };
}

It looks like some entirely new syntax here, but in fact, it’s really just a matter of properly prototyped subroutines. A subroutine that is prototyped as ;&$ accepts an optional block of code and another optional argument after that:

sub versive (;&$) { … }
versive; # no args
versive { some code block }; # code ref
versive sub { some code block }; # same 
versive { some code block } $scalar; # code ref + scalar

The Contextual::Return module uses such prototyped subroutines in a nested way. In the earlier code, the VOID subroutine is evaluated first and returns an object that the SCALAR subroutine sees as its second argument. Each subroutine in the list modifies the return object so that it works appropriately with every new attribute, and then returns the updated object. Very cool.

At least, that explains how the nesting works. But how does the right value get selected? Well, even after staring at the code for a while, all I can say is my head hurts. It has something to do with scalar context being passed down to every subsequent element of the chain, and in those cases, a smart object is returned that does the right thing in the right context. Only the head of the chain might be in list or void context, and the smart object is evaluated in the proper fashion. If the head of the chain is evaluated in a scalar context, then the smart object (of type Contexual::Return::Value) itself is returned, to “discover” in the caller exactly how it is needed (boolean, number, string, or some kind of reference, as you’ll see shortly).

The scalar return value is unique for an additional reason: the code block associated with SCALAR isn’t executed until it is needed. This creates a lazy invocation. For example:

my $x = SCALAR { print "executed\n"; 3 };

The block with print isn’t executed immediately. In fact, the $x value can be passed around the program at will, as long as it is never needed as a boolean, numeric, string, or reference value. But once it’s been used, the block is executed, and the return value is cached. As a reminder of this, you can use LAZY in place of SCALAR with no change in meaning.

You can choose to avoid the caching of the value by flagging the result as ACTIVE:

my $now = ACTIVE SCALAR { localtime };

Each time $now is evaluated in a scalar context, the block is re-executed, returning a different timestring. This can be used for logging, for example:

warn "$now: approaching memory limits";

Although the LIST and VOID values are executed immediately during evaluation, any scalar-context usage can further be distinguished into separate types. For example, you can return separate values depending on whether the scalar is used in a boolean, numeric, or string context:

my $x = BOOL { 0 } NUM { 3.14 } STR { "pi" };
unless ($x) { warn "false!" } # executed!
print "The value is $x\n"; # Value is pi\n
$y = $x + 0; # $y = 3.14

All three of these examples can be executed in sequence, because the value in $x remains an object even after being examined. Each block is executed lazily, however, caching the result of that type of return value. I can disable the caching with ACTIVE as before, causing the block to be repeatedly executed. For example:

my $now = ACTIVE NUM { time } STR { localtime };

Now, each time I use $now in a string context, it’s the current interpreted localtime string, but when I use it in a numeric context, it’s the Unix epoch time value. Cool.

If I’d rather “lock in” the type, so it’s not some sort of Schroedinger’s Cat that may or may not be a numeric value, and collapses instead to the first observation, I can add FIXED:

my $x = FIXED BOOL { 0 } NUM { 3.14 } STR { "pi" };

Now, the moment I use it in one of the three contexts, the other two contexts are re-vectored to the new value. For example:

my $three = FIXED BOOL { 3 } STR { "three" };

If I use this first in a boolean context, it locks in as 3. Otherwise, it locks in as three. And by “lock in”, I mean that it no longer has any magical properties. In the Harry Potter universe, it is now a muggle.

Besides boolean, numeric, and string contexts, you can also distinguish between various types of reference contexts. For example, I could create a value that acts appropriately whether I’m dereferencing it as a hashref or arrayref. Consider a file-stat subroutine that returns an array ref of stat values in an arrayref context, or can be used as a hashref to pick out stat items by name:

my @STAT = qw(dev ino mode nlink uid gid
  rdev size atime mtime ctime blksize blocks);
sub statify {
  my @stat = stat(shift) or return FAIL;
  return
    ARRAYREF { \@stat }
    HASHREF {
      my %stat;
      @stat{@STAT} = @stat;
      \%stat;
    };
}

Now I can call this as:

my $passwd = statify("/etc/passwd");
my $n1 = $passwd->{size}; # select size via hashref
my $n2 = $passwd->[7]; # select size via arrayref

And both work equally well. (If I had included FIXED, the second one would have failed, as it would have already locked in the arrayref nature of the return value.)

The FAIL line illustrates another feature of a Contexual::Return- enhanced subroutine. If FAIL is returned in a scalar context, an undef is substituted. However, if FAIL is executed in any other context, an exception is thrown. You can enhance the message of the exception with a parameter to FAIL:

my @stat = stat(shift) or return FAIL { "stat failed: $!" };

This default behavior is heavily configurable. See the Contextual::Return man page for more details.

What if I had invoked statify in a list context, because there’s no LIST block? In a nice interpretation of “do what I mean”, the ARRAYREF value is automatically dereferenced for us:

my @full_stat = statify("/etc/passwd");
print $full_stat[7];

In fact, there are many automatic defaults for the various types of return values. I could have just as easily spelled out the LIST return:

my @STAT = qw(dev ino mode nlink uid gid
  rdev size atime mtime ctime blksize blocks);
sub statify {
  my @stat = stat(shift) or return FAIL;
  return
    LIST { @stat }
    HASHREF {
      my %stat;
      @stat{@STAT} = @stat;
      \%stat;
    };
}

This results in an arrayref of the LIST return when used in an arrayref context. The full list of return values includes:

DEFAULT
  VOID
  NONVOID
  LIST
  SCALAR
  VALUE
    STR, NUM, BOOL
  REF
    SCALARREF, ARRAYREF, CODEREF, HASHREF
    GLOBREF, OBJREF

This list is nested from the most general to the most specific, although you should consult the man page for some interesting exceptions. The general routines will be used when a more specific routine is not available.

One problem that I’ve run into from time to time that is solved easily by Contextual::Return is how to perform an evaluation in the context of the caller, but still execute code after that evaluation. If a RESULT block is present, it overrides the normal “last expression evaluated” logic for subroutine return values, and also is executed in the context of the caller.

For example, suppose you want to execute a coderef as a transaction against a database handle:

sub perform {
  my $dbh = shift;
  my $code = shift;
  return
    VALUE {
      $dbh->start_work;
      RESULT { $code->() };
      $dbh->commit;
    };
}

In this case, the VALUE block is selected regardless of scalar or list context. The RESULT block is executed in the context of the caller, and sets the result value. If the result block throws an exception, the commit is skipped. Simple and elegant, but not quite complete: I should probably revert the transaction. The presence of a RECOVER block causes all other blocks to act as if they had an eval wrapper. If any exception occurs, the $@ variable is set, and you end up in the RECOVER block, which can force a RESULT if needed:

sub perform {
  my $dbh = shift;
  my $code = shift; 
  return
    VALUE {
      $dbh->start_work;
      $code->();
    }
    RECOVER {
      if ($@) { # exception
        $dbh->revert; # roll back
        RESULT { FAIL }; # exception
      } else {
        $dbh->commit; # all good!
      }
    };
}

I didn’t have room to talk about the LVALUE support here, but hopefully I’ve whetted your appetite for Contextual::Return. Until next time, enjoy!

Comments on "Many Happy Returns"

Hello. fantastic job. I did not anticipate this. This is a remarkable story. Thanks!

That is the end of this article. Here you?ll obtain some internet sites that we feel you will value, just click the links.

Sites of interest we’ve a link to.

Very handful of internet websites that happen to be detailed beneath, from our point of view are undoubtedly very well worth checking out.

Wonderful story, reckoned we could combine a couple of unrelated information, nonetheless actually worth taking a appear, whoa did a single find out about Mid East has got a lot more problerms as well.

Below you will obtain the link to some web pages that we think you need to visit.

Here are some hyperlinks to websites that we link to mainly because we believe they are really worth visiting.

Sites of interest we have a link to.

One of our visitors recently advised the following website.

We came across a cool website that you simply may possibly delight in. Take a look for those who want.

Just beneath, are many entirely not connected web sites to ours, nevertheless, they are certainly worth going over.

Just beneath, are a lot of totally not connected sites to ours, having said that, they may be certainly really worth going over.

Very few web-sites that transpire to be detailed below, from our point of view are undoubtedly properly really worth checking out.

Usually posts some pretty exciting stuff like this. If you are new to this site.

Check below, are some completely unrelated internet websites to ours, nevertheless, they’re most trustworthy sources that we use.

Every once in a when we pick out blogs that we read. Listed beneath are the most recent sites that we pick out.

Usually posts some really fascinating stuff like this. If you?re new to this site.

Just beneath, are various totally not related web pages to ours, however, they may be certainly worth going over.

Below you?ll come across the link to some web-sites that we feel you need to visit.

Leave a Reply