Reputation: 6032
In Perl, it is idiomatic to write
$_ = '2021-04-09 10:23:42';
my ($year, $month, $day) = m/(\d\d\d\d)-(\d\d)-(\d\d)/;
How can I use this with Readonly ? Trying Readonly::Array
Readonly::Array my ($year, $month, $day) => m/(\d\d\d\d)-(\d\d)-(\d\d)/;
results in
Type of arg 1 to Readonly::Array must be array (not list) at rdonly.pl line 4, near "m/(\d\d\d\d)-(\d\d)-(\d\d)/;"
Trying Readonly::Scalar
Readonly::Scalar my ($year, $month, $day) => m/(\d\d\d\d)-(\d\d)-(\d\d)/;
++$year;
die $year unless $year == 2022;
results in
1 at rdonly.pl line 9.
($year is not read-only and has an incorrect value)
Upvotes: 3
Views: 336
Reputation: 386331
Interface 1
Readonly::ManyScalars my ($year, $month, $day) => [ /^(\d\d\d\d)-(\d\d)-(\d\d)\z/ ]
or die("Invalid input\n");
while ( Readonly::ManyScalars my ($k, $v) => [ each %h ] ) {
...
}
Implementation:
use Readonly qw( );
sub Readonly::ManyScalars {
my $vals = pop(@_);
for (0..$#_) {
Readonly::Scalar $_[ $_ ] => $vals->[ $_ ];
}
# As similar to list assignment as we can.
return wantarray ? @_ : 0+@$vals;
}
This version has the advantage of being similar to the existing Readonly subs. Note that you need to create an array from the assigned values (e.g. by wrapping the expression in square brackets as shown above).
Interface 2
(ro my $year, ro my $month, ro my $day) = /^(\d\d\d\d)-(\d\d)-(\d\d)\z/
or die("Invalid input\n");
while ( (ro my $k, ro my $v) = each(%h) ) {
...
}
Implementation:
use Readonly qw( );
use Variable::Magic qw( wizard cast );
my $wiz = wizard(
data => sub { [ 0, $_[1] ] },
set => sub {
Readonly::Scalar(${ $_[1][1] }, ${ $_[0] });
$_[1][0] = 1;
},
free => sub {
Readonly::Scalar(${ $_[1][1] }, undef) if !$_[1][0];
},
);
sub ro(\$) :lvalue { cast(my $proxy, $wiz, $_[0]); $proxy }
This version has the advantage that not all the variables have to be read-only, and you do stuff like (ro my $x, undef, ro my $z) = ...
rather than using dummy vars.
Upvotes: 3
Reputation: 6626
I do not have a clean way of doing this. However, combining refaliasing
(or Data::Alias
on older Perls) and Readonly
does the trick, although it looks quite dirty:
use Readonly;
$_ = '2021-04-09 10:23:42';
\(my @ymd) = (\my $year, \my $month, \my $day);
my @matching = m/(\d\d\d\d)-(\d\d)-(\d\d)/;
map { Readonly::Scalar $ymd[$_] => $matching[$_] } 0 .. $#ymd;
print "$year/$month/$day"; # print 2021/04/09
++$year; # dies with "Modification of a read-only value attempted at tmp.pl line ..."
The result is pretty similar as what you wanted. In particular, the content of @ymd
is also read-only, which means that you won't have the surprise of discovering that $year
was modified through @ymd
.
Using List::MoreUtils::pairwise
allows to replace
map { Readonly::Scalar $ymd[$_] => $matching[$_] } 0 .. $#ymd;
With
pairwise { Readonly::Scalar $a => $b } @ymd, @matching;
Which looks a bit nicer.
If you'd prefer not to use the refaliasing feature, you can use Data::Alias
: you just need to replace \(my @ymd) = (\my $year, \my $month, \my $day);
with alias my @ymd = my ($year, $month, $day);
Finally, there are several ways to put that code in a sub
in order to factorize the code a bit and improve readability. For instance:
$_ = '2021-04-09 10:23:42';
init_readonly(my $year, my $month, my $day,
[m/(\d\d\d\d)-(\d\d)-(\d\d)/]);
print "$year/$month/$day";
++$year;
die $year unless $year == 2022;
sub init_readonly {
my $values = pop @_;
pairwise { Readonly::Scalar $a => $b } @_, @$values;
}
(Data::Alias
is not needed anymore since within a sub, @_
contains aliases to the arguments)
Upvotes: 1