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

Evo::Class

VERSION

version 0.0259

DESCRIPTION

Fast full featured post-modern Object oriented programming. Available both in PP and C. See https://github.com/alexbyk/perl-evo/tree/master/bench

SYNOPSYS

  package main;
  use Evo;

  {

    package My::Human;
    use Evo -Class, -Loaded;

    has 'name' => 'unnamed', rw;
    has 'gender';
    has age => optional, rw, check sub($v) { $v >= 18 };
    sub greet($self) { say "I'm " . $self->name }
  }

  my $alex = My::Human->new(gender => 'male');

  # default value "unnamed"
  say $alex->name;

  # fluent design
  $alex->name('Alex')->age(18);
  say $alex->name, ': ', $alex->age;

  # method
  $alex->greet;

  ## ------------ protecting you from errors, uncomment to test
  ## will die, gender is required
  #My::Human->new();

  ## will die, age must be >= 18
  #My::Human->new(age => 17, gender => 'male');
  #My::Human->new(gender => 'male')->age(17);

  # --------- code reuse
  {

    package My::Developer;
    use Evo -Class;
    with 'My::Human';    # extends 'My::Human'; implements 'My::Human';

    has lang => 'Perl';

    sub show($self) {
      $self->greet();
      say "I like ", $self->lang;
    }


  }

  my $dev = My::Developer->new(gender => 'male');
  $dev->show;

SYNTAX

DIFFERENCES WITH SIMILAR MODULES

You will find thet syntax differs from other modules, such Moose, Moo. That's because I decided not to copy and made it to be as short/safe/obvious/common as possible. Give it a try

Also multiple inheritance is considered a good development pattern, because the flat code reuse (mixing) is used.

EXPORTING SYNTAX

By default this module exports some keywords, like has, check, rw and so on. If your code conflicts with them, don't worry, perl will notify you and you can either rename conflicting methods, or exclude them from exporting/rename them this way:

  use Evo '-Class * -check -has has:attr';
  attr foo => 'FOO';

  sub check { }
  sub has   { }

  say __PACKAGE__->new->foo;

We skipped check and has, because they conflict with our methods, and export has under attr name, because we need it.

Usage

creating an object

  package My::Class;
  use Evo -Class;
  has 'simple';

new

  my $foo = My::Class->new(simple => 1);
  my $foo2 = My::Class->new();

We're protected from common mistakes, because constructor won't accept unknown attributes. Also, if attributes aren't optional and have additional flags, they will be checked too.

Attributes

  has 'foo';
  has 'bar' => 'BAR', rw, check sub {1};
  has 'baz' => rw, 'BAZ';

Without options attributes are required and read-only. You can pass extra flags/options + a default value in any order. If you make a mistake, smart syntax parser will notify you. In the example above default values are BAR and BAZ. Pay attention, rw and check are not strings, so 'rw' or check => is a mistake.

has_dummy

  has_dummy 'dummy';

Like attribute, but doesn't install methods, only check constructor's arguments. Can be used to make things work with non-evo classes

Flags and Options

rw

Make attribute read-write

default value

Default value can be a scalar or a code reference, which will be called with a class as the first argument, unless lazy flag is passed

  has 'def_code' => sub($class) { uc "$class" };
  say __PACKAGE__->new->def_code;

You can't use a reference, except a code reference, as a default value. To return, for example, a hashref, use this:

  has foo => sub($class) { { class => $class } };
  say __PACKAGE__->new->foo->{class};

lazy

This flag changes a behaviour of default value. It should be a code that will be called at the first invocation, not in constructor, and an instance will be passed as the argument. The result of this invocation will be stored in attribute

  has foo => lazy, sub($self) { [] };
  say __PACKAGE__->new->foo;

You should know that using this feature is an antipattern in the most of the cases.

optional

  has 'foo', optional;

By default, attributes are required. You can pass this flag to mark attribute as optional (but in most cases this is antipattern)

check

You can provide function that will check passed value (via constuctor and changing), and if that function doesn't return true, an exception will be thrown.

  has big => check sub { shift > 10 };

You can also return (0, "CustomError") to provide more expressive explanation

  package main;
  use Evo;

  {

    package My::Foo;
    use Evo '-Class *';

    has big => rw, check sub($val) { $val > 10 ? 1 : (0, "not > 10"); };
  };

  my $foo = My::Foo->new(big => 11);

  $foo->big(9);    # will die
  my $bar = My::Foo->new(big => 9);    # will die

inject

Used to describe dependencies of a class. We can build Foo that depends on Bar and we don't care how Bar is implemented. Evo::Di will resolve all dependencies

  package Foo;
  use Evo -Class, -Loaded;

  has bar => inject 'Bar';

  package Bar;
  use Evo -Class, -Loaded;
  has host => inject 'HOST';

  package main;
  use Evo '-Di';
  my $di = Evo::Di->new();
  $di->provide(HOST => '127.0.0.1');

  my $foo = $di->single('Foo');
  say $foo->bar->host;

See Evo::Di for more information.

CODE REUSE

Only public functions will be inherited.

Subroutine will be considered is a private if: It's imported from other modules It's monkey patched from other module by *Foo::foo = sub {} It's exported by Evo::Export functions, (when a package is both a library and a class)

Subroutine will be considered as a public if: It is defined in a class. It is inherited by extends or with

All attributes are public.

Methods, generated somehow else, for example by *foo = sub {}, can be marked as public by "reg_method" in Evo::Class::Meta

Private methods

If you want to mark a method as private, use new lexical_subs feature

  my sub _private {'private'}

This if preferred way to garantee that no one will be able to use this subroutine.

In some cases you may want to access private method for testing in the parent, but make unaccessible from a child. In this case use the folowing example:

  package Foo;
  use Evo -Class, -Loaded;
  sub _hello($self) : Private { 'hello from ' . $self }
  sub greet($self)            { say _hello($self) }

  package Bar;
  use Evo -Class;
  with 'Foo';


  package main;
  Bar->new->greet;
  say 'Foo can _hello?: ', !!Foo->can('_hello');
  say 'Bar can _hello?: ', !!Bar->can('_hello');

Pay attention. _hello method should be called in this way: _hello($self), NOT $self->_hello - this will break your code.

:Private attribute marks a method as private making an invocation of "mark_as_private" in Evo::Class::Meta to skip this method from inheritance process

And of course, many developers just name methods like _private (this method is actually private) to show that it can be changed, call them as they want and don't bother about clashing problems at all. Choose your own way.

Overriding

Evo protects you from method clashing. But if you want to override method or fix clashing, use "has_over" function or :Override attribute

    package My::Peter;
    use Evo -Class;
    with 'My::Human';

    has_over name => 'peter';
    sub greet : Over { ... }

If you want to call parent's method, call it by full name instead of SUPER

  sub foo($self) : Over {
    say "Child";
    $self->My::Parent::foo();
  }

FUNCTIONS

This functions will be exported by default even without export list use Evo::Class; You can always export something else like use Evo::Class 'has'; or export nothing use Evo::Class ();

META

Return current Evo::Class::Meta object for the class.

  use Data::Dumper;
  say Dumper __PACKAGE__->META->info;

See what's going on with the help of "info" in Evo::Class::Meta

extends

Extends classes or roles. See also "EXTENDING ALIEN CLASSES"

implements

Check if all required methods are implemented. Like interfaces

with

This does "extend + check implementation". Consider this example:

  package main;
  use Evo;

  {

    package My::Role::Happy;
    use Evo -Class, -Loaded;

    requires 'name';

    sub greet($self) {
      say "My name is ", $self->name, " and I'm happy!";
    }

    package My::Class;
    use Evo -Class;

    has name => 'alex';

    #extends 'My::Role::Happy';
    #implements 'My::Role::Happy';
    with 'My::Role::Happy';

  }

  My::Class->new()->greet();

My::Role::Happy requires name in derivered class. We could install shared code with extends and then check implemantation with implements. Or just use with wich does both.

You may want to use extends and implements separately to resolve circular requirements, for example

CODE ATTRIBUTES

  sub foo : Over { 'OVERRIDEN'; }

Mark name as overridden. Overridden means it will override the "parent's" method with the same name without diying

EXTENDING ALIEN CLASSES

In some case you may wish to inherite from non-evo classes using @ISA. Evo::Class will croak an error, if you try to use "extend" or "with" in this case. But you can do it old fashiot way use parent 'Some::Alien::Class';

While Evo::Class doesn't use @ISA, it popolates @Child::ISA with @Parent::ISA to work with old classes. But in this case it won't help you to avoid classical problems:

  package My::Alien;
  use Evo -Loaded;
  sub foo {'ALIEN'}

  package My::Parent;
  use Evo -Loaded, -Class;

  # with 'My::Alien'; # will die, use instead:
  use parent 'My::Alien';
  sub bar {'EVO'}

  package My::Child;
  use Evo -Class;
  with 'My::Parent';

  package main;
  use Evo;
  say My::Child->new->foo;    # ALIEN
  say My::Child->new->bar;    # EVO
  say @My::Child::ISA;        # My::Alien

In this example My::Alien isn't Evo class. So we inherit it like use parent 'My::Alien';. My::Child gets bar from My::Parent, and foo works too by classical inheritance mechanism @ISA.

But if you then write a second child and acccidentally rewrite foo, Evo::Class won't be able to find a bug, because foo is an alien method.

  package My::Child2;
  use Evo -Class;
  with 'My::Parent';
  sub foo {};

But it will find a bug in this case, because My::Parent provides bar method and My::Child3::bar is written without :Over attribute

  package My::Child3;
  use Evo -Class;
  with 'My::Parent';
  sub bar {};

INTERNAL

Every class gets $EVO_CLASS_META variable which holds an Evo::Class::Meta instance. See "register" in Evo::Class::Meta

FAQ

Q.: Why another OO module?

A.: Why not?

Q.: Why not traditional attributes syntax.

A.: See it yourself:

Moose:

  use Moose;
  has foo => is => 'ro', default => 'FOO';
  has bar => is => 'rw', default => 'BAR';

Evo::Class

  use Evo -Class;
  has foo => 'FOO';
  has bar => rw, 'BAR';

Q.: Why not perl's inheritance mechanism

A.: Read the docs, you will understand why

A. long: The simmilar functionality can be implemented with ISA and traversing a dependency tree and comparing a lot of method, caching them. This is too many lines of code. OO Inheritance is an abstraction only. Perl's default implementation works somehow and somewhere, this module tries to make a code safer.

Consider an example:

  package My::Foo;
  use Evo -Loaded;
  use Fcntl 'SEEK_CUR';

  package My::Bar;
  use parent 'My::Foo';
  say __PACKAGE__->can('SEEK_CUR');

You don't expect My::Bar having a method SEEK_CUR, but that's perl classical inheritance problem. Other module like Moose provide autocleaners, Evo do all the job for you

  package My::Foo;
  use Evo -Loaded, -Class;
  use Fcntl 'SEEK_CUR';
  sub foo { }

  package My::Bar;
  use Evo -Class;
  with 'My::Foo';
  say __PACKAGE__->can('SEEK_CUR');

It's smart enough to understand that SEEK_CUR is a constant and not a method.

AUTHOR

alexbyk.com

COPYRIGHT AND LICENSE

This software is copyright (c) 2016 by alexbyk.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.