Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected Hash flattening

Tags:

hashmap

raku

I'm looking for explanation why those two data structures are not equal:

$ perl6 -e 'use Test; is-deeply [ { a => "b" } ], [ { a => "b" }, ];'
not ok 1 -
# Failed test at -e line 1
# expected: $[{:a("b")},]
#      got: $[:a("b")]

Trailing comma in Hashes and Arrays is meaningless just like in P5:

$ perl6 -e '[ 1 ].elems.say; [ 1, ].elems.say'
1
1

But without it Hash is somehow lost and it gets flattened to array of Pairs:

$ perl6 -e '[ { a => "b", c => "d" } ].elems.say;'
2

I suspect some Great List Refactor laws apply here but I'd like to get more detailed explanation to understand logic behind this flattening.

like image 274
Pawel Pabian bbkr Avatar asked May 20 '18 21:05

Pawel Pabian bbkr


1 Answers

Trailing comma in Hashes and Arrays is meaningless just like in P5

No, it's not meaningless:

(1 ).WHAT.say ; # (Int)
(1,).WHAT.say ; # (List)

The big simplification in the Great List Refactor was switching to the single argument rule for iterating features1. That is to say, features like a for or the array and hash composers (and subscripts) always get a single argument. That is indeed what's going on with your original example.

The single argument may be -- often will be -- a list of values, possibly even a list of lists etc., but the top level list would still then be a single argument to the iterating feature.

If the single argument to an iterating feature does the Iterable role (for example lists, arrays, and hashes), then it's iterated. (This is an imprecise formulation; see my answer to "When does for call the iterator method?" for a more precise one.)

So the key thing to note here about that extra comma is that if the single argument does not do the Iterable role, such as 1, then the end result is exactly the same as if the argument were instead a list containing just that one value (i.e. 1,):

.perl.say for {:a("b")}   ; # :a("b")     Iterable Hash was iterated
.perl.say for {:a("b")} , ; # {:a("b")}   Iterable List was iterated
.perl.say for 1           ; # 1           Non Iterable 1 left as is
.perl.say for 1 ,         ; # 1           Iterable List was iterated

The typical way "to preserve structure [other than] using trailing comma when single element list is declared" (see comment below), i.e. to stop a single Iterable value being iterated as it normally would, is by item-izing it with a $:

my @t = [ $[ $[ "a" ] ] ];
@t.push: "b";
@t.perl.say; # [[["a"],], "b"]

1 The iteration is used to get values to be passed to some code in the case of a for; to get values to become elements of the array/hash being constructed in the case of a composer; to get an indexing slice in the case of a subscript; and so on for other iterating features.

like image 171
raiph Avatar answered Oct 19 '22 23:10

raiph