Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type coercion in Perl6 class attribute

Like most things in Perl5, there are many ways to create a class that supports custom type coercions for its attributes. Here's a simple one, from an array reference to a hash:

#!/usr/bin/env perl

package Local::Class {
  use Moo;
  use Types::Standard qw( HashRef ArrayRef );

  has set => (
    is => 'ro',
    coerce => 1,
    isa => HashRef->plus_coercions(
      ArrayRef, sub { return { map { $_ => 1} @{$_[0]} } },
    ),
  );
}

my $o = Local::Class->new({ set => [qw( a b b c )] });
# $o->set now holds { a => 1, b => 1, c => 1}

I've been trying to port something like this to Perl6, where it seems like what I need is a way to coerce an Array to a SetHash. So far, the only way I've been able to do that is like this:

#!/usr/bin/env perl6

class Local::Class {
  has %.set;
  ## Wanted to have
  # has %.set is SetHash;
  ## but it dies with "Cannot modify an immutable SetHash"

  submethod TWEAK (:$set) {
    self.set = $set.SetHash;
  }
}

my $o = Local::Class.new( set => [< a b b c >] );
# $o.set now holds {:a, :b, :c}

But this doesn't seem to me to be the right way to do it, at the very least for the tiny detail that passing an odd-numbered list to the constructor makes the script die.

So, how is this done in Perl6? What are the recommended ways (because I'm sure there's more than one) to implement custom type coercions for class attributes?

like image 521
jja Avatar asked Dec 02 '16 02:12

jja


1 Answers

TWEAK runs after the object has been intialized by BUILD, and that's where providing an odd-numbered array will blow up.

Move the coercion to BUILD time, and things should work as expected:

class Local::Class {
  has %.set;
  submethod BUILD (:$set) {
    %!set := $set.SetHash;
  }
}

It would be nice if you could use a coercing SetHash() parameter type in combination with automatic attribute initialization, but this will fail as the sigil is part of the attribute's name, and the parameter cannot be %-sigilled if you want to accept non-associative types.

However, it works fine if you use a $-sigilled attribute instead:

class Local::Class {
  has $.set;
  submethod BUILD (SetHash() :$!set) {}
}

As @smls points out, the following variant

class Local::Class {
  has %.set is SetHash;
  submethod BUILD (:$set) {
    %!set = $set.SetHash;
  }
}

probably should work as well instead of dying with Cannot modify an immutable SetHash.

As a workaround for SetHash not being assignable, you could use

class Local::Class {
  has %.set is SetHash;
  submethod BUILD (:$set) {
    %!set{$set.SetHash.keys} = True xx *;
  }
}
like image 103
Christoph Avatar answered Sep 29 '22 08:09

Christoph