Last week I wrote about how Perl 5 gives us several ways to solve problems, and how we are free to choose among them based on what we think is important, clearer, more transparent (or opaque), etc. I wrote about how I liked the fact that in general Perl 5 trusts the developer to make that decision; and used a specific case to illustrate how I could choose between two (or more) alternatives.

Since then, I’ve thought a little more about it, and I think I’ve come up with another possible solution that I’d like to leave here for the record.

For some quick background: CPAN has PerlX::Maybe that offers a maybe function that can be used to conditionally include elements in a list. This is useful when key-value pairs in eg. a hash declaration should be conditionally skipped. But because of the way maybe is implemented, it’s not always safe to use, and when used so it is, it becomes unwieldly.

So I propose a different maybe:

sub maybe (&$@) {
    my ( $block, $test ) = splice @_, 0, 2;

    return @_ unless defined $test;

    my @values = $block->();
    if ( @values == 1 ) {
        for (ref $values[0]) {
            if    ( /HASH/ )  { @values = %{ $values[0] } }
            elsif ( /ARRAY/ ) { @values = @{ $values[0] } }
        }
    }

    return ( @values, @_ );
};

This function takes a block of code and a value, and returns the result of calling the block only when the value is defined. Any additional parameters that are passed to the function after the value to check are returned at the end regardless.

The (&$@) after the function name is a prototype, which roughly speaking gives the Perl 5 interpreter a hint as to the context in which the different parameters should be … interpreted. Specifically, it allows us to call this function like this:

some_function(
            foo => 42,
            bar => 1,
    maybe { baz => $obj->bar } $obj,
);

Thanks to the prototype, we don’t need the parentheses when calling maybe, and the { ... } gets interpreted as a block of code we can conditionally (and therefore safely) call. The $obj after that (note the lack of commas!) is the condition to check.

This call is functionally equivalent to

maybe( sub { baz => $obj->bar }, $obj )

which is itself largely equivalent to

sub { baz => $obj->bar }->() if defined $obj

The additional code in the declaration above makes it so that calling it like this

some_function(
    foo => 42,
    bar => 1,
    maybe \&returns_arrayref => $check,
    maybe \&returns_hashref  => $check,
);

will also work, by conditionally calling the code references and dereferencing their return values into the return list. PerlX::Maybe has some additional features to de-reference blessed objects, but this seems to me like a bad idea, so I’m happy to skip that.

Needless to say, I have never used this in anger, so there may be bugs that I’ve missed or situations that I haven’t considered. But as a statement of what I’d like to be able to do with a maybe function, I think it does the job.

There may also be additional features that could be added, but if there’s one thing that I’ve learned from professionaly developing code and releasing some publicly, it’s that the more things are supported, the harder the maintenance.

Now that I’ve written this down, this feels like a surprisingly obvious thing to say.