Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a convenience for safe dereferencing in Perl?

So perl5porters is discussing to add a safe dereferencing operator, to allow stuff like

$ceo_car_color = $company->ceo->car->color
    if  defined $company
    and defined $company->ceo
    and defined $company->ceo->car;

to be shortened to e.g.

$ceo_car_color = $company->>ceo->>car->>color;

where $foo->>bar means defined $foo ? $foo->bar : undef.

The question: Is there some module or unobstrusive hack that gets me this operator, or similar behavior with a visually pleasing syntax?

For your enjoyment, I'll list ideas that I was able to come up with.

  1. A multiple derefencing method (looks ugly).

    sub multicall {
        my $instance = shift // return undef;
        for my $method (@_) {
            $instance = $instance->$method() // return undef;
        }
        return $instance;
    }
    
    $ceo_car_color = multicall($company, qw(ceo car color));
    
  2. A wrapper that turns undef into a proxy object (looks even uglier) which returns undef from all function calls.

    { package Safe; sub AUTOLOAD { return undef } }
    sub safe { (shift) // bless {}, 'Safe' }
    
    $ceo_car_color = safe(safe(safe($company)->ceo)->car)->color;
    
  3. Since I have access to the implementations of ceo(), car() and color(), I thought about returning the safe proxy directly from these methods, but then existing code might break:

    my $ceo = $company->ceo;
    my $car = $ceo->car if defined $ceo; # defined() breaks
    

    Unfortunately, I don't see anything in perldoc overload about overloading the meaning of defined and // in my safe proxy.

like image 549
Stefan Majewsky Avatar asked Oct 07 '22 12:10

Stefan Majewsky


1 Answers

Maybe this is not the most useful solution, but it's one more WTDI (a variant of nr. 1) and it's a non-trivial use-case for List::Util's reduce, which are very rare. ;)

Code

#!/usr/bin/env perl

use strict;
use warnings;
use feature     'say';
use List::Util  'reduce';

my $answer = 42;
sub new { bless \$answer }
sub foo { return shift }        # just chaining
sub bar { return undef }        # break the chain
sub baz { return ${shift()} }   # return the answer

sub multicall { reduce { our ($a, $b); $a and $a = $a->$b } @_ }

my $obj = main->new();
say $obj->multicall(qw(foo foo baz)) // 'undef!';
say $obj->multicall(qw(foo bar baz)) // 'undef!';

Output

42
undef!

Note:

Of course it should be

return unless defined $a;
$a = $a->$b;

instead of the shorter $a and $a = $a->$b from above to work correctly with defined but false values, but my point here is to use reduce.

like image 167
memowe Avatar answered Oct 10 '22 02:10

memowe