Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can you write a conditional based on the definedness of multiple variables?

Every now and then I find myself needing to write conditionals with two or three branches: at minimum, one for when all of two or more variables are defined and one for when any or none of those are defined. Sometimes that last branch needs to be two separate branches. What are some short and sweet ways of writing this?

like image 763
Kaiepi Avatar asked Jun 18 '20 12:06

Kaiepi


3 Answers

with, without, and orwith are conditionals that work similarly to if, unless, and elsif, except they check for the definedness of their argument instead of their truthiness. Junctions get threaded when used in combination with these, so a conditional like that can be written like this:

my Str $foo = '' if 2.rand.floor;
my Str $bar = '' if 2.rand.floor;
with $foo & $bar {
    say 'yes';
} orwith $foo | $bar {
    say 'maybe';
} else {
    say 'no';
}

Or for an arbitrary number of variables:

my Str @strings = do 2.rand.floor ?? '' !! Nil for ^3;
with all @strings {
    say 'yes';
} orwith any @strings {
    say 'maybe';
} else {
    say 'no';
}
like image 74
Kaiepi Avatar answered Apr 29 '23 13:04

Kaiepi


with et al are great, and I might write a chained with statement, especially if the matching of particular variable combinations was ad hoc or I thought could become so given expected evolution of the code.

But given your literal question, and the scenario you have in your answer, I'd much more likely go with given/when instead:

my ($foo, $bar, $baz) = (Mu, 42).pick xx Inf;

given $foo, $bar, $baz {
  when .all.defined { say 'yes'   }
  when .any.defined { say 'maybe' }
  default           { say 'no'    }
}
like image 41
raiph Avatar answered Apr 29 '23 13:04

raiph


You could create a combined value and use a signature as a when conditional.

( :(…) is the syntax for a Signature literal. )

given ($foo,$bar,$baz) {
   when :( Any:D, Any:D, Any:D ) { 'all defined' }
   when :( Any:D, Any:D, Any:U ) { '$baz undefined' }
   when :( Any:D, Any:_, Any:D ) { '$bar not checked' }

   default { … }
}

You could create lexical constants to make it shorter.

my constant D = Any:D; # defined
my constant U = Any:U; # undefined
my constant _ = Any:_; # either

given ($foo,$bar,$baz) {
   when :(D,D,D) { 'all defined' }
   when :(D,D,U) { '$baz undefined' }
   when :(D,_,D) { '$bar not checked' }

   default { … }
}

Or if you want it to be more visual:

my constant term:<▣> = Any:D; # defined
my constant term:<□> = Any:U; # undefined
my constant term:<▨> = Any:_; # either

given ($foo,$bar,$baz) {
   when :(▣,▣,▣) { 'all defined' }
   when :(▣,▣,□) { '$baz undefined' }
   when :(▣,▨,▣) { '$bar not checked' }

   default { … }
}

You could create a subroutine that gives the Signature to use instead.

## something like this should work
## but something is weird with the returns constraint
#sub prefix:<d> ( $_ ) {
#  my constant %lookup = (
#    '▣' => Parameter.new( type => Any:D ),
#    '□' => Parameter.new( type => Any:U ),
#    '▨' => Parameter.new( type => Any:_ ),
#  );
#  Signature.new: params => %lookup{.comb}
#}

sub prefix:<d> ( $_ ) {
  my constant %lookup = (
    '▣' => 'Any:D',
    '□' => 'Any:U',
    '▨' => 'Any:_',
  );
  use MONKEY-SEE-NO-EVAL;
  EVAL ":(%lookup{.comb}.join(","))"
}

given ($foo,$bar,$baz) {
   when d'▣▣▣' { 'all defined' }
   when d'▣▣□' { '$baz undefined' }
   when d'▣▨▣' { '$bar not checked' }

   default { … }
}
like image 31
Brad Gilbert Avatar answered Apr 29 '23 13:04

Brad Gilbert