NAME
Aion - постмодернистская объектная система для Perl 5, такая как «Mouse», «Moose», «Moo», «Mo» и «M», но с улучшениями
VERSION
0.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(Object => Num) {
my ($self) = @_;
eval "${\ $self->a} ${\ $self->op} ${\ $self->b}"
}
}
Calc->new(a => 1.1, b => 2)->result # => 3.1
DESCRIPTION
Aion — ООП-фреймворк для создания классов с фичами, имеет аспекты, роли и так далее.
Свойства, объявленные через has, называются фичами.
А is
, isa
, default
и так далее в has
называются аспектами.
Помимо стандартных аспектов, роли могут добавлять свои собственные аспекты с помощью подпрограммы aspect.
Сигнатура методов может проверяться с помощью атрибута :Isa(...)
.
SUBROUTINES IN CLASSES AND ROLES
use Aion
импортирует типы из модуля Aion::Types
и следующие подпрограммы:
has ($name, %aspects)
Создаёт метод для получения/установки функции (свойства) класса.
Файл 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
Добавляет в модуль роли. Для каждой роли вызывается метод import_with
.
Файл lib/Role/Keys/Stringify.pm:
package Role::Keys::Stringify;
use Aion -role;
sub keysify {
my ($self) = @_;
join ", ", sort keys %$self;
}
1;
Файл lib/Role/Values/Stringify.pm:
package Role::Values::Stringify;
use Aion -role;
sub valsify {
my ($self) = @_;
join ", ", map $self->{$_}, sort keys %$self;
}
1;
Файл 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)
Проверяет, что $package
— это суперкласс для данного или сам этот класс.
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)
Проверяет, что $package
— это роль, которая используется в классе или другой роли.
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 { ... })
Добавляет аспект к has
в текущем классе и его классам-наследникам или текущей роли и применяющим её классам.
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
Аспект вызывается каждый раз, когда он указан в has
.
Создатель аспекта имеет параметры:
$cls
— пакет сhas
.$name
— имя фичи.$value
— значение аспекта.$construct
— хэш с фрагментами кода для присоединения к методу объекта.$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 order/]
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,
order => 0,
};
$feature # --> $_feature
};
has moon => (is => "rw", lvalue => 1);
}
SUBROUTINES IN CLASSES
extends (@superclasses)
Расширяет класс другим классом/классами. Он вызывает из каждого наследуемого класса метод import_extends
, если он в нём есть.
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)
Конструктор.
Устанавливает
%param
для фич.Проверяет, что параметры соответствуют фичам.
Устанавливает значения по умолчанию.
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)
Проверяет, что в классах использующих эту роль есть указанные подпрограммы или фичи.
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)
Проверяет, что свойство установлено.
Фичи имеющие default => sub { ... }
выполняют sub
при первом вызове геттера, то есть: являются отложенными.
$object->has('фича')
позволяет проверить, что default
ещё не вызывался.
package ExHas { use Aion;
has x => (is => 'rw');
}
my $ex = ExHas->new;
$ex->has("x") # -> ""
$ex->x(10);
$ex->has("x") # -> 1
clear (@features)
Удаляет ключи фич из объекта предварительно вызвав на них clearer
(если есть).
package ExClearer { use Aion;
has x => (is => 'rw');
has y => (is => 'rw');
}
my $c = ExClearer->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
включает в модуль следующие методы:
new (%parameters)
Конструктор.
ASPECTS
use Aion
включает в модуль следующие аспекты для использования в has
:
is => $permissions
ro
— создать только геттер.wo
— создать только сеттер.rw
— создать геттер и сеттер.
По умолчанию — rw
.
Дополнительные разрешения:
+
— фича обязательна в параметрах конструктора.+
не используется с-
.-
— фича не может быть установлена через конструктор. '-' не используется с+
.*
— не инкрементировать счётчик ссылок на значение (применитьweaken
к значению после установки его в фичу).
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
Функция с *
не удерживает значение:
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
Указывает тип, а точнее – валидатор, фичи.
package ExIsa { use Aion;
has x => (is => 'ro', isa => Int);
}
eval { ExIsa->new(x => 'str') }; $@ # ~> \* Feature x must have the type Int. The it is 'str'
eval { ExIsa->new->x }; $@ # ~> Get feature `x` must have the type Int. The it is undef
ExIsa->new(x => 10)->x # -> 10
Список валидаторов см. в Aion::Type.
default => $value
Значение по умолчанию устанавливается в конструкторе, если параметр с именем фичи отсутствует.
package ExDefault { use Aion;
has x => (is => 'ro', default => 10);
}
ExDefault->new->x # -> 10
ExDefault->new(x => 20)->x # -> 20
Если $value
является подпрограммой, то подпрограмма считается конструктором значения фичи. Используется ленивое вычисление.
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
вызывается после установки свойства в конструкторе (new
) или через сеттер. Этимология – впустить.
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
вызывается перед возвратом свойства из объекта через геттер. Этимология – выпустить.
package ExRelease { use Aion;
has x => (release => sub {
my ($self, $value) = @_;
$_[1] = $value + 1;
});
}
my $ex = ExRelease->new(x => 10);
$ex->x # -> 11
Cleareer => $ SUB
$sub
вызывается при вызове декструктора или $object->clear("feature")
, но только если свойство имеется (см. $object->has("feature")
).
package ExClearer { use Aion;
our $x;
has x => (clearer => sub {
my ($self) = @_;
$x = $self->x
});
}
$ExClearer::x # -> undef
ExClearer->new(x => 10);
$ExClearer::x # -> 10
my $ex = ExClearer->new(x => 12);
$ExClearer::x # -> 10
$ex->clear('x');
$ExClearer::x # -> 12
undef $ex;
$ExClearer::x # -> 12
ATTRIBUTES
Aion
добавляет в пакет универсальные атрибуты.
Isa (@signature)
Атрибут Isa
проверяет сигнатуру функции.
ВНИМАНИЕ: использование атрибута «Isa» замедляет работу программы.
СОВЕТ: использования аспекта isa
для объектов более чем достаточно, чтобы проверить правильность данных объекта.
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\].
Author
Yaroslav O. Kosmina mailto:dart@cpan.org
LICENSE
⚖ GPLv3
COPYRIGHT
The Aion module is copyright © 2023 Yaroslav O. Kosmina. Rusland. All rights reserved.