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

NAME

Minions::Implementation

SYNOPSIS

package Example::Construction::Acme::Set_v1;

use Minions::Implementation
    has => {
        set => {
            default => sub { {} },
            init_arg => 'items',
            map_init_arg => sub { return { map { $_ => 1 } @{ $_[0] } } },
        }
    },
;

sub has {
    my ($self, $e) = @_;
    exists $self->{$SET}{$e};
}

sub add {
    my ($self, $e) = @_;
    ++$self->{$SET}{$e};
}

1;

DESCRIPTION

An implementation is a package containing attribute definitions as well as subroutines implementing the behaviours described by the class interface.

CONFIGURATION

An implementation package can be configured either using Minions::Implementation or with a package variable %__meta__. Both methods make use of the following keys:

has => HASHREF

This declares attributes of the implementation, mapping the name of an attribute to a hash with keys described in the following sub sections.

An attribute called "foo" can be accessed via it's object in one of two ways:

# implementation defined using Minions::Implementation
$self->{$FOO}

# implementation defined using %__meta__
$self->{-foo}

The advantage of the first form is that the symbol $FOO is not (easily) available to users of the object, so there is greater incentive for using the provided interface when using the object.

default => SCALAR | CODEREF

The default value assigned to the attribute when the object is created. This can be an anonymous sub, which will be excecuted to build the the default value (this would be needed if the default value is a reference, to prevent all objects from sharing the same reference).

assert => HASHREF

This is like the assert declared in a class package, except that these assertions are not run at construction time. Rather they are invoked by calling the semiprivate ASSERT routine.

handles => ARRAYREF | HASHREF

This declares that methods can be forwarded from the object to this attribute in one of two ways described below. These forwarding methods are generated as public methods if they are declared in the interface, and as semiprivate routines otherwise.

handles => ARRAYREF

All methods in the given array will be forwarded.

handles => HASHREF

Method forwarding will be set up such that a method whose name is a key in the given hash will be forwarded to a method whose name is the corresponding value in the hash.

init_arg => SCALAR

This causes the attribute to be populated with the value of a similarly named constructor parameter.

map_init_arg => CODEREF

If the attribute has an init_arg, it will be populated with the result of applying the given code ref to the value of a similarly named constructor parameter.

reader => SCALAR

This can be a string which if present will be the name of a generated reader method.

Readers should only be created if they are needed by end users of the class.

writer => SCALAR

This can be a string which if present will be the name of a generated writer method.

Writers should only be created if they are needed by end users of the class.

roles => ARRAYREF

A reference to an array containing the names of one or more Role packages.

Any attributes and/or routines defined in the specified roles will be added to the implementation subject to the following rules

Implementation trumps Roles

An attribute/routine defined in a role won't get added to the implementation if the implementation already has an attribute/routine with the same name.

Conflicts not allowed

An exception will be raised if the same attribute/routine would be provided by two roles.

Minions::Role describes how roles are configured.

semiprivate => ARRAYREF

These are perhaps only useful when used in conjunction with Roles. They work the same way as in Minions::Role.

attr_style => SCALAR

If this is set to the string '_2', then an attribute named 'foo' can be accessed via its object using the symbol $__foo e.g.

# implementation defined using Minions::Implementation
$self->{$__foo}

This was the default behaviour in Minions 0.000008 and earlier.

PRIVATE ROUTINES

An implementation package will typically contain subroutines that are for internal use in the package and therefore ought not to be declared in the interface. These won't be callable using the $minion->command(...) syntax.

As an example, suppose we want to print an informational message whenever the Set's has or add methods are called. A first cut may look like:

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

    warn sprintf "[%s] I have %d element(s)\n", scalar(localtime), scalar(keys %{ $self->{$SET} });
    exists $self->{$SET}{$e};
}

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

    warn sprintf "[%s] I have %d element(s)\n", scalar(localtime), scalar(keys %{ $self->{$SET} });
    ++$self->{$SET}{$e};
}

But this duplication of code is not good, so we factor it out:

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

    log_info($self);
    exists $self->{$SET}{$e};
}

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

    log_info($self);
    ++$self->{$SET}{$e};
}

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

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

    warn sprintf "[%s] I have %d element(s)\n", scalar(localtime), $self->size;
}

Notice how the log_info routine is called as a regular sub rather than as a method.

Here is a transcript of using this object via reply

5:51% reply -I t/lib
0> use Example::Construction::Set_v1
1> my $set = Example::Construction::Set_v1->new
$res[0] = bless( {
        '1f5f6ad9-' => 'Example::Construction::Set_v1::__Private',
        '1f5f6ad9-set' => {}
    }, 'Example::Construction::Set_v1::__Minions' )

2> $set->can
$res[1] = [
'add',
'has',
'size'
]

3> $set->add(1)
[Sat Jan 10 17:52:35 2015] I have 0 element(s)
$res[2] = 1

4> $set->add(1)
[Sat Jan 10 17:57:03 2015] I have 1 element(s)
$res[3] = 2

5> $set->log_info()
Can't locate object method "log_info" via package "Example::Construction::Set_v1::__Minions" at reply input line 1.
6> 

OBJECT COMPOSITION

Composition allows us to create new objects incorporating the functionality of existing ones.

As an example, consider a queue which we would use like this:

use strict;
use Test::More;
use Example::Delegates::Queue;

my $q = Example::Delegates::Queue->new;

is $q->size => 0;

$q->push(1);
is $q->size => 1;

$q->push(2);
is $q->size => 2;

$q->pop;
is $q->size => 1;
done_testing();

Now suppose we need a queue which maintains a fixed size by evicting the oldest items:

use strict;
use Test::More;
use Example::Delegates::FixedSizeQueue;

my $q = Example::Delegates::FixedSizeQueue->new(max_size => 3);

$q->push($_) for 1 .. 3;
is $q->size => 3;

$q->push($_) for 4 .. 6;
is $q->size => 3;
is $q->pop => 4;
done_testing();

Here is the interface for this fixed size queue

package Example::Delegates::FixedSizeQueue;

use Minions
    interface => [qw( push pop size )],

    construct_with  => {
        max_size => { 
            assert => { positive_int => sub { $_[0] =~ /^\d+$/ && $_[0] > 0 } }, 
        },
    }, 

    implementation => 'Example::Delegates::Acme::FixedSizeQueue_v1',
;

1;

And it is implemented like this

package Example::Delegates::Acme::FixedSizeQueue_v1;

use Example::Delegates::Queue;

use Minions::Implementation
    has  => {
        q => {
            default => sub { Example::Delegates::Queue->new },

            handles => [qw( size pop )],
        },

        max_size => {
            init_arg => 'max_size',
        },
    },
;

sub push {
    my ($self, $val) = @_;

    $self->{$Q}->push($val);

    if ($self->size > $self->{$MAX_SIZE}) {
        $self->pop;
    }
}

1;

The fixed size queue is composed out of the regular queue, which handles the size and pop methods.