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

Aion - a postmodern object system for Perl 5, as Moose and Moo, but with improvements

VERSION

0.1

SYNOPSIS

        package Calc {
        
            use Aion;
        
            has a => (is => 'ro+', isa => Num);
            has b => (is => 'ro+', isa => Num);
            has op => (is => 'ro', isa => Enum[qw/+ - * \/ **/], default => '+');
        
            sub result {
                my ($self) = @_;
                eval "${\ $self->a} ${\ $self->op} ${\ $self->b}"
            }
        
        }
        
        Calc->new(a => 1.1, b => 2)->result   # => 3.1

DESCRIPTION

Aion — OOP framework for create classes with features, has aspects, roles and so on.

Properties declared via has are called features.

And is, isa, default and so on in has are called aspects.

In addition to standard aspects, roles can add their own aspects using subroutine aspect.

SUBROUTINES IN CLASSES AND ROLES

use Aion include in module types from Aion::Types and next subroutines:

has ($name, %aspects)

Make method for get/set feature (property) of the class.

File lib/Animal.pm:

        package Animal;
        use Aion;
        
        has type => (is => 'ro+', isa => Str);
        has name => (is => 'rw-', isa => Str, default => 'murka');
        
        1;



        use lib "lib";
        use Animal;
        
        my $cat = Animal->new(type => 'cat');
        
        $cat->type   # => cat
        $cat->name   # => murka
        
        $cat->name("murzik");
        $cat->name   # => murzik

with

Add to module roles. It call on each the role method import_with.

File lib/Role/Keys/Stringify.pm:

        package Role::Keys::Stringify;
        
        use Aion -role;
        
        sub keysify {
            my ($self) = @_;
            join ", ", sort keys %$self;
        }
        
        1;

File lib/Role/Values/Stringify.pm:

        package Role::Values::Stringify;
        
        use Aion -role;
        
        sub valsify {
            my ($self) = @_;
            join ", ", map $self->{$_}, sort keys %$self;
        }
        
        1;

File lib/Class/All/Stringify.pm:

        package Class::All::Stringify;
        
        use Aion;
        
        with qw/Role::Keys::Stringify Role::Values::Stringify/;
        
        has [qw/key1 key2/] => (is => 'rw', isa => Str);
        
        1;



        use lib "lib";
        use Class::All::Stringify;
        
        my $s = Class::All::Stringify->new(key1=>"a", key2=>"b");
        
        $s->keysify     # => key1, key2
        $s->valsify     # => a, b

isa ($package)

Check $package is the class what extended this class.

        package Ex::X { use Aion; }
        package Ex::A { use Aion; extends qw/Ex::X/; }
        package Ex::B { use Aion; }
        package Ex::C { use Aion; extends qw/Ex::A Ex::B/ }
        
        Ex::C->isa("Ex::A") # -> 1
        Ex::C->isa("Ex::B") # -> 1
        Ex::C->isa("Ex::X") # -> 1
        Ex::C->isa("Ex::X1") # -> ""
        Ex::A->isa("Ex::X") # -> 1
        Ex::A->isa("Ex::A") # -> 1
        Ex::X->isa("Ex::X") # -> 1

does ($package)

Check $package is the role what extended this class.

        package Role::X { use Aion -role; }
        package Role::A { use Aion; with qw/Role::X/; }
        package Role::B { use Aion; }
        package Ex::Z { use Aion; with qw/Role::A Role::B/ }
        
        Ex::Z->does("Role::A") # -> 1
        Ex::Z->does("Role::B") # -> 1
        Ex::Z->does("Role::X") # -> 1
        Role::A->does("Role::X") # -> 1
        Role::A->does("Role::X1") # -> ""
        Ex::Z->does("Ex::Z") # -> ""

aspect ($aspect => sub { ... })

It add aspect to has in this class or role, and to the classes, who use this role, if it role.

        package Example::Earth {
            use Aion;
        
            aspect lvalue => sub {
                my ($cls, $name, $value, $construct, $feature) = @_;
        
                $construct->{attr} .= ":lvalue";
            };
        
            has moon => (is => "rw", lvalue => 1);
        }
        
        my $earth = Example::Earth->new;
        
        $earth->moon = "Mars";
        
        $earth->moon # => Mars

Aspect is called every time it is specified in has.

Aspect handler has parameters:

  • $cls — the package with the has.

  • $name — the feature name.

  • $value — the aspect value.

  • $construct — the hash with code fragments for join to the feature method.

  • $feature — the hash present feature.

        package Example::Mars {
            use Aion;
        
            aspect lvalue => sub {
                my ($cls, $name, $value, $construct, $feature) = @_;
        
                $construct->{attr} .= ":lvalue";
        
                $cls # => Example::Mars
                $name # => moon
                $value # -> 1
                [sort keys %$construct] # --> [qw/attr eval get name pkg ret set sub/]
                [sort keys %$feature] # --> [qw/construct has name opt/]
        
                my $_construct = {
                    pkg => $cls,
                    name => $name,
                                attr => ':lvalue',
                                eval => 'package %(pkg)s {
                %(sub)s
        }',
                    sub => 'sub %(name)s%(attr)s {
                        if(@_>1) {
                                my ($self, $val) = @_;
                                %(set)s%(ret)s
                        } else {
                                my ($self) = @_;
                                %(get)s
                        }
                }',
                    get => '$self->{%(name)s}',
                    set => '$self->{%(name)s} = $val',
                    ret => '; $self',
                };
        
                $construct # --> $_construct
        
                my $_feature = {
                    has => [is => "rw", lvalue => 1],
                    opt => {
                        is => "rw",
                        lvalue => 1,
                    },
                    name => $name,
                    construct => $_construct,
                };
        
                $feature # --> $_feature
            };
        
            has moon => (is => "rw", lvalue => 1);
        }

SUBROUTINES IN CLASSES

extends (@superclasses)

Extends package other package. It call on each the package method import_extends if it exists.

        package World { use Aion;
        
            our $extended_by_this = 0;
        
            sub import_extends {
                my ($class, $extends) = @_;
                $extended_by_this ++;
        
                $class      # => World
                $extends    # => Hello
            }
        }
        
        package Hello { use Aion;
            extends qw/World/;
        
            $World::extended_by_this # -> 1
        }
        
        Hello->isa("World")     # -> 1

new (%param)

Constructor.

  • Set %param to features.

  • Check if param not mapped to feature.

  • Set default values.

        package NewExample { use Aion;
            has x => (is => 'ro', isa => Num);
            has y => (is => 'ro+', isa => Num);
            has z => (is => 'ro-', isa => Num);
        }
        
        eval { NewExample->new(f => 5) }; $@            # ~> f is not feature!
        eval { NewExample->new(n => 5, r => 6) }; $@    # ~> n, r is not features!
        eval { NewExample->new }; $@                    # ~> Feature y is required!
        eval { NewExample->new(z => 10) }; $@           # ~> Feature z cannot set in new!
        
        my $ex = NewExample->new(y => 8);
        
        eval { $ex->x }; $@  # ~> Get feature `x` must have the type Num. The it is undef
        
        $ex = NewExample->new(x => 10.1, y => 8);
        
        $ex->x # -> 10.1

SUBROUTINES IN ROLES

requires (@subroutine_names)

Check who in classes who use the role present the subroutines.

        package Role::Alpha { use Aion -role;
        
            sub in {
                my ($self, $s) = @_;
                $s =~ /[${\ $self->abc }]/
            }
        
            requires qw/abc/;
        }
        
        eval { package Omega1 { use Aion; with Role::Alpha; } }; $@ # ~> abc requires!
        
        package Omega { use Aion;
            with Role::Alpha;
        
            sub abc { "abc" }
        }
        
        Omega->new->in("a")  # -> 1

METHODS

has ($feature)

It check what property is set.

        package ExHas { use Aion;
            has x => (is => 'rw');
        }
        
        my $ex = ExHas->new;
        
        $ex->has("x")   # -> ""
        
        $ex->x(10);
        
        $ex->has("x")   # -> 1

clear (@features)

Cleared the features.

        package ExClear { use Aion;
            has x => (is => 'rw');
            has y => (is => 'rw');
        }
        
        my $c = ExClear->new(x => 10, y => 12);
        
        $c->has("x")   # -> 1
        $c->has("y")   # -> 1
        
        $c->clear(qw/x y/);
        
        $c->has("x")   # -> ""
        $c->has("y")   # -> ""

METHODS IN CLASSES

use Aion include in module next methods:

new (%parameters)

The constructor.

ASPECTS

use Aion include in module next aspects for use in has:

is => $permissions

  • ro — make getter only.

  • wo — make setter only.

  • rw — make getter and setter.

Default is rw.

Additional permissions:

  • + — the feature is required. It is not used with -.

  • - — the feature cannot be set in the constructor. It is not used with +.

  • * — the value is reference and it maked weaken can be set.

        package ExIs { use Aion;
            has rw => (is => 'rw');
            has ro => (is => 'ro+');
            has wo => (is => 'wo-');
        }
        
        eval { ExIs->new }; $@ # ~> \* Feature ro is required!
        eval { ExIs->new(ro => 10, wo => -10) }; $@ # ~> \* Feature wo cannot set in new!
        ExIs->new(ro => 10);
        ExIs->new(ro => 10, rw => 20);
        
        ExIs->new(ro => 10)->ro  # -> 10
        
        ExIs->new(ro => 10)->wo(30)->has("wo")  # -> 1
        eval { ExIs->new(ro => 10)->wo }; $@ # ~> has: wo is wo- \(not get\)
        ExIs->new(ro => 10)->rw(30)->rw  # -> 30

Feature with * don't hold value:

        package Node { use Aion;
            has parent => (is => "rw*", isa => Maybe[Object["Node"]]);
        }
        
        my $root = Node->new;
        my $node = Node->new(parent => $root);
        
        $node->parent->parent   # -> undef
        undef $root;
        $node->parent   # -> undef
        
        # And by setter:
        $node->parent($root = Node->new);
        
        $node->parent->parent   # -> undef
        undef $root;
        $node->parent   # -> undef

isa => $type

Set feature type. It validate feature value

default => $value

Default value set in constructor, if feature falue not present.

        package ExDefault { use Aion;
            has x => (is => 'ro', default => 10);
        }
        
        ExDefault->new->x  # -> 10
        ExDefault->new(x => 20)->x  # -> 20

If $value is subroutine, then the subroutine is considered a constructor for feature value. This subroutine lazy called where the value get.

        my $count = 10;
        
        package ExLazy { use Aion;
            has x => (default => sub {
                my ($self) = @_;
                ++$count
            });
        }
        
        my $ex = ExLazy->new;
        $count   # -> 10
        $ex->x   # -> 11
        $count   # -> 11
        $ex->x   # -> 11
        $count   # -> 11

trigger => $sub

$sub called after the value of the feature is set (in new or in setter).

        package ExTrigger { use Aion;
            has x => (trigger => sub {
                my ($self, $old_value) = @_;
                $self->y($old_value + $self->x);
            });
        
            has y => ();
        }
        
        my $ex = ExTrigger->new(x => 10);
        $ex->y      # -> 10
        $ex->x(20);
        $ex->y      # -> 30

ATTRIBUTES

Aion add universal attributes.

Isa (@signature)

Attribute Isa check the signature the function where it called.

WARNING: use atribute Isa slows down the program.

TIP: use aspect isa on features is more than enough to check the correctness of the object data.

        package Anim { use Aion;
        
            sub is_cat : Isa(Object => Str => Bool) {
                my ($self, $anim) = @_;
                $anim =~ /(cat)/
            }
        }
        
        my $anim = Anim->new;
        
        $anim->is_cat('cat')    # -> 1
        $anim->is_cat('dog')    # -> ""
        
        
        eval { Anim->is_cat("cat") }; $@ # ~> Arguments of method `is_cat` must have the type Tuple\[Object, Str\].
        eval { my @items = $anim->is_cat("cat") }; $@ # ~> Returns of method `is_cat` must have the type Tuple\[Bool\].

If use name of type in @signature, then call subroutine with this name from current package.

AUTHOR

Yaroslav O. Kosmina Lmailto:dart@cpan.org

LICENSE

GPLv3

COPYRIGHT

The Aion module is copyright © 2023 Yaroslav O. Kosmina. Rusland. All rights reserved.