Reputation: 61510
In our codebase we frequently use this construct to handle options to our programs:
Readonly my $ARGS => Getopt::Declare->new(
join( "\n",
'[strict]',
"--option1 <some-option-description> [required]",
)
) or exit(1);
The problem being that when strictness is violated in Getopt::Declare->new(...)
it will return undef
, which would be ok normally except that this undef
is stored in a Readonly variable , which when evaluated in boolean context is true.
An obvious solution is to remove the Readonly
and the code would function as expected; however I don't really like this solution since changing this would allows $ARGS
to be modified.
my $ARGS => Getopt::Declare->new(
join( "\n",
'[strict]',
"--option1 <some-option-description> [required]",
)
) or exit(1);
Another solution is to wrap the Getopt::Declare->new(...) or exit(1)
in parens, so they are evaluated before assigning to the Readonly variable.
Readonly my $ARGS => (
Getopt::Declare->new(
join( "\n",
'[strict]',
"--option1 <some-option-description> [required]",
)
) or exit(1);
)
I guess an alternative would be to use the higher precedence version of or ||
, which binds stronger than =>
but I am not sure it is as readable.
Readonly my $ARGS => Getopt::Declare->new(
join( "\n",
'[strict]',
"--option1 <some-option-description> [required]",
)
) || exit(1);
Upvotes: 2
Views: 150
Reputation: 58619
The immediate issue, as @choroba suggested, is one of precedence:
Readonly my $v => ... or die; # This construct... Readonly(my $v, ...) or die; # is really this... tie my $v, 'Readonly', ... or die; # which is more-or-less this, which evaluates to ... ReadOnly::Scalar=SCALAR(0x123) or die; # the object beneath the tie() ...
So you'll never hit the right half of the or
because the returned object evaluates true.
Now, if you really want to continue using that syntax, you could do something like this to force using the tie()d variable and not the underlying object:
use Readonly ();
sub MakeReadonly(\[$@%]@) {
&Readonly::Readonly; # tie() ${$_[0]} to Readonly implementation
${$_[0]}; # ... but return the tied variable, not the object
}
....
MakeReadonly my $e => ... or die; # This ...
MakeReadonly(my $e, ...) or die; # becomes this ...
$e or die; # which is effectively this.
As an aside, I'd recommend being careful with this construct. The appeal of Readonly
is that it allows a syntax deceptively similar to assigning to an attributed scalar, as if you were doing this:
my $v : Readonly = something() or die; # XXX Doesn't work! Not even Attribute::Constant
# works like this, unfortunately.
... but it's quite different. Your phrasing in the title and body of the post suggest you were at least initially thinking of it this way. (E.g., "Readonly variables evaluate to true" -- well, their underlying objects do, the tied variables themselves maybe not. Or, "evaluated before assigning" -- well, you're not really assigning per se, but again tie()ing.)
Upvotes: 1
Reputation: 118148
Readonly my $ARGS => Getopt::Declare->new(
# ...
);
$ARGS or exit(1);
ought to work as well.
Upvotes: 3