http://qs1969.pair.com?node_id=1114523

Minions is yet another OOP automation module, roughly similar to Moo, but which has the addtional goal of putting Encapsulation centre stage. I wrote it because after reading How Large Does Your Project Have To Be to Justify Using Moose? (especially the comments by tye and JavaFan), I became increasingly disillusioned with the Moo(se) way of OOP (essentially OOP with no Encapsulation).

Here is a sample that implements the fixed size queue from Re^5: The future of Perl? (that sub-thread also illustrates limitations of the Moo way of OOP)
package FixedSizeQueue; use Minions interface => [qw( push pop size )], construct_with => { max_items => { assert => { positive_int => sub { $_[0] =~ /^\d+$/ && $_[0 +] > 0 } }, }, }, implementation => 'FixedSizeQueue::Default', ; 1; package FixedSizeQueue::Default; use Minions::Implementation has => { q => { default => sub { [ ] } }, max_size => { init_arg => 'max_items', }, }, ; sub size { my ($self) = @_; scalar @{ $self->{$__q} }; } sub push { my ($self, $val) = @_; log_info($self); push @{ $self->{$__q} }, $val; if ($self->size > $self->{$__max_size}) { $self->pop; } } sub pop { my ($self) = @_; log_info($self); shift @{ $self->{$__q} }; } sub log_info { my ($self) = @_; warn sprintf "[%s] I have %d element(s)\n", scalar(localtime), $se +lf->size; } 1;
And a sample of usage:
% reply -I lib 0> use FixedSizeQueue 1> my $q = FixedSizeQueue->new(max_items => 3) $res[0] = bless( { '932db126-' => 'FixedSizeQueue::__Private', '932db126-max_size' => 3, '932db126-q' => [] }, 'FixedSizeQueue::__Minions' ) 2> $q->can $res[1] = [ 'pop', 'push', 'size' ] 3> $q->push($_) for 1 .. 3 [Mon Jan 26 12:01:53 2015] I have 0 element(s) [Mon Jan 26 12:01:53 2015] I have 1 element(s) [Mon Jan 26 12:01:53 2015] I have 2 element(s) $res[2] = '' 4> $q->pop [Mon Jan 26 12:02:09 2015] I have 3 element(s) $res[3] = 1 5> $q $res[4] = bless( { '932db126-' => 'FixedSizeQueue::__Private', '932db126-max_size' => 3, '932db126-q' => [ 2, 3 ] }, 'FixedSizeQueue::__Minions' ) 6> $q->push($_) for 4 .. 6 [Mon Jan 26 12:02:55 2015] I have 2 element(s) [Mon Jan 26 12:02:55 2015] I have 3 element(s) [Mon Jan 26 12:02:55 2015] I have 4 element(s) [Mon Jan 26 12:02:55 2015] I have 3 element(s) [Mon Jan 26 12:02:55 2015] I have 4 element(s) $res[5] = '' 7> $q $res[6] = bless( { '932db126-' => 'FixedSizeQueue::__Private', '932db126-max_size' => 3, '932db126-q' => [ 4, 5, 6 ] }, 'FixedSizeQueue::__Minions' ) 8> $q->log_info() Can't locate object method "log_info" via package "FixedSizeQueue::__M +inions" at reply input line 1. 9>
Not all Moo features are supported (for this early release I've focused on those I actually use). Important differences from Moo include
  1. Attributes can be safely accessed inside classes without the overhead of a function call
  2. As a consequence of 1., attributes need not be exposed via methods (unless there is a good reason to do so).
  3. No need to clean up animal droppings
  4. Private subroutines via the mechanism shown in Re: OO - best way to have protected methods (packages)
  5. Class methods are "class only", and object methods are "object only"
  6. No compatibility with Moose
Feedback is much appreciated.

Replies are listed 'Best First'.
Re: RFC: automating modular classes
by blindluke (Hermit) on Jan 26, 2015 at 19:18 UTC

    The more, the merrier. Personally, I like the minionize($hashref) option better, mostly because it avoids creating two packages for every class I want.

    The implementation of the methods passed as anonymous subroutines in the %Class hash seems a bit difficult to read though. Still, this could probably be avoided:

    use Minions (); my %Method; # maybe this hash could be already imported into # the namespace with a param passed to Minions my %Class = ( name => 'Counter', interface => [qw( next )], implementation => { methods => { next => $Method{'next'}, }, has => { count => { default => 0 }, }, }, ); $Method{'next'} = sub { my ($self) = @_; # maybe this could be avoided with some sugar $self->{-count}++; }; Minions->minionize(\%Class);

    If there would be some more sugar to facilitate writing the package this way, I would like it even more. Is the line interface => [qw( next )], needed? Maybe it could be replaced with public_methods => {...} section within implementation, and all the methods added there would automagically land in interface => []?

    The documentation says nothing (or I did not read it carefully enough to find it) about how Minions behaves when stuff goes wrong. On the other hand, the fact that you provide code examples wrapped in simple tests, is wonderful. More examples should be written this way.

    Looking forward to future updates. Best luck with your distribution.

    - Luke

      Thanks for the feedback. I was considering leaving that section out of the documentation, because it doesn't scale up very well, but I ended up leaving it in for completeness.

      The subs in the "implementation" section don't really need to be anonymous - they could be also be refs to named subs defined elsewhere.

      The interface => [...] is needed because the whole point of this module is to encourage programming in terms of interfaces, and interfaces should be independent of a particular implementation.

      This is also the reason for having separate packages (but also having the just the interface and POD in one file, it's easier to get an overview compared to having to swim through a sea of code).