File Coverage

blib/lib/Type/Tie/Aggregate.pm
Criterion Covered Total %
statement 33 33 100.0
branch 2 4 50.0
condition 2 6 33.3
subroutine 10 10 100.0
pod 1 1 100.0
total 48 54 88.8


line stmt bran cond sub pod time code
1             # ABSTRACT: like Type::Tie, but slower and more flexible
2              
3             ######################################################################
4             # Copyright (C) 2021 Asher Gordon #
5             # #
6             # This program is free software: you can redistribute it and/or #
7             # modify it under the terms of the GNU General Public License as #
8             # published by the Free Software Foundation, either version 3 of #
9             # the License, or (at your option) any later version. #
10             # #
11             # This program is distributed in the hope that it will be useful, #
12             # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
14             # General Public License for more details. #
15             # #
16             # You should have received a copy of the GNU General Public License #
17             # along with this program. If not, see #
18             # . #
19             ######################################################################
20              
21             package Type::Tie::Aggregate;
22             $Type::Tie::Aggregate::VERSION = '0.001';
23             #pod =head1 SYNOPSIS
24             #pod
25             #pod use Type::Tie::Aggregate;
26             #pod use Types::Standard qw(Dict Optional Num Str);
27             #pod
28             #pod ttie my %hash, Dict[name => Str, age => Optional[Num]], (
29             #pod name => 'John Doe',
30             #pod age => 42,
31             #pod );
32             #pod
33             #pod $hash{name} = 'Jane Doe'; # ok
34             #pod $hash{age}++; # ok
35             #pod $hash{age} = 'forty-two; # dies
36             #pod delete $hash{name}; # dies ('name' is mandatory)
37             #pod
38             #pod # Unfortunately this does not work, because the hash is
39             #pod # momentarily cleared and will no longer pass the type constraint
40             #pod # (which requires a 'name' key).
41             #pod %hash = (name => 'J. Random Hacker');
42             #pod
43             #pod # Use this instead (also more efficient).
44             #pod (tied %hash)->initialize(name => 'J. Random Hacker');
45             #pod
46             #pod =head1 DESCRIPTION
47             #pod
48             #pod Like L, this module exports a single function:
49             #pod C. Also like L, C ties a variable to
50             #pod a type constraint (coercions will be honored).
51             #pod
52             #pod However, unlike L, when an assignment happens on
53             #pod a variable tied with C, the I variable will be
54             #pod re-checked, not just the value that was added. This is much more
55             #pod expensive, of course, but can be very useful for structured types such
56             #pod as C from L as show in the
57             #pod L.
58             #pod
59             #pod Any type constraints supporting the L interface
60             #pod should work, not just L types. However, in the
61             #pod examples that follow, all type constraints are from
62             #pod L unless specified otherwise.
63             #pod
64             #pod =head2 Initialization and Re-initialization
65             #pod
66             #pod Since some types don't allow empty values (see the L),
67             #pod values may need to be given when initializing the type. For example,
68             #pod this is invalid:
69             #pod
70             #pod ttie my %hash, Dict[name => Str]; # dies
71             #pod
72             #pod No values were given to initialize C<%hash>, so C<%hash> failed the
73             #pod type constraint C Str]> (which requires a C
74             #pod key). Instead, this should be done:
75             #pod
76             #pod ttie my %hash, Dict[name => Str], (name => 'My Name');
77             #pod
78             #pod This initializes C<%hash> with the value C<< (name => 'My Name') >>
79             #pod before any type checking is performed, so, at the end of the day,
80             #pod C<%hash> passes the type constraint.
81             #pod
82             #pod Another important thing to note is that when a variable is
83             #pod re-initialized, it is temporarily emptied. So the following is
84             #pod invalid:
85             #pod
86             #pod ttie my %hash, Dict[name => Str], (name => 'My Name');
87             #pod %hash = (name => 'Other Name'); # dies
88             #pod
89             #pod Instead, the C method should be used on the tied object,
90             #pod like so:
91             #pod
92             #pod ttie my %hash, Dict[name => Str], (name => 'My Name');
93             #pod (tied %hash)->initialize(name => 'Other Name'); # ok
94             #pod
95             #pod This is also more efficient than the previous method.
96             #pod
97             #pod =head2 Deep Tying
98             #pod
99             #pod C ties variables deeply, meaning that if any references
100             #pod contained within the variable are changed, the entire variable is
101             #pod rechecked against the type constraint. Blessed objects are not deeply
102             #pod tied, but tied references are and the functionality of these tied
103             #pod references is preserved.
104             #pod
105             #pod For example, the following Does The Right Thing(TM):
106             #pod
107             #pod ttie my %hash, HashRef[ArrayRef[Int]];
108             #pod $hash{foo} = [1, 2, 3]; # ok
109             #pod $hash{foo}[0] = 'one'; # dies
110             #pod $hash{bar} = [3, 2, 1]; # ok
111             #pod push @{$hash{bar}}, 'zero'; # dies
112             #pod
113             #pod This also works:
114             #pod
115             #pod use List::Util qw(all);
116             #pod use Tie::RefHash;
117             #pod
118             #pod ttie my @array, ArrayRef[HashRef[Int]];
119             #pod
120             #pod my $scalar_key = 'scalar';
121             #pod my @array_key = (1, 2, 3);
122             #pod tie my %refhash, 'Tie::RefHash', (
123             #pod \$scalar_key => 1,
124             #pod \@array_key => 2,
125             #pod );
126             #pod
127             #pod push @array, \%refhash;
128             #pod
129             #pod $array[0]{\$scalar_key} = 'foo'; # dies
130             #pod $array[0]{\@array_key} = 42; # ok
131             #pod all { ref ne '' } keys %{$array[0]}; # true
132             #pod
133             #pod Currently, circular references are not handled correctly (see
134             #pod L).
135             #pod
136             #pod =head1 CAVEATS
137             #pod
138             #pod =head2 Re-initialization
139             #pod
140             #pod Re-initialization of tied variables using C<@array = @init> or
141             #pod C<%hash = %init> does not always work. Use
142             #pod C<< (tied @array)->initialize(@init) >> and
143             #pod C<< (tied %hash)->initialize(%init) >> instead. See
144             #pod L for more information.
145             #pod
146             #pod =head2 Retying References
147             #pod
148             #pod If a variable tied to a type contains a reference, then that reference
149             #pod cannot be contained by any other variable tied to a type. For example,
150             #pod the following will die:
151             #pod
152             #pod my $arrayref = [42];
153             #pod ttie my @num_array, ArrayRef[ArrayRef[Num]], ($arrayref);
154             #pod ttie my @str_array, ArrayRef[ArrayRef[Str]], ($arrayref);
155             #pod
156             #pod If this were allowed, it would not be clear whether
157             #pod C should die or not. This behavior may be
158             #pod changed in a later release, but you probably should not be doing this
159             #pod regardless.
160             #pod
161             #pod =head2 Circular References
162             #pod
163             #pod Circular references are not handled correctly. Hopefully this will be
164             #pod fixed in a future release.
165             #pod
166             #pod =cut
167              
168 5     5   435027 use v5.13.2;
  5         38  
169 5     5   30 use strict;
  5         11  
  5         119  
170 5     5   39 use warnings;
  5         10  
  5         153  
171 5     5   2499 use namespace::autoclean;
  5         94270  
  5         24  
172 5     5   354 use Carp;
  5         9  
  5         325  
173 5     5   34 use Scalar::Util qw(reftype);
  5         11  
  5         228  
174 5     5   2817 use parent 'Exporter';
  5         1631  
  5         31  
175              
176             our @EXPORT = qw(ttie);
177              
178             # Used by Type::Tie::Aggregate::Deep;
179 10     10   45 sub _tied_types { qw(SCALAR ARRAY HASH) }
180              
181             {
182             my %tied_types = map { $_ => 1 } _tied_types;
183              
184             # Used also by Type::Tie::Aggregate::Deep. Returns the type of the
185             # reference.
186             sub _check_ref_type {
187 245     245   528 my ($class, $ref) = @_;
188 245   33     732 my $type = reftype $ref // croak 'Not a reference';
189 245 50       622 $type = 'SCALAR' if $type eq 'REF';
190 245 50       604 return unless $tied_types{$type};
191 245         848 return $type;
192             }
193             }
194              
195             #pod =func ttie
196             #pod
197             #pod ttie my $scalar, TYPE, $init_val;
198             #pod ttie my @array, TYPE, @init_val;
199             #pod ttie my %hash, TYPE, %init_val;
200             #pod
201             #pod Tie C<$scalar>, C<@array>, or C<%hash> to C and initialize with
202             #pod C<$init_val>, C<@init_val>, or C<%init_val>.
203             #pod
204             #pod =cut
205              
206             sub ttie (\[$@%]@) {
207 19     19 1 89019 my ($ref, $type, @args) = @_;
208              
209 19         51 my $ref_type;
210 19   33     157 $ref_type = __PACKAGE__->_check_ref_type($ref) //
211             croak "Cannot tie variable of type $ref_type";
212              
213 19         110 my $pkg = __PACKAGE__ . '::' . ucfirst lc $ref_type;
214 19         3134 require $pkg =~ s|::|/|gr . '.pm';
215              
216 19         231 &CORE::tie($ref, $pkg, $type, @args);
217 12         53 return $ref;
218             }
219              
220             #pod =method initialize
221             #pod
222             #pod (tied $scalar)->initialize($init_val);
223             #pod (tied @array)->initialize(@init_val);
224             #pod (tied %hash)->initialize(%init_val);
225             #pod
226             #pod Re-initialize C<$scalar>, C<@array>, or C<%hash>. This is necessary
227             #pod because some types don't allow an empty value, and the variable will
228             #pod temporarily be emptied (except for scalars) if initialized the usual
229             #pod way (e.g., C<@array = qw(foo bar baz)>). This is also more efficient
230             #pod than conventional initialization.
231             #pod
232             #pod See L for more info.
233             #pod
234             #pod =method type
235             #pod
236             #pod my $type = (tied VAR)->type;
237             #pod
238             #pod Return the type constraint for C. Note that the type cannot
239             #pod currently be set, only read.
240             #pod
241             #pod =cut
242              
243             #pod =head1 SEE ALSO
244             #pod
245             #pod =for :list
246             #pod * L
247             #pod
248             #pod =cut
249              
250             1;
251              
252             __END__