How to create an object in Perl6 from a hash?

In Perl5, you can do something like this:

#!/usr/bin/env perl use 5.010; package Local::Class { use Moo; has [qw( xy )] => ( is => 'ro'); sub BUILDARGS { shift; return (@_) ? (@_ > 1) ? { @_ } : shift : {} } } use Local::Class; # Create object directly my $x = Local::Class->new( x => 1, y => 10 ); say $x->x, ' ', $x->y; # 1 10 # Arguments from a hash my %hash = ( x => 5, y => 20 ); $x = Local::Class->new(%hash); say $x->x, ' ', $x->y; # 5 20 # Arguments from a hash reference $x = Local::Class->new(\%hash); say $x->x, ' ', $x->y; # 5 20 

The two calls below work the same way because of the custom BUILDARGS method, which basically turns both of them into the hash types expected by Moo (se) ?.

But how can I do the same in Perl6?

 #!/usr/bin/env perl6 class Local::Class { has $.x; has $.y; } my $x; # This works $x = Local::Class.new( x => 1, y => 10 ); say $xx, ' ', $xy; # 1 10 # This doesn't my %hash = %( x => 5, y => 20 ); $x = Local::Class.new(%hash); # This doesn't either $x = Local::Class.new(item(%hash)); # Both die with: # Default constructor for 'Local::Class' only takes named arguments 

So, how can I take the hash that was created elsewhere and convert it to the kind of named arguments needed by the default constructor for the class?

+5
source share
1 answer

Using the default constructor

The .new constructor .new by default, maps arguments to a name for public attributes.

In your example, you pass the hash as a positional argument. You can use the syntax | to interpolate hash entries in the argument list as named arguments:

 $x = Local::Class.new(|%hash); 

However, note that this will cause problems if your class has an array attribute, for example has @.z :

 class Local::Class { has $.x; has $.y; has @.z; } my %hash = x => 5, y => 20, z => [1, 2]; my $x = Local::Class.new(|%hash); say $x; # Local::Class.new(x => 5, y => 20, z => [[1, 2],]) 

This is because, like all hashes, %hash places each of its values ​​in an element container. Thus, the attribute will be initialized as @.z = $([1, 2]) , which leads to an array of one element, which is the original array.

One way to avoid this is to use Capture instead of Hash :

 my $capture = \( x => 5, y => 20, z => [1, 2] ); my $x = Local::Class.new(|$capture); say $x; # Local::Class.new(x => 5, y => 20, z => [1, 2]) 

Or use Hash , but then deconter its values ​​with <> and turn it all into a Map (which, unlike Hash , will not add element containers back) before putting it into the argument list:

 my %hash = x => 5, y => 20, z => [1, 2]; my $x = Local::Class.new(|Map.new: (.key => .value<> for %hash)); say $x; # Local::Class.new(x => 5, y => 20, z => [1, 2]) 

Using custom constructor

If you prefer to deal with this in the class itself rather than in the code that the class uses, you can change the constructor to your liking.

Note that the default .new calls .bless to actually allocate an object, which in turn calls .BUILD to handle attribute initialization.

Thus, the easiest way is to keep the standard .new implementation, but provide a custom .BUILD . You can map named arguments to attributes directly in your signature, so the BUILD body can actually remain empty:

 class Local::Class { has $.x; has $.y; has @.z; submethod BUILD (:$!x, :$!y, :@!z) { } } my %hash = x => 5, y => 20, z => [1, 2]; my $x = Local::Class.new(|%hash); say $x; # Local::Class.new(x => 5, y => 20, z => [1, 2]) 

Associating an array-in-element-container with the @ parameter automatically removes the element's container, so it does not suffer from the array-in-array problem described above.

The downside is that you have to list all the public attributes of your class in the BUILD parameter list. In addition, you still have to interpolate the hash with | in code that uses the class.

To get around both of these limitations, you can implement a custom .new like this:

 class Local::Class { has $.x; has $.y; has @.z; method new (%attr) { self.bless: |Map.new: (.key => .value<> for %attr) } } my %hash = x => 5, y => 20, z => [1, 2]; my $x = Local::Class.new(%hash); say $x; # Local::Class.new(x => 5, y => 20, z => [1, 2]) 
+4
source

Source: https://habr.com/ru/post/1260618/


All Articles