1. Moo in practice - App::Math::Tutor
Jens Rehsack
Niederrhein Perl Mongers
2014-2015
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 1 / 41
2. Introduction Motivation
Motivation
Moo and App::Math::Tutor
real world examples over far-fetched conceptuals
MooX::Cmd, MooX::Options and MooX::ConfigFromFile provide way more
features and flexibility than either
◮ App::Cmd with Getopt::Long::Descriptive
◮ MooseX::App::Cmd along with corresponding MooseX wrappers around
related stuff
2nd
generation of modern OO in Perl5
App::Math::Tutor
Allow parents help their children improving their mathematical skills
Add support for exercise types as children require
provide extensible design to allow easy augment of exercise
Goal: Improve to Web-Service, eg. by mapping MooX::Cmd to URI path and
MooX::Options to GET parameters
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 3 / 41
3. Introduction Audience
Audience
Audience
Developers who want to create or improve Perl5 software
Developers who want to learn how to develop modern OO with Perl5
Developers who are interested in developing mathematical exercises
Prerequisites of the Audience
Following knowledge is expected:
General knowledge about object oriented programming or concepts like
◮ classes
◮ objects
◮ polymorphism, inheritance and/or roles
◮ methods, class functions
◮ attributes, properties
slightly above basic Perl experience
ever heard of Smalltalk and its OO-concept is a strong bonus
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 4 / 41
4. Modules Classes and Roles
Classes in Moo
classes can be instantiated
one can inherit from classes
one can aggregate classes
distinguish naming conflicts is up to developer
{
package Natural;
use Moo;
sub _stringify { ... };
}
{
package Roman;
use Moo;
extends "Natural";
sub _stringify { ... };
}
my $natnum = Natural ->new( value => 42 ); say $natnum ->_stringify (); # 42
my $romnum = Roman ->new( value => 42 ); say $romnum ->_stringify (); # XLII
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 6 / 41
5. Modules Classes and Roles
Roles in Moo
roles describe a dedicated behavior (e.g. printable)
roles can be composed into classes
one can’t inherit from roles - only consume
roles cannot exist stand-alone
roles are consumed once
naming conflicts cause compile time error
{ package Printable;
use Moo:: Role; # now it’s a role - no ’is a’ relationship anymore
sub print { my $self = shift; say $self ->_stringify }
}
{ package Natural;
use Moo; # class
with "Printable"; # consumes a role
sub _stringify { ... };
}
my $natnum = Natural ->new( value => 42 ); $natnum ->print; # 42
my $romnum = Roman ->new( value => 42 ); $romnum ->print; # XLII
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 7 / 41
6. Attributes Attributes in Moo
Attributes in Moo
package VulFrac;
use Moo;
has nume rator => ( is => "ro", required => 1 );
has denom inator => ( is => "ro", required => 1 );
1;
use ”has” keyword to define a attribute
attributes ”numerator” and ”denominator”
attributes are immutable and required
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 8 / 41
7. Attributes Attribute Options
Attribute options - Selection I
is mandatory behavior description
ro defines the attribute is read-only
rw defined the attribute is read/writable
lazy defines the attribute is read-only with a lazy
initialization, implies builder => 1
required when set to a true value, attribute must be passed on instantiation
init arg Takes the name of the key to look for at instantiation time of the
object. A common use of this is to make an underscored attribute
have a non-underscored initialization name. undef means that
passing the value in on instantiation is ignored.
isa defines a subroutine (coderef) which is called to validate values to
set
coerce defines a subroutine (coderef) which forces attribute values
default subroutine (coderef) which is called to initialize an attribute
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 9 / 41
8. Attributes Attribute Options
Attribute options - Selection II
Following options can benefit from attribute shortcuts: the value of 1 instead of a
method name means to generate a coderef which calls the method
${option name} ${attr name}
builder takes a method name (string) which is called to initialize an
attribute (calls build ${attr name} on attribute shortcut)
trigger takes a subroutine (coderef) which is called anytime the attribute
is set (calls trigger ${attr name} on attribute shortcut)
clearer takes a method name (string) which will clear the attribute
(provides clear ${attr name} on attribute shortcut)
predicate takes a method name (string) which will return true if an
attribute has a value (provides has ${attr name} on attribute
shortcut)
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 10 / 41
9. Methods Method Examples
Methods in Moo I
package VulFrac;
use Moo;
use overload ’""’ => " _stringify",
’0+’ => "_numify",
’bool ’ => sub { 1 },
’<=>’ => " _num_compare";
has numerator => ( is => "ro", required => 1 );
has denominator => ( is => "ro", required => 1,
isa => sub { $_ [0] != 0 or die "Not != 0" };
sub _stringify { my $self = shift;
return sprintf(" frac {%s}{%s}",
$self ->numerator , $self -> denominator ); }
sub _numify { $_[0]-> numerator / $_[0]-> denominator ; }
...
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 11 / 41
10. Methods Method Examples
Methods in Moo II
package Rationale;
use Moo;
extends "VulFrac";
has digits => ( is => "ro", required => 1 );
sub _stringify {
my $digits = $_[0]-> digits;
sprintf("%.${digits}g", $_[0]-> _numify ); }
nothing like MooseX::Declare - pure Perl5 keywords are enough for plain
methods
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 12 / 41
11. Methods Method Modifiers
Method Modifiers
Method modifiers are a convenient feature from the CLOS (Common Lisp Object
System) world:
before before method(s) => sub { ...}
before is called before the method it is modifying. Its return value
is totally ignored.
after after method(s) => sub { ...}
after is called after the method it is modifying. Its return value is
totally ignored.
around around method(s) => sub { ...}
around is called instead of the method it is modifying. The method
you’re overriding is passed as coderef in the first argument.
- No support for super, override, inner or augment
In doubt MooX::Override provides override and super while MooX::Augment
provides augment and inner
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 13 / 41
12. Methods Method Modifiers
Method Modifiers - Advantages
supersedes $self->SUPER::foo(@ ) syntax
cleaner interface than SUPER
allows multiple modifiers in single namespace
also possible from within roles and not restricted to inheritance
ensures that inherited methods invocation happens right (mostly - remember
around)
no need to change packages
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 14 / 41
13. Methods Method Modifiers
Methods Modifiers - around avoid calling $orig
package App:: Math :: Tutor :: Role :: Roman;
use Moo:: Role;
with "App:: Math :: Tutor :: Role :: Natural";
{ package RomanNum;
use Moo;
extends "NatNum"; # derives overloading !
sub _stringify { ... } }
around " _guess_natural_number" => sub {
my $orig = shift;
my $max_val = $_[0]-> format;
my $value = int( rand( $max_val - 1 ) ) + 1;
return RomanNum ->new( value => $value );
};
captures control
receives responsibility
runtime of modified method completely eliminated
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 15 / 41
14. Methods Method Modifiers
Methods Modifiers - around modifying $orig return value
package App:: Math :: Tutor :: Role :: Roman;
use Moo:: Role;
with "App:: Math :: Tutor :: Role :: Natural";
{ package RomanNum;
use Moo;
extends "NatNum"; # derives overloading !
sub _stringify { ... } }
around " _guess_natural_number" => sub {
my $orig = shift;
my $num = $orig ->(@_);
return RomanNum ->new( value => $num ->value );
};
modifies only required part
leaves most responsibility in modified method
runtime of modified method added to this method’s runtime
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 16 / 41
16. Structure CLI Concept
Exercise Groups
App::Math::Tutor::Cmd::VulFrac Exercises in vulgar fraction calculation
App::Math::Tutor::Cmd::Natural Exercises in calculations using natural numbers
App::Math::Tutor::Cmd::Roman Exercises in calculations using natural numbers,
but show them using roman number encoding (exercises and
solutions)
App::Math::Tutor::Cmd::Unit Exercises in calculations using units (times,
currency, . . . )
App::Math::Tutor::Cmd::Power Exercises in calculations of power mathematics
App::Math::Tutor::Cmd::Polynom Exercises for polynomial mathematics (Zero of
a function, Vertex, . . . )
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 19 / 41
17. Design Simple Exercise
Typical Exercise design
App::Math::Tutor::Cmd::VulFrac::Cmd::Add
build exercise() : exercise
App::Math::Tutor::Role::VulFracExercise
format
App::Math::Tutor::Role::VulFrac
check vulgar fraction() : bool
guess vulgar fraction() : VulFrac
get vulgar fractions() : [ VulFrac ]
VulFrac
num
denum
stringify() : Str
numify() : Num
num compare() : Int
euklid() : Int
gcd() : Int
reduce() : VulFrac
reciprocal() : VulFrac
App::Math::Tutor::Role::Exercise
exercise : lazy
# { section, caption, label,
# header, challenges, solutions }
output name : lazy
template filename : lazy
build exercise() = 0 : exercise
build output name() : Str
build template filename() = 0 : Str
execute()
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 20 / 41
18. Design Derived Exercise
Derived Exercise design
App::Math::Tutor::Cmd::Roman::Cmd::Add
build exercise() : exercise
App::Math::Tutor::Role::NaturalExercise
format
App::Math::Tutor::Role::Roman
around => guess natural number() : Roman
App::Math::Tutor::Role::Natural
check natural number() : bool
guess natural number() : Natural
get natural number() : [ Natural ]
RomanNum
stringify() : Str
Natural
value
stringify() : Str
numify() : Num
num compare() : Int
App::Math::Tutor::Role::Exercise
exercise : lazy
# { section, caption, label,
# header, challenges, solutions }
output name : lazy
template filename : lazy
build exercise() = 0 : exercise
build output name() : Str
build template filename() = 0 : Str
execute()
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 21 / 41
19. Improvements Design
Improving design
introduce factories
add lazy factory attribute (allowes overriding factory to use by around’ing
builder)
separate number classes (type system, but no MooseX::Types)
approval for reasonable exercise value should be part of factory
approval for valid number should be coerced (trigger?)
Design history
I started with just vulgar fraction exercises
Design covered exactly those needs
Design future
Modern Perl-OO allowes easy refactoring to apply above improvements
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 22 / 41
20. MooX General purpose eXtensions
MooX in general
Some Moo extensions for getting a picture - the list is neither complete nor is it
intended to be
MooX Distributions
MooX::Log::Any logging role building a very lightweight wrapper to Log::Any
MooX::Validate minimalist Data Validation for Moo
MooX::Types::MooseLike Moose like types for Moo
MooX::Override adds ”override method => sub . . . ” support to Moo
MooX::Augment adds ”augment method => sub . . . ” support to Moo
MooX::late easily translate Moose code to Moo
MooX::PrivateAttributes create attribute only usable inside your package
MooX::Singleton turn your Moo class into singleton
MooX::Aliases easy aliasing of methods and attributes in Moo
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 24 / 41
21. MooX CLI related eXtensions
MooX Distributions for CLI
MooX::Cmd
giving an easy Moo style way to make command organized CLI apps
support sub-commands with separated options when used together with
MooX::Options
MooX::Options
explicit Options eXtension for Object Class
no mess with composed attributes
MooX::ConfigFromFile
Moo eXtension for initializing objects from config file
RC files with structures (JSON, INI, YAML, XML, ...)
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 25 / 41
22. MooX CLI related eXtensions
General CLI Construction Flow
:main :MooX::Cmd :MooX::Options :MooX::ConfigFromFile :Moo
new with cmd()
buildcommand
commands()
splitargv
new with options()
parse
options
new()
BUILDARGS()
super->BUILDARGS()
buildloaded
config
helpon
exception
execute
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 26 / 41
23. Provided Framework Exercise Role
package App:: Math :: Tutor :: Role :: Exercise;
use strictures; # instead of strict + warnings
use Moo:: Role; use MooX :: Options;
option quantity => (
is => "ro",
doc => "Specifies number of exercises to generate",
long_doc => "Specify number of exercises to generate. "
. "In case of several kind of ...",
format => "i",
short => "n",
default => sub { 15 },
);
has output _name => ( is => "lazy" );
sub _build_output_name {
join( "", map { $_ -> command_name || "" } @{ $_[0]-> command_chain } );
}
being a role (no ”is a” relationship) and add CLI options capabilities
defining an attribute which will be initialized by command line
support MooX::Option’s --man usage feature
attribute for output file to generate — lazy to allow provide individual
name builder
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 28 / 41
24. Provided Framework Exercise Role
has exercises => ( is => "lazy" );
sub execute {
my $exercises = $_[0]->exercises;
my $ttcpath = File ::Spec ->catfile(
File :: ShareDir :: dist_dir("App -Math -Tutor"),
$_[0]-> template_filename . ".tt2" );
my $template = Template ->new( { ABSOLUTE => 1 });
my $rc = $template ->process( $ttcpath , {
exercises => $exercises ,
output => { format => ’pdf’ },
},
$_[0]-> output_name . ’.pdf’);
$rc or croak( $template ->error () );
return 0;
}
attribute containing the exercises definitions, exercises itself and their
solutions (depending on template) — lazy implies requires
’’ build exercises’’
method to satisfy MooX::Cmd
fetch exercises definition implies calling build exercises
build full qualified path of template file name residing in app’s share
directory, instantiate template processor and run it
force output format ”pdf” (Template::LaTeX will be instructed by template)
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 29 / 41
25. Provided Framework Vulgar Fraction Exercise Role
package App:: Math :: Tutor :: Role ::Vul FracExercise;
use strictures; use Moo:: Role; use MooX :: Options;
with "App:: Math :: Tutor :: Role :: Exercise", "App:: Math :: Tutor :: Role :: VulFrac";
option format => (
is => "ro",
doc => "specifies format of numerator/ denominator ",
long_doc => "Allow specifying the format of the numerator/ denominator ...",
isa => sub { defined( $_[0] ) and !ref $_[0]
and $_ [0] !~ m ,^d?n+(?:/d?n+)?$, and die("Invalid format");
},
coerce => sub { ...
my ( $fmta , $fmtb ) = ( $_[0] =~ m ,^(d?n+)(?:/(d?n+))?$, );
$fmtb //= $fmta;
my $starta = "1"; my $startb = "1";
$fmta =~ s/^(d)(.*)/ $2/ and $starta = $1;
$fmtb =~ s/^(d)(.*)/ $2/ and $startb = $1;
[ $starta . "0" x length($fmta), $startb . "0" x length($fmtb) ]
},
default => sub { return [ 100, 100 ]; }, format => "s",
);
be a nice package ˆWrole, intending to provide command line options for
instantiation
compose role behavior using Exercise and VulFrac
define option coercing option string into array ensuring the array ref as value
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 30 / 41
26. Create Exercise Preamble
package App:: Math :: Tutor :: Cmd:: VulFrac ::Cmd :: Add;
use strictures;
use Moo;
use MooX :: Cmd;
use MooX :: Options;
has template_filename => ( is => "ro",
default => "twocols");
with "App:: Math :: Tutor :: Role :: VulFracExercise ";
$ mtut vulfrac add
we’re of course a nice class
satisfy requirement of Exercise role: provide template filename – two column
template (for addition and substraction)
compose role ...VulFracExercise
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 31 / 41
27. Create Exercise Create Exercise Numbers
sub _build_exercises {
my ($self) = @_;
my (@tasks );
foreach my $i ( 1 .. $self ->amount ) {
my @line;
for ( 0 .. 1 ) { # 0: +, 1: -
my ( $a , $b ) = $self -> get_vulgar_fractions (2);
push @line , [ $a , $b ];
}
push @tasks , @line;
}
my $exercises = ...;
return $exercises;
}
exercise builder has to be provided by individual exercise
hold tasks of the exercise sheet
how many tasks per sheet? (remember the option in ...Role::Exercise)
a ”+” and a ”-” exercise per line
invoke factory per task
safe each line for processing
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 32 / 41
28. Create Exercise Format Challenge / Solution
my $exercises = {
section => "Vulgar fraction addition / subtraction ",
caption => ’Fractions ’,
label => ’vulgar_fractions_addition ’,
header => [ [ ’Vulgar Fraction Addition ’, ’Vulgar Fraction Su
challenges => [], solutions => [],
};
foreach my $line (@tasks) {
my ( @solution , @challenge );
foreach my $i ( 0 .. 1 ) {
my ( $a , $b , @way ) = @{ $line ->[$i] }; my $op = $i ? ’-’ : ’+’;
$op eq ’-’ and $a < $b and ( $b , $a ) = ( $a , $b );
push @challenge , sprintf( ’$ %s %s %s = $’, $a , $op , $b );
push @way , sprintf( ’%s %s %s’, $a , $op , $b );
...
}
push( @{ $exercises ->{ challenges} }, @challenge );
push( @{ $exercises ->{ solutions} }, @solution );
}
create exercise structure containing challenges and solutions
loop over created tasks and exercises per line
format challenge using operator ’""’ of VulFrac objects
same (but different) opening ”way” and remember the little thingies . . .
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 33 / 41
29. Create Exercise Format Solutions
sub _build_exercises {
( $a , $b ) = ( $a ->_reduce , $b = $b ->_reduce );
push @way , sprintf( ’%s %s %s’, $a , $op , $b )
if ( $a ->num != $line ->[$i]->[0]->num or $b ->num != ... );
my $gcd = VulFrac ->new( num => $a ->denum , denum => $b ->denum )->_gcd;
my ( $fa , $fb ) = ( $b ->{ denum} / $gcd , $a ->{ denum} / $gcd );
push @way , sprintf( ’frac {%d cdot %d}{%d cdot %d} %s ...’,
$a ->num , $fa , $a ->denum , $fa , $op , $b ->num , $fb , ... );
push @way , sprintf( ’frac {%d}{%d} %s frac {%d}{%d}’,
$a ->num*$fa , $a ->denum*$fa , $op , $b ->num*$fb , $b ->denum*$fb );
push @way , sprintf( ’frac {%d %s %d}{%d}’,
$a ->num * $fa , $op , $b ->num * $fb , $a ->denum * $fa );
my $s = VulFrac ->new( denum => $a ->denum * $fa ,
num => $i ? $a ->num * $fa - $b ->num * $fb : ...s/ -/+/);
push @way , "" . $s; my $c = $s ->_reduce; $c ->num != $s ->num and push @way , "
$c ->num > $c ->denum and $c ->denum > 1 and push @way , $c ->_stringify(1);
push( @solution , ’$ ’ . join( " = ", @way ) . ’ $’ );
}
try to reduce operands and add them to opening when successful
World of Handcraft: show calculation method by determine
geatest common divisor of operands denominator, format subsequent steps to
reach the solution
remark possible reducing, mixed fraction, cord it and go ahead
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 34 / 41
32. Conclusion
Conclusion
lazy attributes allow designing a multi-stage initialization phase
benefit of common runtime (faster load) when using
◮ DBIx::Class
◮ Web::Simple (Jedi)
improve design by
◮ using roles for behavioral design (avoid duck typing)
◮ using explicit patterns for clear separation of concerns
◮ express intensions clearer for method overloading by using method modifiers
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 38 / 41
34. Resources Thank you
Thank You For Contributing
Graham ”haarg” Knop Found a lot of spelling errors and first suggestions
Vincent ”celogeek” Bachelier Suggested some real examples
Fedor Babkin UML diagram improvements
Curtis ”Ovid” Poe Final review and figured out missing fundamentals
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 40 / 41
35. Resources Thank you
Thank You For Listening
Questions?
Jens Rehsack <rehsack@cpan.org>
Jens Rehsack (Niederrhein.PM) Moo in practice - App::Math::Tutor 2014-2015 41 / 41