Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl Moose attributes with minimum, maximum, and default value

Using Moose, it's possible to create attributes having a default value. I have a set of attributes which all have a minimum, maximum and a default value. They are attributes representing a scale (such as Tk::Scale).

Currently, I have at least 3 attributes: current & default:

has 'attr' => (is => 'rw', isa => 'Int', default => 300, lazy => 1, clearer => '_clear_attr');

min:

has 'attr_min' => (is => 'rw', isa => Int', default => 100);

max:

has 'attr_max' => (is => 'rw', isa => Int', default => 1000);

Is it possioble to have all four (current, default, min, max) in one attribute?

like image 676
capfan Avatar asked May 27 '13 19:05

capfan


2 Answers

I think you want to create a validation rule.

use Moose::Util::TypeConstraints;

subtype 'ScaleVal',
   as 'Int',
   where { 100 <= $_ && $_ <= 1000 };

has attr => (
   is      => 'rw',
   isa     => 'ScaleVal',
   default => 300,
);
like image 92
ikegami Avatar answered Nov 19 '22 01:11

ikegami


There are of course a lot of ways to combine a bunch of values into a more complex value -- that's basically what the study of data structures is about. What particular data structures to choose is a fairly involved question, highly dependent on your actual use cases.

I know fairly little about your case, but the thing I've gleaned from your question that all of these attributes represent similarly-structured concepts. And so I would create a new data type, the Scale:

use MooseX::Declare;
class Scale {
    for (qw/min max def/) {
        has $_ => (is => 'ro', isa => 'Num', required => 1);
    }
    has val => (is => 'rw', isa => 'Num', lazy_build => 1);
    method _build_val() {
        return $self->def;
    }
    method BUILD($args) {
        confess "Out of range!" unless $self->_is_in_range($self->val);
    }
    method _is_in_range($val) {
        return defined $val && $val >= $self->min && $val <= $self->max;
    }
    before val ($new_val?) {
        return unless defined $new_val;
        confess "Out of range!" unless $self->_is_in_range($new_val);
    }
}

And I would present attributes on some ThingWithScale that were backed by a Scale object.

class ThingWithScale {
    has _attr => (
        is => 'ro', isa => 'Scale',
        default => sub { shift->_make_attr() },
    );
    method _make_attr($class: $val?) {
        return Scale->new(
            min => 100, max => 1000, def => 200,
            (defined $val ? (val => $val) : ()),
        )
    }
    # Convert `attr` argument to a `Scale` object before passing to real constructor.
    sub BUILDARGS {
        my ($class, %args) = @_;

        if (defined (my $attr = delete $args{attr})) {
            %args = (
                %args,
                _attr => $class->_make_attr($attr)
            );
        }
        return $class->SUPER::BUILDARGS(%args);
    }
}

my $thing = ThingWithScale->new(attr => 101);

And about the time I was writing that BUILDARGS method to automatically instantiate a Scale object from the simple constructor parameter, I would realize that what I really wanted to do was to invent a new attribute trait to describe attributes that had minimum and maximum legal values.

like image 24
darch Avatar answered Nov 19 '22 02:11

darch