-
Notifications
You must be signed in to change notification settings - Fork 293
/
containers.pod6
419 lines (303 loc) · 14.3 KB
/
containers.pod6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
=begin pod :tag<perl6>
=TITLE Containers
=SUBTITLE A low-level explanation of Perl 6 containers
This section explains the levels of indirection involved in dealing with
variables and container elements. The difference types of containers used in
Perl 6 are explained and the actions applicable to them like assigning, binding
and flattening. More advanced topics like self-referencial data, type
constraints and custom containers are discussed at the end.
=head1 What is a variable?
Some people like to say "everything is an object", but in fact a variable is
not a user-exposed object in Perl 6.
When the compiler encounters a variable declaration like C<my $x>, it
registers it in some internal symbol table. This internal symbol table is
used to detect undeclared variables and to tie the code generation for the
variable to the correct scope.
At runtime, a variable appears as an entry in a I<lexical pad>, or
I<lexpad> for short. This is a per-scope data structure that stores a
pointer for each variable.
In the case of C<my $x>, the lexpad entry for the variable C<$x> is a
pointer to an object of type C<Scalar>, usually just called I<the
container>.
=head1 Scalar containers
Although objects of type L<C<Scalar>|/type/Scalar> are everywhere in Perl 6, you
rarely see them directly as objects, because most operations I<decontainerize>,
which means they act on the C<Scalar> container's contents instead of the
container itself.
In code like
my $x = 42;
say $x;
the assignment C<$x = 42> stores a pointer to the C<Int> object 42 in the
scalar container to which the lexpad entry for C<$x> points.
The assignment operator asks the container on the left to store the value on
its right. What exactly that means is up to the container type. For
C<Scalar> it means "replace the previously stored value with the new one".
Note that subroutine signatures allow passing around of containers:
sub f($a is rw) {
$a = 23;
}
my $x = 42;
f($x);
say $x; # OUTPUT: «23»
Inside the subroutine, the lexpad entry for C<$a> points to the same
container that C<$x> points to outside the subroutine. Which is why
assignment to C<$a> also modifies the contents of C<$x>.
Likewise a routine can return a container if it is marked as C<is rw>:
my $x = 23;
sub f() is rw { $x };
f() = 42;
say $x; # OUTPUT: «42»
For explicit returns, C<return-rw> instead of C<return> must be used.
Returning a container is how C<is rw> attribute accessors work. So
class A {
has $.attr is rw;
}
is equivalent to
class A {
has $!attr;
method attr() is rw { $!attr }
}
Scalar containers are transparent to type checks and most kinds of read-only
accesses. A C<.VAR> makes them visible:
my $x = 42;
say $x.^name; # OUTPUT: «Int»
say $x.VAR.^name; # OUTPUT: «Scalar»
And C<is rw> on a parameter requires the presence of a writable Scalar
container:
sub f($x is rw) { say $x };
f 42;
CATCH { default { say .^name, ': ', .Str } };
# OUTPUT: «X::Parameter::RW: Parameter '$x' expected a writable container, but got Int value»
=head1 Callable containers
Callable containers provide a bridge between the syntax of a
L<Routine|/type/Routine> call and the actual call of the method
L<CALL-ME|/type/Callable#method_CALL-ME> of the object that is stored in the
container. The sigil C<&> is required when declaring the container and has to be
omitted when executing the C<Callable>. The default type constraint is
L<Callable|/type/Callable>.
my &callable = -> $ν { say "$ν is", $ν ~~ Int??" whole"!!" not whole" }
callable( ⅓ );
callable( 3 );
The sigil has to be provided when referring to the value stored in the
container. This in turn allows C<Routine>s to be used as
L<arguments|/type/Signature#Constraining_signatures_of_Callables> to calls.
sub f() {}
my &g = sub {}
sub caller(&c1, &c2){ c1, c2 }
caller(&f, &g);
=head1 Binding
Next to assignment, Perl 6 also supports I<binding> with the C<:=> operator.
When binding a value or a container to a variable, the lexpad entry of the
variable is modified (and not just the container it points to). If you write
my $x := 42;
then the lexpad entry for C<$x> directly points to the C<Int> 42. Which
means that you cannot assign to it anymore:
my $x := 42;
$x = 23;
CATCH { default { say .^name, ': ', .Str } };
# OUTPUT: «X::AdHoc: Cannot assign to an immutable value»
You can also bind variables to other variables:
my $a = 0;
my $b = 0;
$a := $b;
$b = 42;
say $a; # OUTPUT: «42»
Here, after the initial binding, the lexpad entries for C<$a> and C<$b> both
point to the same scalar container, so assigning to one variable also
changes the contents of the other.
You've seen this situation before: it is exactly what happened with the
signature parameter marked as C<is rw>.
X<|\ (container binding)>
Sigilless variables and parameters with the trait C<is raw> always
bind (whether C<=> or C<:=> is used):
my $a = 42;
my \b = $a;
b++;
say $a; # OUTPUT: «43»
sub f($c is raw) { $c++ }
f($a);
say $a; # OUTPUT: «44»
=head1 Scalar containers and listy things
There are a number of positional container types with slightly different
semantics in Perl 6. The most basic one is L<List|/type/List>;
it is created by the comma operator.
say (1, 2, 3).^name; # OUTPUT: «List»
A list is immutable, which means you cannot change the number of elements
in a list. But if one of the elements happens to be a scalar container,
you can still assign to it:
my $x = 42;
($x, 1, 2)[0] = 23;
say $x; # OUTPUT: «23»
($x, 1, 2)[1] = 23; # Cannot modify an immutable value
CATCH { default { say .^name, ': ', .Str } };
# OUTPUT: «X::Assignment::RO: Cannot modify an immutable Int»
So the list doesn't care about whether its elements are values or
containers, they just store and retrieve whatever was given to them.
Lists can also be lazy; in that case, elements at the end are generated on demand from an
iterator.
An C<Array> is just like a list, except that it forces all its elements to
be containers, which means that you can always assign to elements:
my @a = 1, 2, 3;
@a[0] = 42;
say @a; # OUTPUT: «[42 2 3]»
C<@a> actually stores three scalar containers. C<@a[0]> returns one of
them, and the assignment operator replaces the integer value stored in that
container with the new one, C<42>.
=head1 Assigning and binding to array variables
Assignment to a scalar variable and to an array variable both do
the same thing: discard the old value(s), and enter some new value(s).
Nevertheless, it's easy to observe how different they are:
my $x = 42; say $x.^name; # OUTPUT: «Int»
my @a = 42; say @a.^name; # OUTPUT: «Array»
This is because the C<Scalar> container type hides itself well, but C<Array>
makes no such effort. Also assignment to an array variable is coercive, so
you can assign a non-array value to an array variable.
To place a non-C<Array> into an array variable, binding works:
my @a := (1, 2, 3);
say @a.^name; # OUTPUT: «List»
=head1 Binding to array elements
As a curious side note, Perl 6 supports binding to array elements:
my @a = (1, 2, 3);
@a[0] := my $x;
$x = 42;
say @a; # OUTPUT: «[42 2 3]»
If you've read and understood the previous explanations, it is now time to
wonder how this can possibly work. After all, binding to a variable requires a
lexpad entry for that variable, and while there is one for an array, there
aren't lexpad entries for each array element, because you cannot expand the
lexpad at runtime.
The answer is that binding to array elements is recognized at the syntax
level and instead of emitting code for a normal binding operation, a special
method (called C<BIND-KEY>) is called on the array. This method handles binding
to array elements.
Note that, while supported, one should generally avoid directly binding
uncontainerized things into array elements. Doing so may produce
counter-intuitive results when the array is used later.
my @a = (1, 2, 3);
@a[0] := 42; # This is not recommended, use assignment instead.
my $b := 42;
@a[1] := $b; # Nor is this.
@a[2] = $b; # ...but this is fine.
@a[1, 2] := 1, 2; # runtime error: X::Bind::Slice
CATCH { default { say .^name, ': ', .Str } };
# OUTPUT: «X::Bind::Slice: Cannot bind to Array slice»
Operations that mix Lists and Arrays generally protect against such
a thing happening accidentally.
=head1 Flattening, items and containers
The C<%> and C<@> sigils in Perl 6 generally indicate multiple values
to an iteration construct, whereas the C<$> sigil indicates only one value.
my @a = 1, 2, 3;
for @a { }; # 3 iterations
my $a = (1, 2, 3);
for $a { }; # 1 iteration
C<@>-sigiled variables do not flatten in list context:
my @a = 1, 2, 3;
my @b = @a, 4, 5;
say @b.elems; # OUTPUT: «3»
There are operations that flatten out sublists that are not inside a scalar
container: slurpy parameters (C<*@a>) and explicit calls to C<flat>:
my @a = 1, 2, 3;
say (flat @a, 4, 5).elems; # OUTPUT: «5»
sub f(*@x) { @x.elems };
say f @a, 4, 5; # OUTPUT: «5»
You can also use C<|> to create a L<Slip|/type/Slip>, introducing a list into the other.
my @l := 1, 2, (3, 4, (5, 6)), [7, 8, (9, 10)];
say (|@l, 11, 12); # OUTPUT: «(1 2 (3 4 (5 6)) [7 8 (9 10)] 11 12)»
say (flat @l, 11, 12) # OUTPUT: «(1 2 3 4 5 6 7 8 (9 10) 11 12)»
In the first case, every element of C<@l> is I<slipped> as the corresponding
elements of the resulting list. C<flat>, in the other hand, I<flattens> all
elements including the elements of the included array, except for C«(9 10)».
As hinted above, scalar containers prevent that flattening:
sub f(*@x) { @x.elems };
my @a = 1, 2, 3;
say f $@a, 4, 5; # OUTPUT: «3»
The C<@> character can also be used as a prefix to coerce the argument to a
list, thus removing a scalar container:
my $x = (1, 2, 3);
.say for @$x; # 3 iterations
However, the I<decont> operator C«<>» is more appropriate to decontainerize
items that aren't lists:
my $x = ^Inf .grep: *.is-prime;
say "$_ is prime" for @$x; # WRONG! List keeps values, thus leaking memory
say "$_ is prime" for $x<>; # RIGHT. Simply decontainerize the Seq
my $y := ^Inf .grep: *.is-prime; # Even better; no Scalars involved at all
Methods generally don't care whether their invocant is in a scalar, so
my $x = (1, 2, 3);
$x.map(*.say); # 3 iterations
maps over a list of three elements, not of one.
=head1 Self-referential data
Containers types, including C<Array> and C<Hash>, allow you to create
self-referential structures.
my @a;
@a[0] = @a;
put @a.perl;
# OUTPUT: «((my @Array_75093712) = [@Array_75093712,])»
Although Perl 6 does not prevent you from creating and using self-referential
data, by doing so you may end up in a loop trying to dump the data. As a
last resort, you can use Promises to L<handle|/type/Promise#method_in> timeouts.
=head1 Type constraints
Any container can have a type constraint in the form of a L<type
object|/language/typesystem#Type_objects> or a
L<subset|/language/typesystem#subset>. Both can be placed between a declarator
and the variable name or after the trait L<of|/type/Variable#trait_is_dynamic>.
The constraint is a property of the variable, not the container.
subset Three-letter of Str where .chars == 3;
my Three-letter $acronym = "ÞFL";
In this case, the type constraint is the (compile-type defined) subset
C<Three-letter>.
Variables may have no container in them, yet still offer the ability to
re-bind and typecheck that rebind. The reason for that is in such cases the
binding operator L<:=|/language/operators#infix_:=> performs the typecheck:
my Int \z = 42;
z := 100; # OK
z := "x"; # Typecheck failure
The same isn't the case when, say, binding to a L<Hash|/type/Hash> key, as the binding is
then handled by a method call (even though the syntax remains the same, using
C<:=> operator).
The default type constraint of a C<Scalar> container is L<Mu|/type/Mu>. Introspection of
type constraints on containers is provided by C<.VAR.of> method, which for C<@>
and C<%> sigiled variables gives the constraint for values:
my Str $x;
say $x.VAR.of; # OUTPUT: «(Str)»
my Num @a;
say @a.VAR.of; # OUTPUT: «(Num)»
my Int %h;
say %h.VAR.of; # OUTPUT: «(Int)»
=head2 Definedness constraints
A container can also enforce a variable to be defined. Put a smiley in the
declaration:
my Int:D $def = 3;
say $def; # OUTPUT: «3»
$def = Int; # Typecheck failure
You'll also need to initialize the variable in the declaration, it can't be
left undefined after all.
It's also possible to have this constraint enforced in all variables declared
in a scope with the
L<default defined variables pragma|/language/variables#Default_defined_variables_pragma>.
People coming from other languages where variables are always defined will
want to have a look.
=head1 Custom containers
To provide custom containers Perl 6 provides the class C<Proxy>. It takes two
methods that are called when values are stored or fetched from the container.
Type checks are not done by the container itself and other restrictions like
readonlyness can be broken. The returned value must therefore be of the same
type as the type of the variable it is bound to. We can use type captures to
work with types in Perl 6.
sub lucky(::T $type) {
my T $c-value; # closure variable
return Proxy.new(
FETCH => method () { $c-value },
STORE => method (T $new-value) {
X::OutOfRange.new(what => 'number', got => '13', range => '-∞..12, 14..∞').throw
if $new-value == 13;
$c-value = $new-value;
}
);
}
my Int $a := lucky(Int);
say $a = 12; # OUTPUT: «12»
say $a = 'FOO'; # X::TypeCheck::Binding
say $a = 13; # X::OutOfRange
CATCH { default { say .^name, ': ', .Str } };
=end pod
# vim: expandtab softtabstop=4 shiftwidth=4 ft=perl6