| 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__ |