NAME

Aion - a postmodern object system for Perl 5, such as “Mouse”, “Moose”, “Moo”, “Mo” and “M”, but with improvements

VERSION

1.2

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 : Isa(Me => Num) {
		my ($self) = @_;
		eval "${\ $self->a} ${\ $self->op} ${\ $self->b}";
	}

}

Calc->new(a => 1.1, b => 2)->result   # => 3.1

DESCRIPTION

Aion is OOP-framework for creating classes with features, has aspects, roles and so on.

The properties declared through HAS are called features.

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

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

The signature of the methods can be checked using the attribute :Isa(...).

SUBROUTINES IN CLASSES AND ROLES

Use Aion imports types from the moduleAion::Types and the following subprograms:

has ($name, %aspects)

Creates a method for obtaining/setting the function (properties) of the class.

lib/Animal.pm file:

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

Adds to the module of the role. For each role, the import_with method is called.

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 q/Role::Keys::Stringify/;
with q/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)

Checks that $package is a super class for a given or this class itself.

package Ex::X { use Aion; }
package Ex::A { use Aion; extends q/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)

Checks that $package is a role that is used in a class or another role.

package Role::X { use Aion -role; }
package Role::A { use Aion -role; with qw/Role::X/; }
package Role::B { use Aion -role; }
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 { ... })

Adds the aspect to has in the current class and its classroom classes or the current role and applies its classes.

package Example::Earth {
	use Aion;

	aspect lvalue => sub {
		my ($lvalue, $feature) = @_;

		return unless $lvalue;

		$feature->construct->add_attr(":lvalue");
	};

	has moon => (is => "rw", lvalue => 1);
}

my $earth = Example::Earth->new;

$earth->moon = "Mars";

$earth->moon # => Mars

The aspect is called every time it is indicated in has.

The creator of the aspect has the parameters:

  • $value — aspect value.

  • $feature - meta-object describing the feature (Aion::Meta::Feature).

  • $aspect_name — aspect name.

package Example::Mars {
	use Aion;

	aspect lvalue => sub {
		my ($value, $feature, $aspect_name) = @_;

		$value # -> 1
		$aspect_name # => lvalue

		$feature->construct->add_attr(":lvalue");
	};

	has moon => (is => "rw", lvalue => 1);
}

SUBROUTINES IN CLASSES

extends (@superclasses)

Expands the class with another class/classes. It causes from each inherited class the method of import_extends, if it is in it.

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 q/World/;

	$World::extended_by_this # -> 1
}

Hello->isa("World")	 # -> 1

new (%param)

The constructor.

  • Installs %param for features.

  • Checks that the parameters correspond to the features.

  • Sets default values.

package NewExample { use Aion;
	has x => (is => 'ro', isa => Num);
	has y => (is => 'ro+', isa => Num);
	has z => (is => 'ro-', isa => Num);
}

NewExample->new(f => 5) # @-> y required!
NewExample->new(f => 5, y => 10) # @-> f is'nt feature!
NewExample->new(f => 5, p => 6, y => 10) # @-> f, p is'nt features!
NewExample->new(z => 10, y => 10) # @-> z excessive!

my $ex = NewExample->new(y => 8);

$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)

Checks that classes using this role have the specified routines or features.

package Role::Alpha { use Aion -role;

	requires qw/abc/;
}

package Omega1 { use Aion; with Role::Alpha; }

eval { Omega1->new }; $@ # ~> Requires abc of Role::Alpha

package Omega { use Aion;
	with Role::Alpha;

	sub abc { "abc" }
}

Omega->new->abc  # => abc

req ($name => @aspects)

Checks that classes using this role have the specified features with the specified aspects.

package Role::Beta { use Aion -role;

	req x => (is => 'rw', isa => Num);
}

package Omega2 { use Aion; with Role::Beta; }

eval { Omega2->new }; $@ # ~> Requires req x => \(is => 'rw', isa => Num\) of Role::Beta

package Omega3 { use Aion;
	with Role::Beta;

	has x => (is => 'rw', isa => Num, default => 12);
}

Omega3->new->x  # -> 12

ASPECTS

use Aion includes the following aspects in the module for use in has:

is => $permissions

  • ro - create only a gutter.

  • wo - create only a setter.

  • rw - Create getter and setter.

By default - rw.

Additional permits:

  • + - the feature is required in the constructor parameters. + is not used with -.

  • - - the feature cannot be installed via the constructor. '-' is not used with +.

  • * - do not increment the value's reference counter (apply weaken to the value after installing it in the feature).

  • ? – create a predicate.

  • ! – create clearer.

package ExIs { use Aion;
	has rw => (is => 'rw?!');
	has ro => (is => 'ro+');
	has wo => (is => 'wo-?');
}

ExIs->new # @-> ro required!
ExIs->new(ro => 10, wo => -10) # @-> wo excessive!

ExIs->new(ro => 10)->has_rw # -> ""
ExIs->new(ro => 10, rw => 20)->has_rw # -> 1
ExIs->new(ro => 10, rw => 20)->clear_rw->has_rw # -> ""

ExIs->new(ro => 10)->ro  # -> 10

ExIs->new(ro => 10)->wo(30)->has_wo # -> 1
ExIs->new(ro => 10)->wo # @-> Feature wo cannot be get!
ExIs->new(ro => 10)->rw(30)->rw  # -> 30

The function with * does not hold the meaning:

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

Indicates the type, or rather - a validator, feature.

package ExIsa { use Aion;
	has x => (is => 'ro', isa => Int);
}

ExIsa->new(x => 'str') # @-> Set feature x must have the type Int. The it is 'str'!
ExIsa->new->x # @-> Get feature x must have the type Int. The it is undef!
ExIsa->new(x => 10)->x			  # -> 10

For a list of validators, see Aion::Types.

coerce => (1|0)

Includes type conversions.

package ExCoerce { use Aion;
	has x => (is => 'ro', isa => Int, coerce => 1);
}

ExCoerce->new(x => 10.4)->x  # -> 10
ExCoerce->new(x => 10.5)->x  # -> 11

default => $value

The default value is set in the designer if there is no parameter with the name of the feature.

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

ExDefault->new->x  # -> 10
ExDefault->new(x => 20)->x  # -> 20

If $value is a subroutine, then the subroutine is considered the feature's value constructor. Lazy evaluation is used if there is no lazy attribute.

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

lazy => (1|0)

The lazy attribute enables or disables lazy evaluation of the default value (default).

By default it is only enabled if the default is a subroutine.

package ExLazy0 { use Aion;
	has x => (is => 'ro?', lazy => 0, default => sub { 5 });
}

my $ex0 = ExLazy0->new;
$ex0->has_x # -> 1
$ex0->x     # -> 5

package ExLazy1 { use Aion;
	has x => (is => 'ro?', lazy => 1, default => 6);
}

my $ex1 = ExLazy1->new;
$ex1->has_x # -> ""
$ex1->x     # -> 6

trigger => $sub

$sub is called after installing the property in the constructor (new) or through the setter. Etymology - let in.

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

release => $sub

$sub is called before returning the property from the object through the gutter. Etymology - release.

package ExRelease { use Aion;
	has x => (release => sub {
		my ($self, $value) = @_;
		$_[1] = $value + 1;
	});
}

my $ex = ExRelease->new(x => 10);
$ex->x	  # -> 11

init_arg => $name

Changes the property name in the constructor.

package ExInitArg { use Aion;
	has x => (is => 'ro+', init_arg => 'init_x');

	ExInitArg->new(init_x => 10)->x # -> 10
}

accessor => $name

Changes the accessor name.

package ExAccessor { use Aion;
	has x => (is => 'rw', accessor => '_x');

	ExAccessor->new->_x(10)->_x # -> 10
}

writer => $name

Creates a setter named $name for a property.

package ExWriter { use Aion;
	has x => (is => 'ro', writer => '_set_x');

	ExWriter->new->_set_x(10)->x # -> 10
}

reader => $name

Creates a getter named $name for a property.

package ExReader { use Aion;
	has x => (is => 'wo', reader => '_get_x');

	ExReader->new(x => 10)->_get_x # -> 10
}

predicate => $name

Creates a predicate named $name for a property. You can also create a predicate with a standard name using is => '?'.

package ExPredicate { use Aion;
	has x => (predicate => '_has_x');
	
	my $ex = ExPredicate->new;
	$ex->_has_x        # -> ""
	$ex->x(10)->_has_x # -> 1
}

clearer => $name

Creates a cleaner named $name for a property. You can also create a cleaner with a standard name using is => '!'.

package ExClearer { use Aion;
	has x => (is => '?', clearer => 'clear_x_');
}

my $ex = ExClearer->new;
$ex->has_x	  # -> ""
$ex->clear_x_;
$ex->has_x	  # -> ""
$ex->x(10);
$ex->has_x	  # -> 1
$ex->clear_x_;
$ex->has_x	  # -> ""

cleaner => $sub

$sub is called when the destructor or $object->clear_feature is called, but only if the feature is present (see $object->has_feature).

This aspect forces the creation of a predicate and a clearer.

package ExCleaner { use Aion;

	our $x;

	has x => (is => '!', cleaner => sub {
		my ($self) = @_;
		$x = $self->x
	});
}

$ExCleaner::x		  # -> undef
ExCleaner->new(x => 10);
$ExCleaner::x		  # -> 10

my $ex = ExCleaner->new(x => 12);

$ExCleaner::x	  # -> 10
$ex->clear_x;
$ExCleaner::x	  # -> 12

undef $ex;

$ExCleaner::x	  # -> 12

ATTRIBUTES

Aion adds universal attributes to the package.

:Isa (@signature)

The attribute Isa checks the signature of the function.

package MaybeCat { use Aion;

	sub is_cat : Isa(Me => Str => Bool) {
		my ($self, $anim) = @_;
		$anim =~ /(cat)/
	}
}

my $anim = MaybeCat->new;
$anim->is_cat('cat')	# -> 1
$anim->is_cat('dog')	# -> ""

MaybeCat->is_cat("cat") # @-> Arguments of method `is_cat` must have the type Tuple[Me, Str].
my @items = $anim->is_cat("cat") # @-> Returns of method `is_cat` must have the type Tuple[Bool].

The Isa attribute allows you to declare the required functions:

package Anim { use Aion -role;

	sub is_cat : Isa(Me => Bool);
}

package Cat { use Aion; with qw/Anim/;

	sub is_cat : Isa(Me => Bool) { 1 }
}

package Dog { use Aion; with qw/Anim/;

	sub is_cat : Isa(Me => Bool) { 0 }
}

package Mouse { use Aion; with qw/Anim/;
	
	sub is_cat : Isa(Me => Int) { 0 }
}

Cat->new->is_cat # -> 1
Dog->new->is_cat # -> 0
Mouse->new # @-> Signature mismatch: is_cat(Me => Bool) of Anim <=> is_cat(Me => Int) of Mouse

AUTHOR

Yaroslav O. Kosmina mailto:dart@cpan.org

LICENSE

GPLv3

COPYRIGHT

The Aion module is copyright © 2023 Yaroslav O. Kosmina. Rusland. All Rights Reserved.