Dependent Type Constraints Part Two
So in my attempt to add in some support for Dependent Type programming to Perl Moose programming I'm at the part where I need to integrate this into declaring your Moose based Perl objects. To recap, with a Dependent Type Constraint you can declare a constraint like:
subtype Range,
as Dict[max=>Int, min=>Int],
where {
my $range = shift @_;
return $range->{max} > $range->{min};
};
subtype RangedInt,
as Dependent[Int, Range],
where {
my ($int,$range) = @_;
return ($int >= $range->{min} && $int <= $range->{max});
};
RangedInt([max=>10, min=>1])->check(5); ## Is fine
RangedInt([max=>10, min=>1])->check(15); ## Is NOT fine
You can then use this in a Perl class like so:
class MyClass {
has young_adult_age => ( is=>'ro', isa=>RangedInt[min=>18,max=>34] );
}
And you can instantiate this like so:
MyClass->new(young_adult_age=>20);
But the following will throw an exception:
MyClass->new(young_adult_age=>40);
Also, if you are using MooseX::Declare you can use these in your method body signatures:
method height(RangedInt[min=>10,max=>100] $height) { ## Do something here }
All this is supported in the current repository version you can review here.
So far so good. However, it's been requested to be able to fulfill a dependency requirement via $self when using dependent types in a class. My proposed syntax for this is to make the 'isa' and 'does' attribute options similar to default, where if the attribute is lazy and the value is a coderef, we de-reference it and pass $self. So, for example, let's say you create some type constraints and a class that uses it like so:
use Set::Scalar;
use Email::Valid;
subtype Email,
as Str,
where {
return Email::Valid->address($_);
};
subtype Set,
as Dependent[class_type 'Set::Scalar', Any],
where {
my ($set, $item) = @_;
foreach my $element ($set->members) {
return unless $item->check($element);
} 1
}
subtype UniqueEmail,
as Dependent[Email, Set],
where {
my ($email, $set) = @_;
return !$set->has($email);
}
class Person {
has email_set => (is=>'ro', isa=>Set[Email] );
has email => (is=>'ro', isa=>UniqueEmail[Set[Email]] );
}
Ideally, when you try to instantiate this class, the attribute 'email' would be bound to attribute 'email_set', in such a way that it's value should be unique in that set. To achieve this, I am proposing an attribute trait that allows something like:
class Person {
has email_set => (is=>'ro', isa=>Set[Email] );
has email => (
is=>'ro',
isa=>sub {
my $self = shift @_;
my $set = $self->email_set;
return UniqueEmail[$set];
},
traits=>['Dependent'],
);
}
And at validate time, the coderef would be dereferenced with $self, similarly to the way the default option works. We'd do the same for the 'does' attribute. And to make it even more similar to default, I'd support coderefs here without lazy, which would not pass $self, but would execute at runtime.
For myself, I'd rather have another solution, but maybe I just don't fully grasp the use cases. Comments on the above proposals very welcome. However, if you are wanting something very different, you should give me enough specification for me to get it.
==UPDATES==
It's been suggested that instead of overloading 'does' and 'isa', to use a new options, such as 'generate_isa' or similar. Should be more sane and play nicer with other attributes that expect isa and does to behave a certain way.