-
Notifications
You must be signed in to change notification settings - Fork 293
/
hashmap.pod6
496 lines (369 loc) · 17.6 KB
/
hashmap.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
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
=begin pod :tag<perl6>
=TITLE Hashes and maps
=SUBTITLE Working with associative arrays/dictionaries/hashes
=head1 The associative role and associative classes
The L<Associative|/type/Associative> role underlies hashes and maps, as well as other classes such
as L<MixHash|/type/MixHash>. It defines the two types that will be used in associative
classes; by default, you can use anything (literally, since any class that
subclasses L<Any|/type/Any> can be used) L<as a key|#Non-string_keys_(object_hash),
although it will be coerced to a string, and any object as value. You can
access these types using the C<of> and C<keyof> methods.
By default, any object declared with the C<%> sigil will get the Associative
role, and will by default behave like a hash, but this role will only provide
the two methods above, as well as the default Hash behavior.
say (%).^name ; # OUTPUT: «Hash»
Inversely, you cannot use the C<%> sigil if the C<Associative> role is not mixed
in, but since this role does not have any associated properties, you will have
to redefine the behavior of the L<hash subscript
operator|/language/operators#postcircumfix_{_}>. In order to do that, there
are several functions you will have to override:
=begin code
class Logger does Associative[Cool,DateTime] {
has %.store;
method log( Cool $event ) {
%.store{ DateTime.new( now ) } = $event;
}
multi method AT-KEY ( ::?CLASS:D: $key) {
my @keys = %.store.keys.grep( /$key/ );
%.store{ @keys };
}
multi method EXISTS-KEY (::?CLASS:D: $key) {
%.store.keys.grep( /$key/ )??True!!False;
}
multi method DELETE-KEY (::?CLASS:D: $key) {
X::Assignment::RO.new.throw;
}
multi method ASSIGN-KEY (::?CLASS:D: $key, $new) {
X::Assignment::RO.new.throw;
}
multi method BIND-KEY (::?CLASS:D: $key, \new){
X::Assignment::RO.new.throw;
}
}
say Logger.of; # OUTPUT: «(Cool)»
my %logger := Logger.new;
say %logger.of; # OUTPUT: «(Cool)»
%logger.log( "Stuff" );
%logger.log( "More stuff");
say %logger<2018-05-26>; # OUTPUT: «(More stuff Stuff)»
say %logger<2018-04-22>:exists; # OUTPUT: «False»
=end code
In this case, we are defining a logger with Associative semantics that would be
able to use dates (or a part of them) as keys. Since we are parameterizing
C<Associative> to those particular classes, C<of> will return the value type we
have used, C<Cool> in this case (we can log lists or strings only). Mixing the C<Associative> role gives it the right to use the C<%> sigil; binding is needed in the definition since C<%>-sigilled variables get by default the C<Hash> type.
This log is going to be append-only, that is why we escape the associative array
metaphor to use a C<log> method to add new events to the log. Once they have
been added, however, we can retrieve them by date or check if they exist. For
the first we have to override the C<AT-KEY> multi method, for the latter
C<EXIST-KEY>. In the last two statements, we show how the subscript operation
invokes C<AT-KEY>, while the C<:exists> adverb invokes C<EXISTS-KEY>.
We override C<DELETE-KEY>, C<ASSIGN-KEY> and C<BIND-KEY>, but only to throw an
exception. Attempting to assign, delete or bind a value to a key will result in
a C<Cannot modify an immutable Str (value)> exception thrown.
Making classes associative provides a very convenient way of using and working
with them using hashes; an example can be seen in
L<Cro|https://cro.services/docs/reference/cro-http-client#Setting_the_request_body>,
which uses it extensively for the convenience of using hashes to define
structured requests and express its response.
=head1 Mutable hashes and immutable maps
A Hash is a mutable mapping from keys to values (called I<dictionary>, I<hash
table> or I<map> in other programming languages). The values are all scalar
containers, which means you can assign to them. L<Map|/type/Map>s are, on the other hand,
immutable. Once a key has been paired with a value, this pairing cannot be
changed.
Maps and hashes are usually stored in variables with the percent C<%> sigil,
which is used to indicate they are Associative.
Hash and map elements are accessed by key via the C<{ }> postcircumfix operator:
say %*ENV{'HOME', 'PATH'}.perl;
# OUTPUT: «("/home/camelia", "/usr/bin:/sbin:/bin")»
The general L<Subscript|/language/subscripts> rules apply providing shortcuts
for lists of literal strings, with and without interpolation.
my %h = oranges => 'round', bananas => 'bendy';
say %h<oranges bananas>;
# OUTPUT: «(round bendy)»
my $fruit = 'bananas';
say %h«oranges "$fruit"»;
# OUTPUT: «(round bendy)»
You can add new pairs simply by assigning to an unused key:
my %h;
%h{'new key'} = 'new value';
=head1 Hash assignment
Assigning a list of elements to a hash variable first empties the variable,
and then iterates the elements of the right-hand side. If an element is a
L<Pair|/type/Pair>, its key is taken as a new hash key, and its value as the new hash
value for that key. Otherwise the value is coerced to L<Str|/type/Str> and used as a
hash key, while the next element of the list is taken as the corresponding
value.
my %h = 'a', 'b', c => 'd', 'e', 'f';
Same as
my %h = a => 'b', c => 'd', e => 'f';
or
my %h = <a b c d e f>;
or even
my %h = %( a => 'b', c => 'd', e => 'f')
or
my $h = { a => 'b', c => 'd', e => 'f'};
Please note that curly braces are used only in the case that we are not
assigning it to a C<%>-sigilled variable; in case we use it for a C<%>-sigilled
variable we will get an C<Potential difficulties: Useless use of hash
composer on right side of hash assignment; did you mean := instead?> error. As
this error indicates, however, we can use curly braces as long as we use also
binding:
my %h := { a => 'b', c => 'd', e => 'f'};
say %h; # OUTPUT: «{a => b, c => d, e => f}»
Nested hashes can also be defined using the same syntax:
my %h = e => f => 'g';
say %h<e><f>; # OUTPUT: «g»
However, what you are defining here is a key pointing to a L<Pair|/type/Pair>, which is
fine if that is what you want, and if your nested hash has got a single key. But
C<%h<e>> will point to a C<Pair> which will have these consequences:
=begin code
my %h = e => f => 'g';
%h<e><q> = 'k';
# OUTPUT: «(exit code 1) PairCannot modify an immutable Str (Nil) in block <unit>»
=end code
This, however, will effectively define a nested hash:
my %h = e => { f => 'g' };
say %h<e>.^name; # OUTPUT: «Hash»
say %h<e><f>; # OUTPUT: «g»
If a L<Pair|/type/Pair> is encountered where a value is expected, it is used as a
hash value:
my %h = 'a', 'b' => 'c';
say %h<a>.^name; # OUTPUT: «Pair»
say %h<a>.key; # OUTPUT: «b»
If the same key appears more than once, the value associated with its last
occurrence is stored in the hash:
my %h = a => 1, a => 2;
say %h<a>; # OUTPUT: «2»
To assign a hash to a variable which does not have the C<%> sigil, you may use
the C<%()> hash constructor:
my $h = %( a => 1, b => 2 );
say $h.^name; # OUTPUT: «Hash»
say $h<a>; # OUTPUT: «1»
If one or more values reference the topic variable, C<$_>, the right-hand side
of the assignment will be interpreted as a L<Block|/type/Block>, not a Hash:
=begin code :skip-test
my @people = [
%( id => "1A", firstName => "Andy", lastName => "Adams" ),
%( id => "2B", firstName => "Beth", lastName => "Burke" ),
# ...
];
sub lookup-user (Hash $h) { #`(Do something...) $h }
my @names = map {
# While this creates a hash:
my $query = { name => "$person<firstName> $person<lastName>" };
say $query.^name; # OUTPUT: «Hash»
# Doing this will create a Block. Oh no!
my $query2 = { name => "$_<firstName> $_<lastName>" };
say $query2.^name; # OUTPUT: «Block»
say $query2<name>; # fails
CATCH { default { put .^name, ': ', .Str } };
# OUTPUT: «X::AdHoc: Type Block does not support associative indexing.»
lookup-user($query); # Type check failed in binding $h; expected Hash but got Block
}, @people;
=end code
This would have been avoided if you had used the C<%()> hash constructor.
Only use curly braces for creating Blocks.
=head2 Hash slices
You can assign to multiple keys at the same time with a slice.
my %h; %h<a b c> = 2 xx *; %h.perl.say; # OUTPUT: «{:a(2), :b(2), :c(2)}»
my %h; %h<a b c> = ^3; %h.perl.say; # OUTPUT: «{:a(0), :b(1), :c(2)}»
X<|non-string keys>
X<|object hash>
X<|:{}>
=head2 Non-string keys (object hash)
By default keys in C<{ }> are forced to strings. To compose a hash with
non-string keys, use a colon prefix:
my $when = :{ (now) => "Instant", (DateTime.now) => "DateTime" };
Note that with objects as keys, you often cannot use the C«<...>» construct
for key lookup, as it creates only strings and
L<allomorphs|/language/glossary#index-entry-Allomorph>. Use the C«{...}»
instead:
:{ 0 => 42 }<0>.say; # Int as key, IntStr in lookup; OUTPUT: «(Any)»
:{ 0 => 42 }{0}.say; # Int as key, Int in lookup; OUTPUT: «42»
:{ '0' => 42 }<0>.say; # Str as key, IntStr in lookup; OUTPUT: «(Any)»
:{ '0' => 42 }{'0'}.say; # Str as key, Str in lookup; OUTPUT: «42»
:{ <0> => 42 }<0>.say; # IntStr as key, IntStr in lookup; OUTPUT: «42»
Note: Rakudo implementation currently erroneously applies L<the same
rules|/routine/{ }#(Operators)_term_{_}> for C<:{ }> as it does for C<{ }> and
can construct a L<Block|/type/Block> in certain circumstances. To avoid that, you can
instantiate a parameterized Hash directly. Parameterization of C<%>-sigilled
variables is also supported:
my Num %foo1 = "0" => 0e0; # Str keys and Num values
my %foo2{Int} = 0 => "x"; # Int keys and Any values
my Num %foo3{Int} = 0 => 0e0; # Int keys and Num values
Hash[Num,Int].new: 0, 0e0; # Int keys and Num values
Now if you want to define a hash to preserve the objects you are using
as keys I<as the B<exact> objects you are providing to the hash to use as keys>,
then object hashes are what you are looking for.
my %intervals{Instant};
my $first-instant = now;
%intervals{ $first-instant } = "Our first milestone.";
sleep 1;
my $second-instant = now;
%intervals{ $second-instant } = "Logging this Instant for spurious raisins.";
for %intervals.sort -> (:$key, :$value) {
state $last-instant //= $key;
say "We noted '$value' at $key, with an interval of {$key - $last-instant}";
$last-instant = $key;
}
This example uses an object hash that only accepts keys of type L<Instant|/type/Instant> to
implement a rudimentary, yet type-safe, logging mechanism. We utilize a named
L<state|/language/variables#The_state_declarator> variable for keeping track of
the previous C<Instant> so that we can provide an interval.
The whole point of object hashes is to keep keys as objects-in-themselves.
Currently object hashes utilize the L<WHICH|/routine/WHICH> method of an object,
which returns a unique identifier for every mutable object. This is the keystone
upon which the object identity operator (L<===>) rests. Order and containers
really matter here as the order of C<.keys> is undefined and one anonymous list
is never L<===> to another.
my %intervals{Instant};
my $first-instant = now;
%intervals{ $first-instant } = "Our first milestone.";
sleep 1;
my $second-instant = now;
%intervals{ $second-instant } = "Logging this Instant for spurious raisins.";
say ($first-instant, $second-instant) ~~ %intervals.keys; # OUTPUT: «False»
say ($first-instant, $second-instant) ~~ %intervals.keys.sort; # OUTPUT: «False»
say ($first-instant, $second-instant) === %intervals.keys.sort; # OUTPUT: «False»
say $first-instant === %intervals.keys.sort[0]; # OUTPUT: «True»
Since C<Instant> defines its own comparison methods, in our example a sort
according to L<cmp|/routine/cmp> will always provide the earliest instant object as the first
element in the L<List|/type/List> it returns.
If you would like to accept any object whatsoever in your hash, you can use L<Any|/type/Any>!
my %h{Any};
%h{(now)} = "This is an Instant";
%h{(DateTime.now)} = "This is a DateTime, which is not an Instant";
%h{"completely different"} = "Monty Python references are neither DateTimes nor Instants";
There is a more concise syntax which uses binding.
my %h := :{ (now) => "Instant", (DateTime.now) => "DateTime" };
The binding is necessary because an object hash is about very solid, specific objects,
which is something that binding is great at keeping track of but about which assignment doesn't
concern itself much.
=head2 Constraint value types
Place a type object in-between the declarator and the name to constrain the type
of all values of a C<Hash>.
my Int %h;
put %h<Goku> = 900;
try {
%h<Vegeta> = "string";
CATCH { when X::TypeCheck::Binding { .message.put } }
}
# OUTPUT:
# 9001
# Type check failed in assignment to %h; expected Int but got Str ("string")
You can do the same by a more readable syntax.
my %h of Int; # the same as my Int %h
If you want to constraint the type of all keys of a C<Hash>, add C<{Type}> following
the name of variable.
my %h{Int};
Even put these two constriant together.
my %h{Int} of Int;
put %h{21} = 42;
try {
%h{0} = "String";
CATCH { when X::TypeCheck::Binding { .message.put } }
}
try {
%h<string> = 42;
CATCH { when X::TypeCheck::Binding { .message.put } }
}
try {
%h<string> = "String";
CATCH { when X::TypeCheck::Binding { .message.put } }
}
# OUTPUT:
# 42
# Type check failed in binding to parameter 'assignval'; expected Int but got Str ("String")
# Type check failed in binding to parameter 'key'; expected Int but got Str ("string")
# Type check failed in binding to parameter 'key'; expected Int but got Str ("string")
=head1 Looping over hash keys and values
A common idiom for processing the elements in a hash is to loop over the
keys and values, for instance,
my %vowels = 'a' => 1, 'e' => 2, 'i' => 3, 'o' => 4, 'u' => 5;
for %vowels.kv -> $vowel, $index {
"$vowel: $index".say;
}
gives output similar to this:
=for code :lang<output>
a: 1
e: 2
o: 4
u: 5
i: 3
where we have used the C<kv> method to extract the keys and their respective
values from the hash, so that we can pass these values into the loop.
Note that the order of the keys and values printed cannot be relied upon; the
elements of a hash are not always stored the same way in memory for different
runs of the same program. In fact, since version 2018.05, the order is
guaranteed to be different in every invocation. Sometimes one wishes to process
the elements sorted on, e.g., the keys of the hash. If one wishes to print the
list of vowels in alphabetical order then one would write
my %vowels = 'a' => 1, 'e' => 2, 'i' => 3, 'o' => 4, 'u' => 5;
for %vowels.sort(*.key)>>.kv -> ($vowel, $index) {
"$vowel: $index".say;
}
which prints
=for code :lang<output>
a: 1
e: 2
i: 3
o: 4
u: 5
in alphabetical order as desired. To achieve this result, we sorted
the hash of vowels by key (C<%vowels.sort(*.key)>) which we then ask for its
keys and values by applying the C<.kv> method to each element via the unary
C«>>» hyperoperator resulting in a L<List|/type/List> of key/value lists. To extract
the key/value the variables thus need to be wrapped in parentheses.
An alternative solution is to flatten the resulting list. Then the key/value
pairs can be accessed in the same way as with plain C<.kv>:
my %vowels = 'a' => 1, 'e' => 2, 'i' => 3, 'o' => 4, 'u' => 5;
for %vowels.sort(*.key)>>.kv.flat -> $vowel, $index {
"$vowel: $index".say;
}
You can also loop over a C<Hash> using
L<destructuring|/type/Signature#Destructuring_arguments>.
=head2 In place editing of values
There may be times when you would like to modify the values of a hash while
iterating over them.
my %answers = illuminatus => 23, hitchhikers => 42;
# OUTPUT: «hitchhikers => 42, illuminatus => 23»
for %answers.values -> $v { $v += 10 }; # Fails
CATCH { default { put .^name, ': ', .Str } };
# OUTPUT: «X::AdHoc: Cannot assign to a readonly variable or a value»
This is traditionally accomplished by sending both the key and the value as
follows.
my %answers = illuminatus => 23, hitchhikers => 42;
for %answers.kv -> $k,$v { %answers{$k} = $v + 10 };
However, it is possible to leverage the signature of the block in order to
specify that you would like read-write access to the values.
my %answers = illuminatus => 23, hitchhikers => 42;
for %answers.values -> $v is rw { $v += 10 };
It is not possible directly to do in-place editing of hash keys, even in the
case of object hashes; however, a key can be deleted and a new key/value pair
added to achieve the same results. For example, given this hash:
=begin code
my %h = a => 1, b => 2;
for %h.keys.sort -> $k {
# use sort to ease output comparisons
print "$k => {%h{$k}}; ";
}
say ''; # OUTPUT: «a => 1; b => 2;»
=end code
replace key 'b' with 'bb' but retain 'b's value as the new key's value:
=begin code
for %h.keys -> $k {
if $k eq 'b' {
my $v = %h{$k};
%h{$k}:delete;
%h<bb> = $v;
}
}
for %h.keys.sort -> $k {
print "$k => {%h{$k}}; ";
}
say ''; # OUTPUT: «a => 1; bb => 2;»
=end code
=end pod
# vim: expandtab softtabstop=4 shiftwidth=4 ft=perl6