Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I return 2 hashes from a raku sub?

...

#!/usr/bin/env raku
# -*-perl6-*-
# 2021-5-30: Example of how a sub does not seem to be able to return 2 Hashes...


sub GetHashes 
{
    my %H =  100 => 2149, 101 => 2305, 102 => 2076, 103 => 1767, 104 => 1743 ;
    my %G =  100 => 21493, 101 => 23053, 102 => 20763, 103 => 17673, 104 => 17433 ;
    return( %H, %G );
}

my ( %H, %G ) = GetHashes ;

%H.say;
%G.say;

...

I'm an old FORTRAN/perl5 programmer, who decided to try to learn raku. The above code does not work. What am I missing? In perl5 I would return ref's to the hashes, and de-ref in the caller. What here?

like image 270
GRM Avatar asked May 30 '21 22:05

GRM


2 Answers

What am I missing?

Two answers among many possible answers are:

  1. You're missing a colon.

    Specifically, you can change:

    my ( %H, %G ) = GetHashes ;
    

    to:

    my ( %H, %G ) := GetHashes ;
                  ^
    

  1. You're missing @p6steve's answer.

    Specifically, you can change:

    my ( %H, %G ) = GetHashes ;
    

    to:

    my ( $H, $G ) = GetHashes ;
    

The summary is that Raku supports multiple ways to make some storage location(s) either "contain" some data or "be a reference to" some data. You were using "list assignment" but need to use one of the alternatives, such as our answers.

Assignment (=)

Assignment means copying data to some storage location(s) that end up "containing" the copied data.[1]

If you're using list assignment, then the more items in the list, the more bytes get copied. If you've got a million items, it's unlikely you want to assign them using list assignment. And there can be other reasons to not want to use assignment, as you have discovered.


Consider, for example, my @array1 = 1, 2. This code:

  1. Constructs a List of two elements -- (1, 2) -- as the data to be assigned/copied;

  2. Constructs a new empty Array and "binds" this new array to the "symbol" (variable name) @array1;

  3. Iterates the List's elements and, for each:

    3.1 Adds a new corresponding empty "container" (a Scalar) to the target array, ready to receive some scalar data;

    3.2 Copies a scalar (single) value from the List into the new Scalar container in the array.

Note how this ends up creating three storage locations at run-time: an array, and two individual Scalars as the first two indexable elements of that array. All three of these storage locations get updated, with the array getting two Scalars added to it, and the two Scalars receiving the two values copied from the list.

Binding (:=)

Binding means copying references (pointers) to data. The data itself is not copied. If there are, say, two references to data, then very little gets copied, no matter how large the data is. One way to stick to copying references, rather than the data being referred to, is to use binding.


Consider, for example my @array2 := 1, 2. This code:

  1. Constructs a List of two elements (1, 2);

  2. Binds a reference to the List to the "symbol" (variable name) @array2.

Note how, unlike the assignment example above, this binding example ends up with just one storage location (@array2) on the left of the := being updated, corresponding to the right being treated as a single value (a List).


There can be multiple storage locations on the left of a binding operation. Consider your example, my ( %H, %G ) := GetHashes;. This code:

  1. Evaluates GetHashes, returning a List of two hashes;

  2. Iterates the list of values/references on the right of the :=, binding each in turn to the list of target storage locations on the left of the :=.

Note how, unlike the previous binding example, this one ends up with two storage locations (associated with the symbols %H and %G) being updated, corresponding to the two hashes in the List returned by the GetHashes call.

The effect of the list assignment in your original code

With your code as it was, you were copying all of the data/values from the right hand side of the =, item by item (so a total of 10 copying acts), into the hash bound to the first variable (%H) on the left.

This is due to the precise semantics of assignment (use of =) which are determined by the individual targets on the left of the =, starting with the first, until it's had its fill, then the second, and so on.

If an assignment target is an Associative (eg %H), it greedily slurps everything it can from the right of the =. This is called "list assignment".

So what happens with your original code is that %H slurps up the first five elements in the first hash returned by GetHashes, then keeps going, so the five elements from the second hash returned by the sub are also assigned to %H.

And because the keys of this second batch of pairs are the same as the keys of the first, this second batch overwrites the first five.

So %H ends up with just the second batch of pairs, and %G stays empty, the result you see.


A similar deal applies for Positional assignment targets, eg a variable named @array.

The only assignment targets in standard Raku that are not greedy in this fashion are Scalar targets such as $bar. So you can usefully write, say, my ($bar, @bam) = 1, 2, 3; and end up with $bar assigned 1, and @bam ending up as [2 3]. Hence @p6steve's answer.

Footnotes

[1] If = is used in some code, but assignment to containers on the left isn't what that expression means, Raku does what the writer presumably means instead. For example:

  • constant list = 1, 2, 3; does not temporarily assign the List (1, 2, 3) to the symbol list at run-time but instead permanently binds it at compile-time. In other words, it does the same as constant list := 1, 2, 3;. (Most folk just write constant list = 1, 2, 3;.)

  • my int @list = 1, 2, 3; does not add any Scalar containers to the native array @list but instead directly writes native integers.

like image 179
raiph Avatar answered Oct 19 '22 19:10

raiph


Well, I am a guilty addict of assignment and I like many of the perl5 reference styles since I grew up with them. As you might expect, Raku has some other ways to handle this situation:

my ( $H, $G ) = GetHashes ;
$H.say;
$G.say;

In this case, Raku is smart enough to automatically decont the scalars and say the underlying hash. This transparency (ie. auto dereferencing) is one of the improvements of Raku over perl5 and a marked change in how the sigils work. Takes a bit of getting used to - but very powerful and concise IMO.

There is a lot of helpful tutorial information in the Raku (Perl6) advent posts --- see this one for an excellent survey.

like image 36
p6steve Avatar answered Oct 19 '22 19:10

p6steve