File Coverage

blib/lib/ReadonlyX.pm
Criterion Covered Total %
statement 66 77 85.7
branch 56 72 77.7
condition 18 24 75.0
subroutine 13 13 100.0
pod n/a
total 153 186 82.2


line stmt bran cond sub pod time code
1             package ReadonlyX;
2 13     13   155467 use 5.008;
  13         29  
3 13     13   42 use strict;
  13         12  
  13         213  
4 13     13   40 use warnings;
  13         15  
  13         472  
5             our $VERSION = "1.03";
6 13     13   333 BEGIN { *ReadonlyX:: = *Readonly:: }
7             package # hide from PAUSE
8             Readonly; # I wish...
9 13     13   44 use Carp;
  13         14  
  13         735  
10 13     13   39 use Exporter;
  13         15  
  13         385  
11 13     13   42 use vars qw/@ISA @EXPORT @EXPORT_OK/;
  13         11  
  13         10278  
12             push @ISA, 'Exporter';
13             push @EXPORT, qw/Readonly/;
14             push @EXPORT_OK, qw/Scalar Array Hash/;
15             #
16             sub Array(\@;@);
17             sub Hash(\%;@);
18             sub Scalar($;$);
19             sub Readonly(\[%@$]$);
20             #
21             sub Array(\@;@) {
22 20 100 66 20   5218 my $var = $#_ == 0 && defined $_[0] ? $_[0] : $_[1];
23 14         34 @{$_[0]}
24             = ref $var eq 'ARRAY'
25             && $#_ == 1
26 20 100 66     113 && ref $var eq 'ARRAY' ? @{$var} : @_[1 .. $#_]
  2 100       4  
27             if $#_ > 0;
28 19         37 _readonly($_[0]);
29             }
30              
31             sub Hash(\%;@) {
32 19 100 66 19   5084 my $var = $#_ == 0 && defined $_[0] ? $_[0] : $_[1];
33 19         26 my $ref = ref $var;
34 19 100 100     361 Carp::croak 'Odd number of elements in hash assignment'
35             unless (@_ % 2 == 1) || $ref eq 'HASH';
36 17 100 66     75 %{$_[0]} = ref $var eq 'HASH' && $#_ == 1 ? %{$var} : @_[1 .. $#_]
  13 100       49  
  5         11  
37             if $#_ > 0;
38 15         33 _readonly($_[0]);
39             }
40              
41             sub Scalar($;$) {
42 20 100 100 20   3706 my $var = $#_ == 0 && defined $_[0] ? $_[0] : $_[1];
43 20         28 my $ref = ref $var;
44 20 0       105 $ref eq 'ARRAY' ? $_[0] = $var : $ref eq 'HASH' ? $_[0]
    50          
    100          
    100          
    100          
45             = $var : $ref eq 'SCALAR'
46             or $ref eq '' ? $_[0] = $var : $ref eq 'REF' ? $_[0] = \$_[1] : 1;
47 17         34 _readonly($_[0]);
48 17         38 Internals::SvREADONLY($_[0], 1);
49             }
50              
51             sub Readonly(\[%@$]$) {
52 5     5   2104 my $type = ref $_[0];
53 5 50 66     18 return Scalar(${$_[0]}, defined $_[1] ? $_[1] : ())
  2 100       23  
54             if $type eq 'SCALAR' or $type eq '';
55 3 50       6 return Hash(%{$_[0]}, defined $_[1] ? $_[1] : ()) if $type eq 'HASH';
  2 100       13  
56 1 50       3 return Array(@{$_[0]}, defined $_[1] ? $_[1] : []) if $type eq 'ARRAY';
  1 50       5  
57             }
58              
59             sub _readonly {
60 247     247   198 my $type = ref $_[0];
61 247 100       299 my ($onoff) = $#_ ? $_[1] : 1;
62 247 100       322 if ($type eq '') {
    100          
    100          
    50          
    0          
63 191         235 return Internals::SvREADONLY($_[0], $onoff);
64             }
65             elsif ($type eq 'SCALAR') {
66 2         3 return Internals::SvREADONLY(${$_[0]}, $onoff);
  2         5  
67             }
68             elsif ($type eq 'HASH') {
69 27         28 for my $key (keys %{$_[0]}) {
  27         107  
70 117         147 _readonly($_[0]->{$key}, $onoff);
71 117         147 Internals::SvREADONLY($_[0]->{$key}, $onoff);
72             }
73 27         33 return Internals::SvREADONLY(%{$_[0]}, $onoff);
  27         73  
74             }
75             elsif ($type eq 'ARRAY') {
76 27         25 for my $index (0 .. $#{$_[0]}) {
  27         67  
77 69         101 _readonly($_[0]->[$index], $onoff);
78 69         91 Internals::SvREADONLY($_[0]->[$index], $onoff);
79             }
80 27         27 return Internals::SvREADONLY(@{$_[0]}, $onoff);
  27         77  
81             }
82             elsif ($type eq 'REF') {
83 0         0 my $refref = ref ${$_[0]};
  0         0  
84 0         0 _readonly(${$_[0]}, $onoff);
  0         0  
85 0 0       0 return Internals::SvREADONLY(@${$_[0]}, $onoff)
  0         0  
86             if $refref eq 'ARRAY';
87 0 0       0 return Internals::SvREADONLY(%${$_[0]}, $onoff)
  0         0  
88             if $refref eq 'HASH';
89 0         0 return Internals::SvREADONLY(${$_[0]}, $onoff);
  0         0  
90             }
91 0         0 Carp::carp 'We do not know what to do with ' . $type;
92             }
93              
94             sub Clone(\[$@%]) {
95 10     10   2668 require Storable;
96 10         4513 my $retval = Storable::dclone($_[0]);
97 10 100       31 $retval = $$retval if ref $retval eq 'REF';
98 10         23 my $type = ref $retval;
99 10 50 66     54 _readonly(( $type eq 'SCALAR' || $type eq '' ? $$retval
    100          
    100          
100             : $type eq 'HASH' ? $retval
101             : $type eq 'ARRAY' ? @$retval
102             : $retval
103             ),
104             0
105             );
106 10 100       47 return $type eq 'SCALAR' ?
    100          
    50          
    100          
    100          
107             $$retval
108             : ($type eq 'ARRAY' ?
109             wantarray ?
110             @$retval
111             : $retval
112             : ($type eq 'HASH' ? wantarray ? %$retval : $retval : $retval)
113             );
114             }
115             1;
116              
117             =head1 NAME
118              
119             ReadonlyX - Faster facility for creating read-only scalars, arrays, hashes
120              
121             =head1 Synopsis
122              
123             use strict;
124             use warnings;
125             use ReadonlyX;
126              
127             # Read-only scalar
128             my $sca1;
129             Readonly::Scalar $sca1 => 3.14;
130             Readonly::Scalar my $sca2 => time;
131             Readonly::Scalar my $sca3 => 'Welcome';
132             my $sca4 = time();
133             Readonly::Scalar $sca4; # Value is not clobbered
134              
135             # Read-only array
136             my @arr1;
137             Readonly::Array @arr1 => [1 .. 4];
138              
139             # or:
140             Readonly::Array my @arr2 => (1, 3, 5, 7, 9);
141              
142             # Read-only hash
143             my %hash1;
144             Readonly::Hash %hash1 => (key => 'value', key2 => 'value');
145             Readonly::Hash my %hash2 => (key => 'value', key2 => 'value');
146              
147             # or:
148             Readonly::Hash my %hash3 => {key => 'value', key2 => 'value'};
149              
150             # You can use the read-only variables like any regular variables:
151             print $sca1;
152             my $something = $sca1 + $arr1[2];
153             warn 'Blah!' if $hash1{key2};
154              
155             # But if you try to modify a value, your program will die:
156             $sca2 = 7; # "Modification of a read-only value attempted"
157             push @arr1, 'seven'; # "Modification of a read-only value attempted"
158             $arr1[1] = 'nine'; # "Modification of a read-only value attempted"
159             delete $hash1{key}; # Attempt to delete readonly key 'key' from a restricted hash
160              
161             # Create mutable clones
162             Readonly::Scalar $scalar => {qw[this that]};
163             # $scalar->{'eh'} = 'foo'; # Modification of a read-only value attempted
164             my $scalar_clone = Readonly::Clone $scalar;
165             $scalar_clone->{'eh'} = 'foo';
166             # $scalar_clone is now {this => 'that', eh => 'foo'};
167              
168             =head1 Description
169              
170             This is a near-drop-in replacement for L, the popular facility for
171             creating non-modifiable variables. This is useful for configuration files,
172             headers, etc. It can also be useful as a development and debugging tool for
173             catching updates to variables that should not be changed.
174              
175             If you really need to have immutable variables in new code, use this instead
176             of Readonly. You'll thank me later. See the section entitled
177             L for more.
178              
179             =head1 Functions
180              
181             All of these functions can be imported into your package by name.
182              
183             =head2 Readonly::Scalar
184              
185             Readonly::Scalar $pi => 3.14;
186             Readonly::Scalar my $aref => [qw[this that]]; # list ref
187             Readonly::Scalar my $href => {qw[this that]}; # hash ref
188              
189             Creates a non-modifiable scalar and assigns a value of to it. Thereafter, its
190             value may not be changed. Any attempt to modify the value will cause your
191             program to die.
192              
193             If the given value is a reference to a scalar, array, or hash, then this
194             function will mark the scalar, array, or hash it points to as being readonly
195             as well, and it will recursively traverse the structure, marking the whole
196             thing as readonly.
197              
198             If the variable is already readonly, the program will die with an error about
199             reassigning readonly variables.
200              
201             =head2 Readonly::Array
202              
203             Readonly::Array @arr1 => [1 .. 4];
204             Readonly::Array my @arr2 => (1, 3, 5, 7, 9);
205              
206             Creates a non-modifiable array and assigns the specified list of values to it.
207             Thereafter, none of its values may be changed; the array may not be lengthened
208             or shortened. Any attempt to do so will cause your program to die.
209              
210             If any of the values passed is a reference to a scalar, array, or hash, then
211             this function will mark the scalar, array, or hash it points to as being
212             Readonly as well, and it will recursively traverse the structure, marking the
213             whole thing as Readonly.
214              
215             If the variable is already readonly, the program will die with an error about
216             reassigning readonly variables.
217              
218             =head2 Readonly::Hash
219              
220             Readonly::Hash %h => (key => 'value', key2 => 'value');
221             Readonly::Hash %h => {key => 'value', key2 => 'value'};
222              
223             Creates a non-modifiable hash and assigns the specified keys and values to it.
224             Thereafter, its keys or values may not be changed. Any attempt to do so will
225             cause your program to die.
226              
227             A list of keys and values may be specified (with parentheses in the synopsis
228             above), or a hash reference may be specified (curly braces in the synopsis
229             above). If a list is specified, it must have an even number of elements, or
230             the function will die.
231              
232             If any of the values is a reference to a scalar, array, or hash, then this
233             function will mark the scalar, array, or hash it points to as being Readonly
234             as well, and it will recursively traverse the structure, marking the whole
235             thing as Readonly.
236              
237             If the variable is already readonly, the program will die with an error about
238             reassigning readonly variables.
239              
240             =head2 Readonly::Clone
241              
242             my $scalar_clone = Readonly::Clone $scalar;
243              
244             When cloning using L or L you will notice that the value
245             stays readonly, which is correct. If you want to clone the value without
246             copying the readonly flag, use this.
247              
248             Readonly::Scalar my $scalar => {qw[this that]};
249             # $scalar->{'eh'} = 'foo'; # Modification of a read-only value attempted
250             my $scalar_clone = Readonly::Clone $scalar;
251             $scalar_clone->{'eh'} = 'foo';
252             # $scalar_clone is now {this => 'that', eh => 'foo'};
253              
254             In this example, the new variable (C<$scalar_clone>) is a mutable clone of the
255             original C<$scalar>. You can change it like any other variable.
256              
257             =head1 Examples
258              
259             Here are a few very simple examples again to get you started:
260              
261             =head2 Scalars
262              
263             A plain old read-only value:
264              
265             Readonly::Scalar $a => "A string value";
266              
267             The value need not be a compile-time constant:
268              
269             Readonly::Scalar $a => $computed_value;
270              
271             Need an undef constant? Okay:
272              
273             Readonly::Scalar $a;
274              
275             =head2 Arrays/Lists
276              
277             A read-only array:
278              
279             Readonly::Array @a => (1, 2, 3, 4);
280              
281             The parentheses are optional:
282              
283             Readonly::Array @a => 1, 2, 3, 4;
284              
285             You can use Perl's built-in array quoting syntax:
286              
287             Readonly::Array @a => qw[1 2 3 4];
288              
289             You can initialize a read-only array from a variable one:
290              
291             Readonly::Array @a => @computed_values;
292              
293             A read-only array can be empty, too:
294              
295             Readonly::Array @a => ();
296             # or
297             Readonly::Array @a;
298              
299             =head2 Hashes
300              
301             Typical usage:
302              
303             Readonly::Hash %a => (key1 => 'value1', key2 => 'value2');
304             # or
305             Readonly::Hash %a => {key1 => 'value1', key2 => 'value2'};
306              
307             A read-only hash can be initialized from a variable one:
308              
309             Readonly::Hash %a => %computed_values;
310              
311             A read-only hash can be empty:
312              
313             Readonly::Hash %a => ();
314             # or
315             Readonly::Hash %a;
316              
317             If you pass an odd number of values, the program will die:
318              
319             Readonly::Hash my %a => (key1 => 'value1', "value2");
320             # This dies with "Odd number of elements in hash assignment"
321              
322             =head1 ReadonlyX vs. Readonly
323              
324             The original Readonly module was written nearly twenty years ago when the
325             built-in capability to lock variables didn't exist in perl's core. The
326             original author came up with the amazingly brilliant idea to use the new (at
327             the time) C construct. It worked amazingly well! But it wasn't long
328             before the speed penalty of tied varibles became embarrassingly obvious. Check
329             any review of Readonly written before 2013; the main complaint was how slow it
330             was and the benchmarks proved it.
331              
332             In an equally brilliant move to work around tie, Readonly::XS was released for
333             perl 5.8.9 and above. This bypassed C for basic scalars which made a
334             huge difference.
335              
336             During all this, two very distinct APIs were also designed and supported by
337             Readonly. One for (then) modern perl and one written for perl 5.6. To make
338             this happen, time consuming eval operations were required and the codebase
339             grew so complex that fixing bugs was nearly impossible. Readonly was three
340             different modules all with different sets of quirks and bugs to fix depending
341             on what version of perl and what other modules you had installed. It was a
342             mess.
343              
344             So, after the original author abandoned both Readonly and Readonly::XS, as
345             bugs were found, they went unfixed. The combination of speed and lack of
346             development spawned several similar modules which usually did a better job but
347             none were a total drop-in replacement.
348              
349             Until now.
350              
351             ReadonlyX is the best of recent versions of Readonly without the old API and
352             without the speed penalty of C. It's what I'd like to do with
353             Readonly if resolving bugs in it wouldn't break 16 years of code out there in
354             Darkpan.
355              
356             In short, unlike Readonly, ReadonlyX...
357              
358             =over
359              
360             =item ...does not use slow C magic or eval. There shouldn't be a
361             speed penalty after making the structure immutable. See the
362             L section below
363              
364             =item ...does not strive to work on perl versions I can't even find a working
365             build of to test against
366              
367             =item ...has a single, clean API! What do all of these different forms of the
368             original Readonly API do?
369              
370             use Readonly;
371             Readonly my @array1 => [2];
372             Readonly \my @array2 => [2];
373             Readonly::Array my @array3 => [2];
374             Readonly::Array1 my @array4 => [2];
375              
376             Be careful because they all behave very differently. Even your version of perl
377             and the contents of the list changes how they work. Give up? Yeah, me too.
378             Bonus: Guess which one doesn't actually make the list items read only.
379              
380             =item ...does the right thing when it comes to deep vs. shallow structures
381              
382             =item ...allows implicit undef values for scalars (Readonly inconsistantly
383             allows this for hashes and arrays but not scalars)
384              
385             =item ...a lot more I can't think of right now but will add when they come to
386             me
387              
388             =item ...is around 100 lines instead of 460ish so maintaining it will be a
389             breeze
390              
391             =item ...doesn't clobber predefined variables when making them readonly
392              
393             Using Readonly, this:
394              
395             my @array = qw[very important stuff];
396             Readonly::Array @array;
397             print "@array";
398              
399             ...wouldn't print anything. I consider it a bug but I'm not sure why it was
400             designed this way originally. With ReadonlyX, you won't lose your
401             C<'very important stuff'>.
402              
403             Note that this is an incompatible change! If you attempt to do this and then
404             switch to plain 'ol Readonly, your code will not work.
405              
406             =back
407              
408             =head1 Benchmarks
409              
410             Don't believe Readonly is slow? Here's the result of basic benchmarking:
411              
412             Hash Benchmark: timing 5000000 iterations of const, normal, readonly, readonlyx...
413             const: 3 wallclock secs ( 2.73 usr + 0.02 sys = 2.75 CPU) @ 1818181.82/s (n=5000000)
414             normal: 3 wallclock secs ( 3.02 usr + -0.02 sys = 3.00 CPU) @ 1666666.67/s (n=5000000)
415             readonly: 47 wallclock secs (40.64 usr + 0.03 sys = 40.67 CPU) @ 122931.67/s (n=5000000)
416             readonlyx: 4 wallclock secs ( 3.22 usr + -0.01 sys = 3.20 CPU) @ 1560549.31/s (n=5000000)
417             Array Benchmark: timing 5000000 iterations of const, normal, readonly, readonlyx...
418             const: 3 wallclock secs ( 2.19 usr + 0.03 sys = 2.22 CPU) @ 2253267.24/s (n=5000000)
419             normal: 1 wallclock secs ( 1.44 usr + 0.00 sys = 1.44 CPU) @ 3474635.16/s (n=5000000)
420             readonly: 36 wallclock secs (32.52 usr + 0.13 sys = 32.64 CPU) @ 153181.58/s (n=5000000)
421             readonlyx: 1 wallclock secs ( 1.12 usr + -0.02 sys = 1.11 CPU) @ 4512635.38/s (n=5000000)
422             Scalar Benchmark: timing 5000000 iterations of const, normal, readonly, readonlyx...
423             const: 1 wallclock secs ( 1.14 usr + -0.02 sys = 1.12 CPU) @ 4448398.58/s (n=5000000)
424             normal: 1 wallclock secs ( 0.99 usr + 0.02 sys = 1.00 CPU) @ 4995005.00/s (n=5000000)
425             readonly: 1 wallclock secs ( 1.25 usr + 0.00 sys = 1.25 CPU) @ 4000000.00/s (n=5000000)
426             readonlyx: 2 wallclock secs ( 1.20 usr + 0.00 sys = 1.20 CPU) @ 4156275.98/s (n=5000000)
427              
428             Find the script to run them yourself in C.
429              
430             =head1 Requirements
431              
432             There are no non-core requirements.
433              
434             =head1 Bug Reports
435              
436             If email is better for you, L but I
437             would rather have bugs sent through the issue tracker found at
438             http://github.com/sanko/readonly/issues.
439              
440             ReadonlyX can be found is the branch of Readonly found here:
441             https://github.com/sanko/readonly/tree/ReadonlyX
442              
443             =head1 Author
444              
445             Sanko Robinson - http://sankorobinson.com/
446              
447             CPAN ID: SANKO
448              
449             =head1 License and Legal
450              
451             Copyright (C) 2016 by Sanko Robinson
452              
453             This module is free software; you can redistribute it and/or modify it under
454             the same terms as Perl itself.
455              
456             =cut