Moops - Moops Object-Oriented Programming Sugar
use Moops; role NamedThing { has name => (is => "ro", isa => Str); } class Person with NamedThing; class Company with NamedThing; class Employee extends Person { has job_title => (is => "rwp", isa => Str); has employer => (is => "rwp", isa => InstanceOf["Company"]); method change_job ( Object $employer, Str $title ) { $self->_set_job_title($title); $self->_set_employer($employer); } method promote ( Str $title ) { $self->_set_job_title($title); } }
Unstable.
Until version 1.000, stuff might change, but not without good reason.
Moops is sugar for declaring and using roles and classes in Perl.
The syntax is inspired by MooseX::Declare, and Stevan Little's p5-mop-redux project (which is in turn partly inspired by Perl 6).
Moops has fewer than half of the dependencies as MooseX::Declare, loads in about 25% of the time, and the classes built with it run significantly faster. Moops does not use Devel::Declare, instead using Perl's pluggable keyword API; this requires Perl 5.14 or above.
Moops uses Moo to build classes and roles by default, but allows you to use Moose if you desire. (And Mouse experimentally.)
The class keyword declares a class:
class
class Foo { # ... }
A version number can be provided:
class Foo 1.2 { # ... }
If no version is provided, your class' $VERSION variable is set to the empty string; this helps the package be seen by Class::Load.
$VERSION
If your class extends an existing class through inheritance, or consumes one or more roles, these can also be provided when declaring the class.
class Foo::Bar 1.2 extends Foo 1.1 with Magic::Monkeys { # ... }
If you use Moops within a package other than main, then package names used within the declaration are "qualified" by that outer package, unless they contain "::". So for example:
main
package Quux; use Moops; class Foo { } # declares Quux::Foo class Xyzzy::Foo # declares Xyzzy::Foo extends Foo { } # ... extending Quux::Foo class ::Baz { } # declares Baz
If you wish to use Moose or Mouse instead of Moo; include that in the declaration:
class Foo using Moose { # ... }
It's also possible to create classes using Tiny (Class::Tiny), but there's probably little point in it, because Moops uses Moo internally, so the more capable Moo is already loaded and in memory.
using Tiny
(The using option is exempt from the package qualification rules mentioned earlier.)
using
Moops uses MooseX::MungeHas in your classes so that the has keyword supports some Moo-specific features, even when you're using Moose or Mouse. Specifically, it supports is => 'rwp', is => 'lazy', builder => 1, clearer => 1, predicate => 1, and trigger => 1. If you're using Moo, the MooX::late extension is enabled too, which allows Moose-isms in Moo too. With the combination of these features, there should be very little difference between Moo, Mouse and Moose has keywords.
has
is => 'rwp'
is => 'lazy'
builder => 1
clearer => 1
predicate => 1
trigger => 1
Moops uses Lexical::Accessor to provide you with private (lexical) attributes - that is, attributes accessed via a coderef method in a lexical variable.
class Foo { lexical_has foo => ( isa => Int, accessor => \(my $_foo), default => 0, ); method increment_foo () { $self->$_foo( 1 + $self->$_foo ); } method get_foo () { return $self->$_foo; } } my $x = Foo->new; $x->increment_foo(); # ok say $x->get_foo(); # says "1" $x->$_foo(42); # dies; $_foo does not exist in this scope
Moose classes are automatically accelerated using MooseX::XSAccessor if it's installed.
Note that it is possible to declare a class with an empty body; use a trailing semicolon.
class Employee extends Person with Employment;
If using Moose or Mouse, classes are automatically made immutable.
namespace::sweep is automatically used in all classes.
Between the class declaration and its body, Attribute::Handlers-style attributes may be provided:
class Person :mutable { # ... } class Employee extends Person with Employment :mutable;
The following attributes are defined for classes:
:assertions - enables assertion checking (see below)
:assertions
:dirty - suppresses namespace::sweep
:dirty
:fp - use Function::Parameters instead of Kavorka
:fp
:mutable - suppresses making Moose classes immutable
:mutable
:ro - make attributes declared with has default to 'ro'
:ro
:rw - make attributes declared with has default to 'rw'
:rw
:rwp - make attributes declared with has default to 'rwp'
:rwp
Roles can be declared similarly to classes, but using the role keyword.
role
role Stringable using Moose # we know you meant Moose::Role { # ... }
Roles do not support the extends option.
extends
Roles can be declared to be using Moo, Moose, Mouse or Tiny. (Note that if you're mixing and matching role frameworks, there are limitations to which class builders can consume which roles. Mouse is generally the least compatible; Moo and Moose classes should be able to consume each others' roles; Moo can also consume Role::Tiny roles.)
If roles use Moo, the MooX::late extension is enabled.
namespace::sweep is automatically used in all roles.
Roles take similar Attribute::Handlers-style attributes to classes, but don't support :mutable.
The namespace keyword works as above, but declares a package without any class-specific or role-specific semantics.
namespace
namespace Utils { # ... }
namespace::sweep is not automatically used in namespaces.
Attribute::Handlers-style attributes are supported for namespaces, but most of the built-in attributes make any sense without class/role semantics. (:assertions does.) Traits written as Moops extensions may support namespaces.
Moops uses Kavorka to declare functions and methods within classes and roles. Kavorka provides the fun and method keywords.
fun
method
class Person { use Scalar::Util 'refaddr'; has name => (is => 'rwp'); # Moo attribute method change_name ( Str $newname ) { $self->_set_name( $newname ) unless $newname eq 'Princess Consuela Banana-Hammock'; } fun is_same_as ( Object $x, Object $y ) { refaddr($x) == refaddr($y) } } my $phoebe = Person->new(name => 'Phoebe'); my $ursula = Person->new(name => 'Ursula'); Person::is_same_as($phoebe, $ursula); # false
Note function signatures use type constraints from Types::Standard; MooseX::Types and MouseX::Types type constraints should also work, provided you use their full names, including their package.
The is_same_as function above could have been written as a class method like this:
is_same_as
class Person { # ... method is_same_as ( $class: Object $x, Object $y ) { refaddr($x) == refaddr($y) } } # ... Person->is_same_as($phoebe, $ursula); # false
The method keyword is not provided within packages declared using namespace; only within classes and roles.
See also Kavorka::Manual::Methods and Kavorka::Manual::Functions.
Within Moose classes and roles, the MooseX::KavorkaInfo module is loaded, to allow access to method signatures via the meta object protocol. (This is currently broken for around method modifiers.)
around
In Moops prior to 0.025, Function::Parameters was used instead of Kavorka. If you wish to continue to use Function::Parameters in a class you can use the :fp attribute:
class Person :fp { ...; }
Or to do so for all classes in a lexical scope:
use Moops function_parameters_everywhere => 1; class Person { ...; }
Or the environment variable MOOPS_FUNCTION_PARAMETERS_EVERYWHERE can be set to true to enable it globally, but this feature is likely to be removed eventually.
MOOPS_FUNCTION_PARAMETERS_EVERYWHERE
Within classes and roles, before, after and around keywords are provided for declaring method modifiers. These use the same syntax as method.
before
after
If your class or role is using Moose or Mouse, then you also get augment and override keywords.
augment
override
See also Kavorka::Manual::MethodModifiers.
Moops uses Kavorka to implement multi subs and multi methods.
See also Kavorka::Manual::MultiSubs.
The Types::Standard type constraints are exported to each package declared using Moops. This allows the standard type constraints to be used as barewords.
Type constraints can be used in attribute definitions (isa) and method signatures. Because Types::Standard is based on Type::Tiny, the same type constraints may be used whether you build your classes and roles with Moo, Moose our Mouse.
isa
Alternative libraries can be imported using the types option; a la:
types
class Document types Types::XSD::Lite { has title => (is => 'rw', isa => NormalizedString); }
Note that if an alternative type constraint library is imported, then Types::Standard is not automatically loaded, and needs to be listed explicitly:
class Document types Types::Standard, Types::XSD::Lite { # ... }
Type libraries built with Type::Library, MooseX::Types and MouseX::Types should all work.
Bear in mind that type constraints from, say, a MooseX::Types library won't be usable in, say, Moo attribute definitions. However, it's possible to wrap them with Type::Tiny, and make them usable:
class Foo types MooseX::Types::Common::Numeric using Moo { use Types::TypeTiny qw( to_TypeTiny ); has favourite_number => ( is => 'rwp', isa => to_TypeTiny(PositiveInt) ); }
You can use the library keyword to declare a new type library:
library
library MyTypes extends Types::Standard declares EmptyString, NonEmptyString { declare EmptyString, as Str, where { length($_) == 0 }; declare NonEmptyString, as Str, where { length($_) > 0 }; } class StringChecker types MyTypes { method check ( Str $foo ) { return "empty" if EmptyString->check($foo); return "non-empty" if NonEmptyString->check($foo); return "impossible?!"; } }
Libraries declared this way can extend existing type libraries written with Type::Library, MooseX::Types or MouseX::Types.
Note that this also provides a solution to the previously mentioned problem of using MooseX::Types type libraries in Moo classes:
library MyWrapper extends MooseX::Types::Common::Numeric; class Foo types MyWrapper using Moo { has favourite_number => ( is => 'rwp', isa => PositiveInt, ); }
The useful constants true and false are imported into all declared packages. (Within classes and roles, namespace::sweep will later remove them from the symbol table, so they don't form part of your package's API.) These constants can help make attribute declarations more readable.
true
false
has name => (is => 'ro', isa => Str, required => true);
Further constants can be declared using the define keyword (see PerlX::Define):
define
namespace Maths { define PI = 3.2; }
Constants declared this way will not be swept away by namespace::sweep, and are considered part of your package's API.
Declared packages can contain assertions (see PerlX::Assert). These are normally optimized away at compile time, but you can force them to be checked using the :assertions attribute.
class Foo { assert(false); # not checked; optimized away } class Bar :assertions { assert(false); # checked; fails; throws exception }
strict and FATAL warnings are imported into all declared packages. However the uninitialized, void, once and numeric warning categories are explicitly excluded, as are any warnings categories added to Perl after version 5.14.
uninitialized
void
once
numeric
Perl 5.14 features, including the state and say keywords, and sane Unicode string handling are imported into all declared packages.
state
say
Try::Tiny is imported into all declared packages.
Scalar::Util's blessed and Carp's confess are imported into all declared packages.
blessed
confess
The "outer" package, where the use Moops statement appears also gets a little sugar: strict, the same warnings as "inner" packages, and Perl 5.14 features are all switched on.
use Moops
true is loaded, so you don't need to do this at the end of your file:
1;
It is possible to inject other functions into all inner packages using:
use Moops imports => [ 'List::Util' => [qw( first reduce )], 'List::MoreUtils' => [qw( any all none )], ];
This is by far the easiest way to extend Moops with project-specific extras.
There is a shortcut for injecting strictures into all inner packages:
use Moops -strict;
Moops is written to hopefully be fairly extensible.
The easiest way to extend Moops is to inject additional imports into the inner packages using the technique outlined in "Custom Sugar" above. You can wrap all that up in a module:
package MoopsX::Lists; use base 'Moops'; use List::Util (); use List::MoreUtils (); sub import { my ($class, %opts) = @_; push @{ $opts{imports} ||= [] }, ( 'List::Util' => [qw( first reduce )], 'List::MoreUtils' => [qw( any all none )], ); $class->SUPER::import(%opts); } 1;
Now people can do use MoopsX::Lists instead of use Moops.
use MoopsX::Lists
Roles in the Moops::TraitFor::Keyword namespace are automatically loaded and applied to keyword objects when a corresponding Attribute::Handlers-style attribute is seen.
Moops::TraitFor::Keyword
For examples extending Moops this way, see the Moops::TraitFor::Keyword::dirty, Moops::TraitFor::Keyword::mutable, Moops::TraitFor::Keyword::ro, Moops::TraitFor::Keyword::rw and Moops::TraitFor::Keyword::rwp traits.
For more complex needs, you can create a trait which will be applied to Moops::Parser.
Parser traits might want to override:
The keywords class method, which returns the list of keywords the parser can handle.
keywords
The class_for_keyword object method, which returns the name of a subclass of Moops::Keyword which will be used for translating the result of parsing the keyword into a string using Perl's built-in syntax.
class_for_keyword
Hopefully you'll be able to avoid overriding the parse method itself, as it has a slightly messy API.
parse
Your class_for_keyword subclass can either be a direct subclass of Moops::Keyword, or of Moops::Keyword::Class or Moops::Keyword::Role.
The keyword subclass might want to override:
The known_relationships class method, which returns a list of valid inter-package relationships such as extends and using for the current keyword.
known_relationships
The qualify_relationship class method, which, when given the name of an inter-package relationship, indicates whether it should be subjected to package qualification rules (like extends and with are, but using is not).
qualify_relationship
with
The version_relationship class method, which, when given the name of an inter-package relationship, indicates whether it should accept a version number.
version_relationship
The generate_package_setup object method which returns a list of strings to inject into the package.
generate_package_setup
The arguments_for_function_parameters object method which is used by the default generate_package_setup method to set up the arguments to be passed to Function::Parameters.
arguments_for_function_parameters
The check_prerequisites method which performs certain pre-flight checks and may throw an exception.
check_prerequisites
Hopefully you'll be able to avoid overriding the generate_code method.
generate_code
You can apply your trait using:
use Moops traits => [ 'Moops::TraitFor::Parser::FooKeyword', 'Moops::TraitFor::Parser::BarKeyword', ];
Please report any other bugs to http://rt.cpan.org/Dist/Display.html?Queue=Moops.
IRC: support is available through in the #moops channel on irc.perl.org.
For anything Moo-specific, you may have better luck in the #web-simple channel; or for Moose-specific queries, #moose.
Similar: MooseX::Declare, https://github.com/stevan/p5-mop-redux.
Main functionality exposed by this module: Moo/MooX::late, Kavorka, Try::Tiny, Types::Standard, namespace::sweep, true.
Internals fueled by: Keyword::Simple, Module::Runtime, Import::Into, Devel::Pragma, Attribute::Handlers.
http://en.wikipedia.org/wiki/The_Bubble_Boy_(Seinfeld).
Toby Inkster <tobyink@cpan.org>.
This software is copyright (c) 2013-2014 by Toby Inkster.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
To install Moops, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Moops
CPAN shell
perl -MCPAN -e shell install Moops
For more information on module installation, please visit the detailed CPAN module installation guide.