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