Why won't this die? - perl

I've created a package that starts a simple HTTP server for testing purposes, but the stop() method doesn't seem to want to stop the fork()'ed process. Killing the process (via SIGHUP) works fine outside of the object, but calling $server->stop just doesn't work. Why?
package MockHub;
use Moose;
use HTTP::Server::Brick;
use JSON;
use Log::Any qw($log);
use English qw(-no_match_vars);
has 'server' => (
'is' => 'ro',
'lazy' => 1,
'isa' => 'HTTP::Server::Brick',
'builder' => '_build_server',
'init_arg' => undef
);
has 'port' => ( 'is' => 'ro', 'isa' => 'Int' );
has 'pid' => ( 'is' => 'rw', 'isa' => 'Int', 'init_arg' => undef );
has 'token' => ( 'is' => 'rw', 'isa' => 'Str', 'init_arg' => undef );
has 'log' => ( 'is' => 'ro', 'isa' => 'Log::Any::Proxy', 'default' => sub { Log::Any->get_logger() } );
sub start {
my $self = shift;
my $pid = fork;
# Spawn the server in a child process.
if (!defined $pid) {
die qq{Can't fork: $!};
}
elsif ($pid == 0) { # child
$self->server->start;
exit; # exit after server exits
}
else { # parent
$self->pid($pid);
return $pid;
}
}
sub _build_server {
my ($self) = #_;
my $port = $self->port;
my $pid = $self->pid || 'NO PID';
my $server = HTTP::Server::Brick->new( port => $port );
$server->mount(
'/foo' => {
'handler' => sub {
my ( $req, $res ) = #_;
my $token = substr( $req->{'path_info'}, 1 ); # remove leading slash
$self->token($token);
$res->header( 'Content-Type' => 'application/json' );
$res->add_content( encode_json( { 'success' => 1, 'message' => 'Process Report Received' } ) );
1;
},
'wildcard' => 1,
},
);
$server->mount(
'/token' => {
'handler' => sub {
my ( $req, $res ) = #_;
my $token = $self->token || '';
$res->header( 'Content-Type' => 'text/plain' );
$res->add_content($token);
1;
},
},
);
return $server;
}
sub stop {
my ($self) = #_;
my $pid = $self->pid || die q{No PID};
if (kill 0, $pid) {
sleep 1;
kill 'HUP', $pid;
if (kill 0, $pid) {
warn q{Server will not die!};
}
}
else {
warn q{Server not running};
}
}
__PACKAGE__->meta->make_immutable;

Although it's not running, the process still exists until its parent reaps it reaped by wait(2). Since the child is never reaped (and since there's no permission issue), kill 0, $pid will always succeed. Fixed:
sub stop {
my ($self) = #_;
my $pid = $self->pid
or die("No child to stop.\n");
kill(TERM => $pid);
or die("Can't kill child.\n");
if (!eval {{
local $SIG{ALRM} = sub { die "timeout\n" };
alarm(15);
waitpid($pid, 0) > 0
or die("Can't reap child.\n");
return 1; # No exception
}}) {
die($#) if $# ne "timeout\n";
warn("Forcing child to end.\n");
kill(KILL => $pid)
or die("Can't kill child.\n");
waitpid($pid, 0) > 0
or die("Can't reap child.\n");
}
$self->pid(0);
}

Your stop subroutine does not appear to stop anything. It sends kill 0 (which merely detects when the process is running), or a HUP. Don't you want to send SIGTERM or similar?
Also, what are you trying to achieve with $self->$pid? When you do the fork(), then the memory space of parent and child are separated, so what you write into $self->pid in the parent will not be visible to the child. You thus need to record the PID of the child in the child, e.g.
$self->pid = $$;
before
$self->server->start;
I'm a little unsure exactly which process you are trying to kill here, and which process is calling stop(). I'm presuming these aren't exactly the same or you'd surely just have quit from there rather than muck around with kill etc.

Related

Perl: Recursive object instantiation with Moose

In the example code below, I am defining a class Person that can have child objects of the same class.
When I invoke the printTree method, I am expecting the following output
Sam Ram Geeta
What I see instead is
SamRamRamRamRamRamRamRamRamRamRamR.....
Any hints on what I am doing wrong and how to achieve my goal?
package Person;
use Moose;
has name => ( is => 'ro' );
my #kids;
sub addChild {
my ( $self, $name ) = #_;
my $k = Person->new( name => $name );
push #kids, $k;
return $k;
}
sub printTree {
my $self = shift;
print $self->name;
$_->printTree foreach ( #kids );
}
no Moose;
package main;
my $s = Person->new( name => "Sam" );
my $r = $s->addChild( "Ram" );
my $g = $s->addChild( "Geeta" );
$s->printTree;
The issue is that #Person::kids does not belong to any one instance, and you effectively end up with
#Person::kids = ($r, $g);
$s->printTree() loops through #Person::kids, calls
$r->printTree() loops through #Person::kids, calls
$r->printTree() loops through #Person::kids, calls
$r->printTree() loops through #Person::kids, calls
...
You need to make it an attribute, e.g.
has kids => (
isa => 'ArrayRef[Person]',
traits => ['Array'],
handles => {
all_kids => 'elements',
push_kids => 'push',
},
default => sub { [] },
);
sub addChild {
my ($self, $name) = #_;
my $k = Person->new(name => $name);
$self->push_kids($k);
return $k;
}
sub printTree {
my ($self) = #_;
print $self->name;
$_->printTree foreach $self->all_kids;
}
You can check perldoc Moose::Meta::Attribute::Native::Trait::Array for other useful handles from the Array trait.

Providing sugar *and* object methods in a Moose role

I'm working on a Moose Role that allows the consuming class to emit XML based on an 'xml_path' option specified to one or more attributes, like so:
package MooseX::Role::EmitsXML::Trait::HasXMLPath;
use Moose::Role;
has xml_path => (
'is' => 'ro',
'isa' => 'Str',
'predicate' => 'has_xml_path',
);
has 'namespace' => (
'is' => 'ro',
'isa' => 'Str',
'predicate' => 'has_namespace',
);
has 'cdata' => (
'is' => 'ro',
'isa' => 'Bool',
'predicate' => 'has_cdata',
);
package MooseX::Role::EmitsXML;
our $VERSION = '0.01';
use Moose::Role;
use namespace::autoclean;
use XML::LibXML;
use Moose::Exporter;
sub has_xml {
my ($meta, $attr_name, %opts) = #_;
$opts{'traits'} ||= [];
push #{$opts{'traits'}}, 'MooseX::Role::EmitsXML::Trait::HasXMLPath';
$meta->add_attribute($attr_name, %opts);
}
Moose::Exporter->setup_import_methods(
with_meta => [qw(has_xml)],
also => [qw/Moose/],
);
sub to_xml {
my ( $self, #args ) = #_;
my $doc = XML::LibXML::Document->new();
for my $attr ( map { $self->meta->get_attribute($_) } $self->meta->get_attribute_list ) {
my $reader = $attr->get_read_method;
if ( $attr->does('XMLPath') && $attr->has_xml_path ) {
my $val = $self->$reader();
my $path = $attr->xml_path;
my #elements = split /\//, $path;
if ( $path =~ /^\// ) { # Throw away blank
shift #elements;
}
my $previous;
while ( my $element = shift #elements ) {
my $node;
my $attrs = extract_attrs($element);
( my $node_name = $element ) =~ s/\[.+?\]//g;
if ( !$previous ) {
if ( !$doc->documentElement ) {
$doc->setDocumentElement( XML::LibXML::Element->new($node_name) );
for my $key ( keys %{$attrs} ) {
$doc->documentElement->setAttribute( $key, $attrs->{$key} );
}
}
else {
my $root1 = $doc->documentElement->nodeName;
my $root2 = $element;
if ( $root1 ne $root2 ) {
die qq{MISMATCH! Root elements do not match: "$root1" <> "$root2"};
}
}
$node = $doc->documentElement;
}
else {
($node) = #{ $previous->find(qq{./$element}) };
if ( !$node ) {
$node = XML::LibXML::Element->new($node_name);
for my $key ( keys %{$attrs} ) {
$node->setAttribute( $key, $attrs->{$key} );
}
$previous->addChild($node);
}
}
$previous = $node;
}
# $previous has become the leaf here
$previous->appendText($val);
}
}
}
sub _extract_attrs {
my $element = shift;
my #attr_strings = ($element =~ m/(\[.+?\])/); # Capture everything between [ and ].
if (scalar #attr_strings > 1) {
die q{Invalid attribute specification. Specify multiple attrs as [#attr1=val1,#attr2=val2]};
}
my %attrs;
if (#attr_strings) {
for my $string (split /,/, $attr_strings[0]) {
my ($key, $val) = ($string =~ m/\[#?\s*(\w+)\s*=\s*"(\w+)"\s*\]/);
if (!$key) {
die qq{Malformed attribute specification: "$string". Should be [key="value"] (DOUBLE QUOTES MANDATORY)\n};
}
if (exists $attrs{$key}) {
warn qq{Duplicate key "$key" in attrs};
}
$attrs{$key} = $val;
}
}
return \%attrs;
}
no Moose::Role;
1;
However, when I try to use it:
package Product;
use Moose;
use MooseX::Role::EmitsXML;
# If I comment this out, has_xml works right ($meta is passed as first argument) but I don't have to_xml() available in the
# consuming class.
#
# If I don't, I have to_xml available in the consuming class, but has_xml doesn't work right.
with 'MooseX::Role::EmitsXML';
has_xml 'description' =>
( 'is' => 'ro', 'isa' => 'Str', 'xml_path' => '/Product/DescriptiveInformation/ProductDescription' );
has_xml 'item_number' => ( 'is' => 'ro', 'isa' => 'Str', 'xml_path' => '/Product/Identifiers/ItemNumber' );
has_xml 'catalog_number' => ( 'is' => 'ro', 'isa' => 'Str', 'xml_path' => '/Product/Identifiers/CatalogNumber' );
has_xml 'upc' => ( 'is' => 'ro', 'isa' => 'Int', 'xml_path' => '/Product/Identifiers/UPC' );
has_xml 'color' => ( 'is' => 'ro', 'isa' => 'Str', 'xml_path' => '/Product/DescriptiveInformation/Color' );
has 'that_je_ne_sais_quoi' => ('is' => 'ro', 'isa' => 'Str' );
1;
package main;
use Test::Most;
use XML::LibXML;
my %product_args = (
color => 'periwinkle',
upc => 1234567890123,
item_number => 'THX-1138',
catalog_number => 'KP-1652051819',
description => q{Oh, yes. It's very nice!},
that_je_ne_sais_quoi => q{Something French. Or maybe Swahili.},
);
ok my $p = Product->new(%product_args), 'Created instance of class using role';
ok my $xml = $p->to_xml, 'Output XML';
ok my $doc = XML::LibXML::parse_string($xml), 'XML is valid (or at least parseable)';
for my $key (keys %product_args) {
my $attr = $p->meta->get_attribute($key);
if ($key ne 'that_je_ne_sais_quoi') {
ok $attr->can('has_xml_path'), qq{Predicate 'has_xml_path' present for "$key"};
ok my $path = $attr->xml_path, qq{Got an XML path for "$key"};
1;
}
}
As the comments say, if I comment out with 'MooseX::Role::EmitsXML', then has_xml gets the consuming package's metaclass as the first argument, but the consuming package doesn't have to_xml. If I uncomment it, the consuming package gets to_xml, but has_xml doesn't get the consuming package metaclass. How can I get both to_xml and the has_xml sugar?
Per ether, this is Not How It's Done. Instead, the Role providing with_xml needs to be defined in a separate package, and the "ultimate" Role should apply the aforementioned to the consuming class, like so:
package MooseX::Role::EmitsXML::Trait::HasXMLPath;
use Moose::Role;
has xml_path => (
'is' => 'ro',
'isa' => 'Str',
'predicate' => 'has_xml_path',
);
has 'namespace' => (
'is' => 'ro',
'isa' => 'Str',
'predicate' => 'has_namespace',
);
has 'cdata' => (
'is' => 'ro',
'isa' => 'Bool',
'predicate' => 'has_cdata',
);
package MooseX::Role::EmitsXML::ToXML;
# This package provides the to_xml() method to the consuming class
our $VERSION = '0.01';
use Moose::Role;
use namespace::autoclean;
use XML::LibXML;
sub to_xml {
my ( $self, #args ) = #_;
my $doc = XML::LibXML::Document->new();
for my $attr ( map { $self->meta->get_attribute($_) } $self->meta->get_attribute_list ) {
my $reader = $attr->get_read_method;
if ( $attr->does('MooseX::Role::EmitsXML::Trait::HasXMLPath') && $attr->has_xml_path ) {
my $val = $self->$reader();
my $path = $attr->xml_path;
my #elements = split /\//, $path;
if ( $path =~ /^\// ) { # Throw away blank
shift #elements;
}
my $previous;
while ( my $element = shift #elements ) {
my $node;
my $attrs = extract_attrs($element);
( my $node_name = $element ) =~ s/\[.+?\]//g;
if ( !$previous ) {
if ( !$doc->documentElement ) {
$doc->setDocumentElement( XML::LibXML::Element->new($node_name) );
for my $key ( keys %{$attrs} ) {
$doc->documentElement->setAttribute( $key, $attrs->{$key} );
}
}
else {
my $root1 = $doc->documentElement->nodeName;
my $root2 = $element;
if ( $root1 ne $root2 ) {
die qq{MISMATCH! Root elements do not match: "$root1" <> "$root2"};
}
}
$node = $doc->documentElement;
}
else {
($node) = #{ $previous->find(qq{./$element}) };
if ( !$node ) {
$node = XML::LibXML::Element->new($node_name);
for my $key ( keys %{$attrs} ) {
$node->setAttribute( $key, $attrs->{$key} );
}
$previous->addChild($node);
}
}
$previous = $node;
}
# $previous has become the leaf here
$previous->appendText($val);
}
}
return "$doc";
}
sub _extract_attrs {
my $element = shift;
my #attr_strings = ($element =~ m/(\[.+?\])/); # Capture everything between [ and ].
if (scalar #attr_strings > 1) {
die q{Invalid attribute specification. Specify multiple attrs as [#attr1=val1,#attr2=val2]};
}
my %attrs;
if (#attr_strings) {
for my $string (split /,/, $attr_strings[0]) {
my ($key, $val) = ($string =~ m/\[#?\s*(\w+)\s*=\s*"(\w+)"\s*\]/);
if (!$key) {
die qq{Malformed attribute specification: "$string". Should be [key="value"] (DOUBLE QUOTES MANDATORY)\n};
}
if (exists $attrs{$key}) {
warn qq{Duplicate key "$key" in attrs};
}
$attrs{$key} = $val;
}
}
return \%attrs;
}
no Moose::Role;
1;
package MooseX::Role::EmitsXML;
# This package applies the role providing to_xml to the consuming class,
# and creates the 'has_xml' sugar
use Moose::Exporter;
sub has_xml {
my ($meta, $attr_name, %opts) = #_;
$opts{'traits'} ||= [];
push #{$opts{'traits'}}, 'MooseX::Role::EmitsXML::Trait::HasXMLPath';
$meta->add_attribute($attr_name, %opts);
}
Moose::Exporter->setup_import_methods(
with_meta => [qw(has_xml)],
base_class_roles => [qw(MooseX::Role::EmitsXML::ToXML)],
);

Moose trigger caller

Is there any way of knowing the trigger caller attribute in Moose ?
For example, taking the example from Moose::Manual::Attributes:
has 'size' => (
is => 'rw',
trigger => \&_size_set,
);
sub _size_set {
my ( $self, $size, $old_size ) = #_;
my $msg = $self->name;
if ( #_ > 2 ) {
$msg .= " - old size was $old_size";
}
$msg .= " - size is now $size";
warn $msg;
}
Is it possible in _set_size to know that the attribute size called it, without needing to specify the name of the caller attribute explicitly?
EDIT: updated per comment.
It might be simpler to create a wrapper that adds one argument:
sub make_trigger {
my ($name, $sub) = #_;
return sub {
my $self = shift;
$self->$sub($name, #_);
};
}
has 'size' => (
is => 'rw',
trigger => make_trigger(size => \&_size_set),
);
sub _size_set {
my ( $self, $name, $size, $old_size ) = #_;
...
}
Here's what #RsrchBoy refers to as the "proper way"...
use v5.14;
use strict;
use warnings;
BEGIN {
package MooseX::WhatTheTrig::Trait::Attribute
{
use Moose::Role;
use Scope::Guard qw(guard);
after _process_trigger_option => sub
{
my $class = shift;
my ($name, $opts) = #_;
return unless exists $opts->{trigger};
my $orig = delete $opts->{trigger};
$opts->{trigger} = sub
{
my $self = shift;
my $guard = guard {
$self->meta->_set_triggered_attribute(undef);
};
$self->meta->_set_triggered_attribute($name);
$self->$orig(#_);
};
}
}
package MooseX::WhatTheTrig::Trait::Class
{
use Moose::Role;
has triggered_attribute => (
is => 'ro',
writer => '_set_triggered_attribute',
);
}
}
package Example
{
use Moose -traits => ['MooseX::WhatTheTrig::Trait::Class'];
has [qw(foo bar)] => (
traits => ['MooseX::WhatTheTrig::Trait::Attribute'],
is => 'rw',
trigger => sub {
my ($self, $new, $old) = #_;
$_ //= 'undef' for $old, $new;
my $attr = $self->meta->triggered_attribute;
say "Changed $attr for $self from $old to $new!";
}
);
}
my $obj = Example->new(foo => 1, bar => 2);
$obj->foo(3);
$obj->bar(4);
You'll notice that the "foo" and "bar" attributes share a trigger, but that the trigger is able to differentiate between the two attributes.
Moose::Exporter has some sugar for making this a little less ugly. I might have a play at turning this into a CPAN module some time.
The proper way to do this would be to employ an attribute trait of some sort; one that passes the name, or (preferably) the metaclass instance of the attribute the trigger belongs to. One could even create a trait that allows the class' metaclass to be asked if we're in an attribute trigger, and if so, which one. (This would be transparent and not break anyone's expectations as to how trigger works.)
The easiest would be to curry your triggers as shown in another example.

Perl HTTP server

I'm new at Perl, and I have a question regarding HTTP servers and client APIs.
I want to write an HTTP server which accepts requests from HTTP clients. The problem is that I do not know how to do it because I'm a Java developer, and it's a little bit difficult for me. Please can you give me some tutorials and example for HTTP::Daemon module for Perl?
I spent a lot of time trying to make a "simple" usable web server by many users simultaneously. The documentation for HTTP::Daemon and other online resources isn't helping me.
Here is a working (Ubuntu 12.10 with default Perl package v5.14.2) example preforked web server with different content type pages and error pages:
#!/usr/bin/perl
use strict;
use warnings;
use CGI qw/ :standard /;
use Data::Dumper;
use HTTP::Daemon;
use HTTP::Response;
use HTTP::Status;
use POSIX qw/ WNOHANG /;
use constant HOSTNAME => qx{hostname};
my %O = (
'listen-host' => '127.0.0.1',
'listen-port' => 8080,
'listen-clients' => 30,
'listen-max-req-per-child' => 100,
);
my $d = HTTP::Daemon->new(
LocalAddr => $O{'listen-host'},
LocalPort => $O{'listen-port'},
Reuse => 1,
) or die "Can't start http listener at $O{'listen-host'}:$O{'listen-port'}";
print "Started HTTP listener at " . $d->url . "\n";
my %chld;
if ($O{'listen-clients'}) {
$SIG{CHLD} = sub {
# checkout finished children
while ((my $kid = waitpid(-1, WNOHANG)) > 0) {
delete $chld{$kid};
}
};
}
while (1) {
if ($O{'listen-clients'}) {
# prefork all at once
for (scalar(keys %chld) .. $O{'listen-clients'} - 1 ) {
my $pid = fork;
if (!defined $pid) { # error
die "Can't fork for http child $_: $!";
}
if ($pid) { # parent
$chld{$pid} = 1;
}
else { # child
$_ = 'DEFAULT' for #SIG{qw/ INT TERM CHLD /};
http_child($d);
exit;
}
}
sleep 1;
}
else {
http_child($d);
}
}
sub http_child {
my $d = shift;
my $i;
my $css = <<CSS;
form { display: inline; }
CSS
while (++$i < $O{'listen-max-req-per-child'}) {
my $c = $d->accept or last;
my $r = $c->get_request(1) or last;
$c->autoflush(1);
print sprintf("[%s] %s %s\n", $c->peerhost, $r->method, $r->uri->as_string);
my %FORM = $r->uri->query_form();
if ($r->uri->path eq '/') {
_http_response($c, { content_type => 'text/html' },
start_html(
-title => HOSTNAME,
-encoding => 'utf-8',
-style => { -code => $css },
),
p('Here are all input parameters:'),
pre(Data::Dumper->Dump([\%FORM],['FORM'])),
(map { p(a({ href => $_->[0] }, $_->[1])) }
['/', 'Home'],
['/ping', 'Ping the simple text/plain content'],
['/error', 'Sample error page'],
['/other', 'Sample not found page'],
),
end_html(),
)
}
elsif ($r->uri->path eq '/ping') {
_http_response($c, { content_type => 'text/plain' }, 1);
}
elsif ($r->uri->path eq '/error') {
my $error = 'AAAAAAAAA! My server error!';
_http_error($c, RC_INTERNAL_SERVER_ERROR, $error);
die $error;
}
else {
_http_error($c, RC_NOT_FOUND);
}
$c->close();
undef $c;
}
}
sub _http_error {
my ($c, $code, $msg) = #_;
$c->send_error($code, $msg);
}
sub _http_response {
my $c = shift;
my $options = shift;
$c->send_response(
HTTP::Response->new(
RC_OK,
undef,
[
'Content-Type' => $options->{content_type},
'Cache-Control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0',
'Pragma' => 'no-cache',
'Expires' => 'Thu, 01 Dec 1994 16:00:00 GMT',
],
join("\n", #_),
)
);
}
There is a very fine example in the documentation for HTTP::Daemon.
A client example compliant with the synopsys from HTTP::Daemon :
require LWP::UserAgent;
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
$ua->env_proxy;
my $response = $ua->get('http://localhost:52798/xyzzy');
if ($response->is_success) {
print $response->decoded_content; # or whatever
}
else {
die $response->status_line;
}
You just need to adapt the port and maybe the host.

How should I define a Moose object subroutine after its initialization?

How should I define a Moose object subroutine after its initialization?
I'm writing an object module using Moose and I plan to serialize (nstore) the created objects.
Examine the following (simplified!) example:
package MyObj 0.001;
use Moose;
use namespace::autoclean;
has 'size' => (
is => 'ro',
isa => 'Int',
required => 1,
);
sub some_sub {
my ($self, #more) = #_;
if ($self->size() < 100) # do something;
elsif (($self->size() < 500)) # do something else;
elsif (($self->size() < 7500)) # do something else;
# ...
}
1;
some_sub acts differently depending on size. Since size is read-only, it remains constant after the object has been initialized.
So, assuming I call some_sub zillion times, it's a pity that I have to go through all the ifs each time.
I'd better do this once after the object has been initialized, then set some_sub to be a simpler function with noifs at all.
But... how can I do that?
UPDATE
Perhaps I should add a lazy attribute of type subref that will hold a reference to the chosen subroutine. some_sub will then simply call $self->chosen_sub->(#_). What do you think?
has calculation_method => (is => 'ro', lazy_build => 1, init_arg => undef);
sub _build_calculation_method {
my $self = shift;
return '_calculate_small' if $self->size < 100;
return '_calculate_medium' if $self->size < 500;
return '_calculate_large' if $self->size < 7500;
return '_calculate_enormous';
}
sub _calculate_small { ... }
sub _calculate_medium { ... }
# etc.
sub calculate {
my $self = shift;
my $method = $self->calculation_method;
return $self->$method(#_);
}
As a bonus, calculation_method is now serializable too.
Perhaps another case for MooseX::SingletonMethod! (Sorry I'm reading your questions in reverse order!).
For eg:
use 5.012;
use warnings;
package MyObj 0.001;
use MooseX::SingletonMethod;
use namespace::autoclean;
has 'size' => (
is => 'ro',
isa => 'Int',
required => 1,
);
sub _which_sub {
my ($self) = #_;
if ($self->size < 100) { return sub{ 'A' } }
elsif ($self->size < 500) { return sub{ 'B' } }
elsif ($self->size < 7500) { return sub{ 'C' } }
return sub { 'D' };
}
package main;
my $obj = MyObj->new( size => 200 );
$obj->add_singleton_method( some_sub => $obj->_which_sub );
say $obj->some_sub; # => B
And it should be possible to add this single method creation from inside your class. Have a look at this blog post for some guidance: Moose Singleton Method: Now without roles!. And also a hotchpotch of posts here
Regarding your update:
use 5.012;
use warnings;
package MyObj;
use Moose;
use namespace::autoclean;
has 'size' => (
is => 'ro',
isa => 'Int',
required => 1,
);
has 'chosen_sub' => (
is => 'ro',
isa => 'CodeRef',
lazy => 1,
builder => '_build_chosen_sub',
init_arg => undef, # unless want option of providing anon sub at construction?
);
sub _build_chosen_sub {
my ($self) = #_;
if ($self->size < 100) { return sub{ 'A' } }
elsif ($self->size < 500) { return sub{ 'B' } }
elsif ($self->size < 7500) { return sub{ 'C' } }
return sub { 'D' };
}
package main;
my $obj = MyObj->new( size => 200 );
say $obj->chosen_sub->(); # => B