The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

The Default constructor

Classes created using Mic are provided with a generated constructor.

This default constructor supports two forms of usage described below.

Keyword Parameters

For objects constructed using keyword parameters, the constructor should be passed a hashref containing the keyword parameters.

Imagine a counter that starts from a user supplied value, so we can use it this way

use Test::More tests => 3;
use Example::Construction::Counter;

my $counter = Example::Construction::Counter->new({start => 10});

is $counter->next => 10;
is $counter->next => 11;
is $counter->next => 12;

The class and implementation:

package Example::Construction::Counter;

use Mic::Class
    interface => {
        object => {
            next => {},
        },
        class => { new => {} }
    },

    implementation => 'Example::Construction::Acme::Counter';

1;

package Example::Construction::Acme::Counter;

use Mic::Impl
    has  => {
        COUNT => { init_arg => 'start' },
    },
;

sub next {
    my ($self) = @_;

    $self->[COUNT]++;
}

1;

Here the 'count' attribute is bound to the 'start' constructor parameter using the init_arg declaration.

Positional parameters

For objects constructed using positional parameters, the constructor should be passed a list of positional parameters. These parameters will be passed to the BUILD special method (described in the next section) as an array ref.

As an example, consider a set object that we'd create by passing a list of items.

use Test::More tests => 3;
use Example::Construction::Set_v1;

my $set = Example::Construction::Set_v1->new(1 .. 4);

ok $set->has(1);
ok ! $set->has(5);
$set->add(5);
ok $set->has(5);

This can be acheieved using the BUILD special method.

package Example::Construction::Set_v2;

use Mic::Class

    interface => {
        object => {
            add => {},
            has => {},
            size => {},
        },
        class => { new => {} }
    },

    implementation => 'Example::Construction::Acme::Set_v2',
;

1;

In the implementation, we convert the argument array to a hash

package Example::Construction::Acme::Set_v2;

use Mic::Impl
    has => {
        SET => {
            default => sub { {} },
        }
    },
;

sub BUILD {
    my ($self, $args) = @_;

    $self->[SET] = { map { $_ => 1 } @{ $args } };
}

sub has {
    my ($self, $e) = @_;

    exists $self->[SET]{$e};
}

sub add {
    my ($self, $e) = @_;

    ++$self->[SET]{$e};
}

sub size {
    my ($self) = @_;
    scalar(keys %{ $self->[SET] });
}

1;

BUILD

If this subroutine is defined, it will be called by the default constructor and will receive the newly created object and either a hashref of named parameters or an arrayref of positional parameters depending on whether the constructor was passed a hashref or list respectiively.

This is useful for carrying out any post-construction logic e.g. object validation.

It can also be used to process constructor arguments, e.g. the counter implementation above can also be written using BUILD instead of init_arg (though init_arg is preferable due to being more concise).

package Example::Construction::Acme::Counter_v2;

use Mic::Impl
    has  => {
        COUNT => { },
    },
;

sub BUILD {
    my ($self, $arg) = @_;

    $self->[COUNT] = $arg->{start};
}

sub next {
    my ($self) = @_;

    $self->[COUNT]++;
}

1;

Writing your own constructor

If the default constructor is not flexible enough and you need to write your own constructor, this can be done with the aid of builders.

Builders

Each class has a corresponding Builder. Within a class method, the Builder is obtained by calling the builder_for routine (see example below).

A builder has the following construction related methods

new_object([HASHREF])

This creates a new instance, in which attributes with declared defaults are populated with those defaults, and all others are populated with undef. A hashref can also be supplied, in which case it is used to populate the attributes.

build

This can be used in a class method to invoke the BUILD method for an object after the object is created.

Examples

We'll rewrite the counter example above providing a new method:

package Example::Construction::Acme::CounterWithNew;

use Mic::Impl
    has  => {
        COUNT => { },
    },
    classmethod => ['new'],
;

sub next {
    my ($self) = @_;

    $self->[COUNT]++;
}

sub new {
    my ($class, $start) = @_;

    my $builder = Mic::builder_for($class);
    my $obj = $builder->new_object({COUNT => $start});
    return $obj;
};

1;

Note that new must be tagged as a 'classmethod' for this to work.