Wednesday, April 17, 2013

Auto-generating setters and getters in Perl with Moose

As part of a project for work interfacing with the RDS encoder for the radio station (an Inovonics Model 730) I thought, "wouldn't it be nice to have a Perl object that provides a complete interface to this device?"  The only trouble is that the device takes a lot of different commands, and it would be really tedious to write sub set_foo { ... }; sub get_foo { ... } umpteen million times for every last command / datum the encoder supported, since every set_foo { ... } was going to boil down to:

sub set_foo {
    my ($self, $value) = @_;
    return _set('foo', $value);
}

sub get_foo {
    my $self = shift;
    return _get('foo');
}

Where _set() and _get() took care of the actual business of communicating with the encoder.

Now, I could have done this in Vim with regular expressions - just copy/paste and use the regex to change the appropriate things. Still tedious, though. I also could have done something like this:

sub set_property {
    my ($self, $property, $value) = @_;
    return _set($property, $value);
}

sub get_property {
    my ($self, $property) = @_;
    return _get($property);
}

And that would have worked fine. I could have put some code in there to throw an exception on an invalid property, maybe something to validate the values based on the property name, all that sort of thing. But Moose gives us a nifty trick to avoid having to validate the property name:

    package MooseSketch;
    use Moose;

    my $meta = __PACKAGE__->meta;
    foreach my $prop (qw/foo bar baz bak/) {
        $meta->add_method(qq/set_$prop/, sub { 
                my $self = shift;
                my $value = shift;
                return $self->_set($prop, $value);
            }
        );
        $meta->add_method(qq/get_$prop/, sub { 
                my $self = shift;
                return $self->_get($prop);
            }
        );
    }

This $meta business comes from Class::MOP::Class, which allows us to do introspection and manipulation of Perl 5 objects. So with Class::MOP::Class, I can add or remove methods programatically at runtime, or even create entire classes. Neat, huh?

Hope you find this useful. I know I sure will. Many thanks to the good people over at Stack Overflow who helped me figure this out. My original question: How to auto-generate a bunch of setters/getters tied to a network service in Moose?