Aion
view release on metacpan or search on metacpan
lib/Aion.pm view on Meta::CPAN
package Aion;
use common::sense;
our $VERSION = "2.3";
use Aion::Types qw//;
use Aion::Meta::RequiresAnyFunction;
use Aion::Meta::Feature;
use Aion::Meta::RequiresFeature;
use Aion::Meta::Subroutine;
# Ðогда оÑÑÑеÑÑвлÑÑÑ Ð¿ÑовеÑки:
# ro - ÑолÑко пÑи вÑдаÑе
# wo - ÑолÑко пÑи ÑÑÑановке
# rw - пÑи вÑдаÑе и ÑcÑановке
# no - никогда не пÑовеÑÑÑÑ
use Aion::Env AION_ISA => (default => 'rw');
sub export($@);
# ÐлаÑÑÑ Ð² коÑоÑÑÑ
подклÑÑÑн Aion Ñ Ð¼ÐµÑаинÑоÑмаÑией
our %META;
# ÐÑзÑваеÑÑÑ Ð¸Ð· дÑÑгого пакеÑа, Ð´Ð»Ñ Ð¸Ð¼Ð¿Ð¾ÑÑа данного
sub import {
my (undef, $attr) = @_;
my $pkg = caller;
*{"$pkg\::DOES"} = \&does if \&does != $pkg->can('DOES');
if($attr ne '-role') { # ÐлаÑÑ
export $pkg, qw/extends/;
*{"${pkg}::new"} = \&initialize;
} else { # РолÑ
export $pkg, qw/requires req/;
}
export $pkg, qw/with has aspect does exactly/;
# ÐеÑаинÑоÑмаÑиÑ
$META{$pkg} = {
order => scalar keys %META,
require => {},
feature => {},
subroutine => {},
aspect => {
is => \&is_aspect,
isa => \&isa_aspect,
coerce => \&coerce_aspect,
lazy => \&lazy_aspect,
default => \&default_aspect,
trigger => \&trigger_aspect,
release => \&release_aspect,
init_arg => \&init_arg_aspect,
accessor => \&accessor_aspect,
writer => \&writer_aspect,
reader => \&reader_aspect,
predicate => \&predicate_aspect,
clearer => \&clearer_aspect,
cleaner => \&cleaner_aspect,
eon => \&eon_aspect,
}
};
eval "package $pkg; use Aion::Types; 1" or die;
}
# УдалÑÐµÑ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð½Ñе ÑимволÑ
sub unimport {
my $pkg = caller;
undef &{"${pkg}::$_"} for qw/extends with aspect requires req/;
eval "package $pkg; no Aion::Types; 1" or die;
}
# ÐкÑпоÑÑиÑÑÐµÑ ÑÑнкÑии в пакеÑ, еÑли иÑ
Ñам еÑÑ Ð½ÐµÑ
sub export($@) {
my $pkg = shift;
for my $sub (@_) {
my $can = $pkg->can($sub);
die "$pkg can $sub!" if $can && $can != \&$sub;
*{"${pkg}::$sub"} = \&$sub unless $can;
}
}
# ÐÑовеÑÑеÑ, ÑÑо ÑÑÐ¾Ñ Ð¿Ð°ÐºÐµÑ Ð¸Ð½Ð¸ÑиализиÑован Aion
sub is_aion($) {
my $pkg = shift;
die "$pkg is'nt class of Aion!" if !exists $META{$pkg};
}
#@category Aspects
# ro, rw, + и -, *
sub is_aspect {
my ($is, $feature) = @_;
die "Use is => '{ro|rw|wo|no} {+|-} {*} {?} {!}'" if $is !~ /^(?<access>ro|rw|wo|no)?(?<require>[+-])?(?<weak>\*)?(?<has>\??)(?<clear>!?)\z/;
my ($construct, $name) = @$feature{qw/construct name/};
$construct->getter("die 'Feature $name cannot be get!';") if $+{access} ~~ [qw/wo no/];
$construct->setter("die 'Feature $name cannot be set!';") if $+{access} ~~ [qw/ro no/];
$construct->add_trigger("%(weaken)s") if $+{weak};
$feature->{required} = 1, $construct->not_specified(' else { die "%(init_arg)s required!" }') if $+{require} eq '+';
$feature->{excessive} = 1, $construct->initer('die "%(init_arg)s excessive!"') if $+{require} eq '-';
$feature->{make_predicate} = 1 if $+{has};
$feature->{make_clearer} = 1 if $+{clear};
}
# isa => Type
sub isa_aspect {
my ($isa, $feature) = @_;
my ($construct, $name) = @$feature{qw/construct name/};
$feature->{isa} = Aion::Types::External[$isa];
$construct->add_release("${\$feature->meta}\{isa}->validate(\$val, 'Get feature $name');") if AION_ISA =~ /ro|rw/;
$construct->add_preset("${\$feature->meta}\{isa}->validate(\$val, 'Set feature $name');") if AION_ISA =~ /wo|rw/;
}
# coerce => 1
sub coerce_aspect {
my ($coerce, $feature) = @_;
return unless $coerce;
lib/Aion.pm view on Meta::CPAN
=head2 pleroma ()
Returns the locator.
Aion->pleroma->isa('Aion::Pleroma') # -> 1
=head1 SUBROUTINES IN CLASSES
=head2 extends (@superclasses)
Expands the class with another class/classes. It causes from each inherited class the method of C<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
=head2 new (%param)
The constructor.
=over
=item * Installs C<%param> for features.
=item * Checks that the parameters correspond to the features.
=item * Sets default values.
=back
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
=head1 SUBROUTINES IN ROLES
=head2 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
=head2 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
=head2 Role inherits role
A role can inherit another role via C<with>. This way you can refine the interface: the types of required features (C<req>) and methods (C<:Isa>) either remain the same or are lowered - they become narrower subtypes. Demotion is checked by the less-t...
The role C<Role::Animal> requires a property C<legs> of the wide type C<Num | Object> and the C<sound> method with signature C<< (Me =E<gt> Str) >>.
package Role::Animal { use Aion -role;
req legs => (isa => Num | Object);
sub sound : Isa(Me => Str);
}
lib/Aion.pm view on Meta::CPAN
C<use Aion> includes the following aspects in the module for use in C<has>:
=head2 is => $permissions
=over
=item * C<ro> - create only a gutter.
=item * C<wo> - create only a setter.
=item * C<rw> - Create getter and setter.
=back
By default - C<rw>.
Additional permits:
=over
=item * C<+> â the feature is required in the constructor parameters. C<+> is not used with C<->.
=item * C<-> â the feature cannot be installed via the constructor. '-' is not used with C<+>.
=item * C<*> â do not increment the value's reference counter (apply C<weaken> to the value after installing it in the feature).
=item * C<?> â create a predicate.
=item * C<!> â create clearer.
=back
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 C<*> 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
=head2 isa => $type
Indicates the type, or rather - a validator, feature.
Can take:
=over
=item * C<Aion::Type> â Aion immediately imports all types from L<Aion::Types> into the package.
=item * Strings are treated as packets and wrapped in C<Object>.
=item * Subroutines - the test value is passed to C<$_> and the subroutine returns a boolean value.
=item * Objects with overloaded C<&{}> operator. If such an object also has a C<coerce> method, then it will participate in casts if C<< coerce =E<gt> 1 >> is specified.
=back
package Externalis {
use overload '&{}' => sub { sub { /^\d+$/ } };
sub coerce { int $_ }
}
package ExIsa { use Aion;
has x => (isa => Int);
has y => (isa => sub { /^\d+$/ });
has z => (isa => bless({}, 'Externalis'), coerce => 1);
}
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
ExIsa->new(y => 'abc') # @-> Set feature y must have the type External[CODE
ExIsa->new(z => ' 6 xyz')->z # -> 6
=head2 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
=head2 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 C<$value> is a subroutine, then the subroutine is considered the feature's value constructor. Lazy evaluation is used if there is no C<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
=head2 lazy => (1|0)
The C<lazy> aspect enables or disables lazy evaluation of the default value (C<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
=head2 eon => (1|2|$key)
The C<eon> aspect implements the B<Dependency Injection> pattern.
It associates a property with a service from the C<< Aion-E<gt>pleroma >> container.
The aspect value can be a service key, 1 or 2.
=over
=item * If 1 â then the key will be the package in C<< isa =E<gt> Object['Packet'] >>.
=item * If 2 â then the key will be âpackage#propertyâ.
=back
File lib/CounterEon.pm:
package CounterEon;
#@eon ex.counter
use Aion;
has accomulator => (isa => 'AccomulatorEon', eon => 1);
1;
File lib/AccomulatorEon.pm:
package AccomulatorEon;
#@eon
use Aion;
has power => (isa => 'PowerEon', eon => 2);
1;
lib/PowerEon.pm file:
package PowerEon;
use Aion;
has counter => (eon => 'ex.counter');
#@eon
sub power { shift->new }
1;
We use pleroma locally:
{
use Aion::Pleroma;
my $pleroma = Aion::Pleroma->new(ini => undef, pleroma => {
'ex.counter' => 'CounterEon#new',
AccomulatorEon => 'AccomulatorEon#new',
'PowerEon#power' => 'PowerEon#power',
});
local *Aion::pleroma = sub { $pleroma };
my $counter = Aion->pleroma->get('ex.counter');
$counter->accomulator->power->counter # -> $counter
}
Aion->pleroma->get('ex.counter') # -> undef
See L<Aion::Pleroma>.
=head2 trigger => $sub
C<$sub> is called after setting the property in the constructor (C<new>) or via a setter.
The etymology of C<trigger> is to 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
=head2 release => $sub
C<$sub> is called before returning a property from an object via a getter.
The etymology of C<release> is to release.
package ExRelease { use Aion;
has x => (release => sub {
my ($self, $value) = @_;
$_[1] = $value + 1;
});
}
my $ex = ExRelease->new(x => 10);
$ex->x # -> 11
=head2 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
}
=head2 accessor => $name
Changes the accessor name.
package ExAccessor { use Aion;
has x => (is => 'rw', accessor => '_x');
ExAccessor->new->_x(10)->_x # -> 10
}
=head2 writer => $name
lib/Aion.pm view on Meta::CPAN
has x => (is => 'ro', writer => '_set_x');
ExWriter->new->_set_x(10)->x # -> 10
}
=head2 reader => $name
Creates a getter named C<$name> for a property.
package ExReader { use Aion;
has x => (is => 'wo', reader => '_get_x');
ExReader->new(x => 10)->_get_x # -> 10
}
=head2 predicate => $name
Creates a predicate named C<$name> for a property. You can also create a predicate with a standard name using C<< is =E<gt> '?' >>.
package ExPredicate { use Aion;
has x => (predicate => '_has_x');
my $ex = ExPredicate->new;
$ex->_has_x # -> ""
$ex->x(10)->_has_x # -> 1
}
=head2 clearer => $name
Creates a cleaner named C<$name> for a property. You can also create a cleaner with a standard name using C<< is =E<gt> '!' >>.
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 # -> ""
=head2 cleaner => $sub
C<$sub> is called when the destructor or C<< $object-E<gt>clear_feature >> is called, but only if the feature is present (see C<< $object-E<gt>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
=head1 ATTRIBUTES
C<Aion> adds universal attributes to the package.
=head2 :Isa (@signature)
The attribute C<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
=head1 SEE ALSO
Aion Ecosystem:
=over
=item * L<Aion::Annotation>
( run in 0.516 second using v1.01-cache-2.11-cpan-d80b1682f3f )