Search code examples
perltypesmoo

How do I check if an object is valid when pushed to an ArrayOf[] member in my perl Moo class?


I have created a Moo class containing an array of objects which is checked against a type using Types::Standard and I want to verify that I'm placing the right objects into that array without hard-coding too much. Here is an example of what I have right now:

package MyClass;
use Moo;
use Types::Standard qw( ArrayRef InstanceOf );

# Initially empty array of objects
has connected => (
    is => 'ro',
    isa => ArrayRef[InstanceOf['MyClass']],
    default => sub { [] }
);

# Add an object to our internal list
sub connect {
    my ( $self, $other ) = @_;
    # TODO: Check if "$other" is actually an InstanceOf['MyClass']
    # without doing "$self->connected( $self->connected )"
    push @{$self->connected}, $other;
}

In connect() I add objects to my internal array but if I understand correctly, the object is never checked against InstanceOf['MyClass'] since I don't actually set the array reference again.

What would be a good way to perform this validation on each new object? I briefly thought about doing $self->connected( $self->connected ) after having pushed a new item but that would have to verify every object.

Ideally I would not even want to know exactly what's in the ArrayRef, only that it is something that has to be checked. I took a look at _type_parameter_ in the documentation for Type::Tiny but I couldn't quite figure out how to use it in my code.


Solution

  • This answer ignores Moo as you you can achieve what you have asked without using it. This does not prevent you using it if you want to, but it's not needed to do what you are asking.

    Perl itself is effectively duck-typed but does support bless which blesses a hash reference into a class, allowing you to attach methods to it.

    In the same way that in any object oriented system classes can inherit from each other in perl you can say that any class isa another class. Then your code can simply check if it has been passed a hash reference that's blessed as that class.

    So if I understand your question correctly, you can ensure that you only add classes of the relevant type (blessed appropriately) to your array by ensuring that your class hierarchy stipulates the correct isa relationships and checking for that.

    So if you define a base type that is the type you want to check for you can have all of your other types declare that they are that:

    Possible example declaration in your base :

    package MyCore;
    
    sub new
    {
    my ($Class, $obj);
    
      $Class=shift;
      $obj= { Key => 'Value' };
      bless($obj, $Class);
      return($obj);
    }
    

    possible declartion in a derived type:

    use MyCore;
    
    package MyDerived;
    @MyDerived::ISA=('MyCore');
    
    sub new()
    {
    my ($Class, $Obj);
    
      $Class=shift;
      $Obj=MyCore::new();
      $Obj->{NewKey}='NewValue';
      bless($Obj, $Class);
    }
    

    Then when you are adding to an array:

    sub connect()
    {
      my ($Self, $Other);
      $Self=shift;
      $Other=($#_>=0)?shift:undef;
    
      if(defined($Other) && $Other->isa('MyCore'))
      {
        push(@{$Self->{Connected}}, $Other);
      }
    }
    

    We use this approach in a high volume webserver built on mod_perl and it is simple and easy to relate to for users of other object oriented environments.