QSeep has asked for the wisdom of the Perl Monks concerning the following question:

Moose's type-checking philosophy seems to be that we should specify type constraints at the level of an attribute, and check types at the point when an attribute is set. It does not normally check other method parameters. It can, though, e.g. with MooseX::Params::Validate or Moops. This can create performance problems with type-checking aggregates. If you have a large array, and you set a ArrayRefInt to point to it, the constraint must now check every element in the array. What if we had a class that contained an arrayref, and had a type constraint, and methods on that class that are used to add elements would check the type constraint for the added elements? Then when we passed one of these objects in as a Moose attribute, the check for the types of all elements would be constant time.
package TypedArray; use Moose; has type => ( is => 'ro', isa => 'Moose::Meta::TypeConstraint', required => 1 ); has elems => ( is => 'ro', isa => ArrayRef, default => sub { [] }, ); sub BUILD { my $self = shift; $self->type->assert_valid($_) for @{$self->elems}; } sub elems_push { my $self = shift; $self->type->assert_valid($_) for @_; push @{$self->elems}, @_; } ... more methods and operator overloads ...
Using overloaded operators, we could make them work much like built-in arrays. To create a new one, you would write, e.g.:
TypedArray->new(type => Int, elems => [4, 8, -7])
It would be nice to have a shorthand syntax to create them. We could use BUILDARGS to simplify it to:
TypedArray->new(Int, 4, 8, -7)
but it would be nice to get rid of even the `new`. We could make a function, e.g.:
typedArray Int, 4, 8, -7
Then we could define a parameterizable type constraint called TypedArrayOf:
subtype TypedArrayOf as Parameterizable['TypedArray', 'Moose::Meta::TypeConstraint' +], where { my ($typed_array, $type) = @_; $typed_array->type->is_a_type_of($type) }, message { my ($typed_array, $type) = @_; return "expected TypedArrayOf[$type], found TypedArrayOf[@ +{[$typed_array->type]}]" };
Unfortunately, we could not use the class name for the type constraint, because this is not a class constraint, and we do not want to conflict with the class constraint. So I chose a compromise by appending the word 'Of' to the name. Instead of having a single TypedArray class, another option would be to create a new class on the fly for each possible type parameter. We could use a parameterized role to build them, using Class::MOP to generate the new classes on demand. Then TypedArray would become a function, taking a single parameter that is an arrayref with the contained type. E.g.,
TypedArray[Int]
would look up the Int type in a global cache, and if not found, would generate a new TypedArray class, parameterized on Int. That class would constrain all elements to be Ints. This could get expensive if we end up creating a lot of classes. What do folks think of the naming here? Can we be more succinct? More consistent? How can we avoid naming conflicts between 1) the class without a type parameter, 2) the class with a type parameter, 3) a constructor function without a type paramter, 4) a constructor function with a type parameter, 5) the type constraint without a parameter, 6) the type constraint with a parameter. What is the most "Moosey" approach?

Replies are listed 'Best First'.
Re: Naming question
by QSeep (Initiate) on Jun 28, 2016 at 05:18 UTC
    Ha! Looks like List::Objects::WithUtils::Array::Typed does what I'm proposing here. With the array_of notation where the first parameter is the type constraint. I'm planning a wider library with support for functional programming, so that doesn't mean I can just stop and say there's nothing to do. But that's a very encouraging existing example and perhaps I can import that code rather than recreating it.