Perl: Cross-references in nested datastructures? - perl

Is there a clean way to, at declaration time, make a stored hash value point to another value in the same datastructure?
For example, say I have a datastructure for command callbacks:
my %commands = (
'a' => {
'init' => sub { return "common initializer"; },
'run' => sub { return "run a"; }
},
'b' => {
'init' => sub { return "init b"; },
'run' => sub { return "run b"; }
},
'c' => {
'init' => sub { return "common initializer"; },
'run' => sub { return "run c"; }
}
);
I know this could be rewritten as:
sub common_initializer() { return "common initializer"; }
my %commands = (
'a' => {
'init' => \&common_initializer,
'run' => sub { return "run a"; }
},
'b' => {
'init' => sub { return "init b"; },
'run' => sub { return "run b"; }
},
'c' => {
'init' => \&common_initializer,
'run' => sub { return "run c"; }
}
);
This works but the subroutines are no longer all anonymous. Double-initialization is another option:
sub get_commands($;$) {
my ($_commands, $pass) = #_;
%$_commands = (
'a' => {
'init' => sub { return "common initializer"; },
'run' => sub { return "run a"; }
},
'b' => {
'init' => sub { return "init b"; },
'run' => sub { return "run b"; }
},
'c' => {
'init' => $$_commands{'a'}{'init'},
'run' => sub { return "run c"; }
}
);
get_commands($_commands, 1) unless (defined $pass);
}
my %commands;
get_commands(\%commands);
This works but it's rather kludgy and expensive. I'm using subroutines in the example above but I'd like this to work for any datatype. Is there a cleaner way to do this in Perl?

I believe that using a named subroutine might be the best option. E.g.:
sub foo { return "foo" }
my %commands ( "a" => { 'init' => \&foo } );
It is easily repeatable, contained and even allows you to add arguments dynamically.
But you can also use a lookup-table:
my %commands ( "a" => {
'init' => "foo",
'run' => "foo"
});
my %run = ( "foo" => sub { return "run foo" });
my %init = ( "foo" => sub { return "init foo" });
print "The run for 'a' is: " . $run{ $commands{a}{run} }->() . "\n";
This looks a bit more complicated to me, but it would work for any datatype, as you requested.
I see that you are using prototypes, e.g. sub foo($;$). You should be aware that these are optional, and they do not do what most people think. Most often you can skip these, and your code will be improved. Read the documentation.

Is there a clean way to, at declaration time, make a stored hash value point to another value in the same datastructure?
Impossible, by definition. You can't look up a value in the hash before you actually assign it to the hash. As such, solutions of the form my %h = ...; can't possibly work.
If you want to avoid duplication, you have two options:
my $common_val = ...;
my %h = ( a => $common_val, b => $common_val );
my %h = ( a => ..., b => undef );
$h{b} = $h{a};
(The first is best because it gives a name to the common thing.)
What I would probably do instead is use classes or objects. Inheritance and composition (e.g. roles) provide convenient means of sharing code between classes.
Classes:
my %commands = (
a => ClassA,
b => ClassB,
);
Objects:
my %commands = (
a => ClassA->new(),
b => ClassB->new(),
);
Either way, the caller would look like this:
$commands{$id}->init();
Taking it one step further, you could get rid of %commands entirely by naming the classes Command::a, Command::b, etc. Then all you'd need is
( "Command::" . $id )->init();
You're effectively using plugins at this point. There are modules that might make using a plugin even more shiny.

Note: Answering my own question but I'm still open to other solutions.
One alternate approach I've come up with is to mark cross-references with specially-formatted strings. At runtime the datastructure is traversed and any such strings are replaced with pointers to the values they name.
This is lighter-weight than the double-initialization method I mentioned in my question. It also has the advantage of keeping everything referenced in the datastructure within the datastructure (i.e. subroutines are all inline). I'm using subroutine references in the example below but this technique can be adapted for use with arbitrary datatypes (i.e. by removing the sanity check).
Here's an example:
#!/usr/bin/perl
use strict;
sub ALIAS($$#);
sub COOK(\%);
my %h = (
'a' => {
'init' => sub { return "common initializer\n"; },
'run' => sub { return "common run\n"; },
},
'b' => {
'init' => sub { return "init b\n"; },
'run' => "ALIAS {'a'}{'run'}"
},
'c' => {
ALIAS 'init' => '{a}{"init"}',
'run' => sub { return "run c\n"; },
},
);
COOK(%h);
print "Init a: " . &{$h{'a'}{'init'}}();
print "Init b: " . &{$h{'b'}{'init'}}();
print "Init c: " . &{$h{'c'}{'init'}}();
print "Run a: " . &{$h{'a'}{'run'}}();
print "Run b: " . &{$h{'b'}{'run'}}();
print "Run c: " . &{$h{'c'}{'run'}}();
# Replaces function aliases with references to the pointed to functions.
#
# Alias format is 'ALIAS {COMMAND}{FUNCTION}' where COMMAND and FUNCTION are
# the top-level and second-level keys in the passed datastructure. Both
# COMMAND and FUNCTION can optionally be quoted. See also ALIAS(...) for
# some syntatic sugar.
#
# IN: %commands -> Hash containing command descriptors (passed by reference)
sub COOK(\%) {
my ($_commands) = #_;
# Loop through commands...
foreach my $command ( keys %$_commands ) {
# Loop through functions...
foreach my $function ( keys %{$$_commands{$command}} ) {
# Only consider strings
next if ( ref $$_commands{$command}{$function} );
# Does the string look like an alias?
if ( $$_commands{$command}{$function}
=~ /^ALIAS\s+
\{
(
(?: [a-zA-Z0-9_-]+ ) |
(?:'[a-zA-Z0-9_-]+') |
(?:"[a-zA-Z0-9_-]+")
)
\}
\{
(
(?: [a-zA-Z0-9_-]+ ) |
(?:'[a-zA-Z0-9_-]+') |
(?:"[a-zA-Z0-9_-]+")
)
\}
$/x ) {
# Matched, find where it points to
my ($link_to_command, $link_to_function) = ($1, $2);
$link_to_command =~ s/['"]//g;
$link_to_function =~ s/['"]//g;
# Sanity check
unless (ref $$_commands{$link_to_command}{$link_to_function} eq 'CODE') {
die "In COOK(...), {$command}{$function} points to " .
"{$link_to_command}{$link_to_function} " .
"which is not a subroutine reference";
}
# Replace string with reference to pointed-to function
$$_commands{$command}{$function}
= $$_commands{$link_to_command}{$link_to_function};
} # END - Alias handler
} # END - Functions loop
} # END - Commands loop
} # END - COOK(...)
# Function providing syntatic sugar to let one write:
# ALIAS 'key' => "{command}{function}"
#
# instead of:
# 'key' => "ALIAS {command}{function}"
#
# This makes aliased functions more visible and makes it easier to write an
# appropriate code syntax highlighting pattern.
#
# See also COOK(...)
sub ALIAS($$#) {
my ($key, $alias, #rest) = #_;
return $key, "ALIAS $alias", #rest;
}
When run, this outputs:
Init a: common initializer
Init b: init b
Init c: common initializer
Run a: common run
Run b: common run
Run c: run c

Related

How to change if/else to hash function? perl

sub funcA{
my ($A) = #_; <--require 1
}
sub funcB{
my ($A, $B) = #_; <--require 2
}
sub funcC{
my ($A, $B, $C) = #_; <--require 3
}
sub funcRun{
my ($param) = #_;
my $other1, $other2, $other3; <--will be assigned at ',,,,,'
,,,,,
if($param eq 'a'){
funcA, $other1;
return;
}elsif($param eq = 'b'){
funcB, $other1, $other2;
return;
}elsif($param eq = 'c'){
funcC, $other1, $other2, $other3;
return;
}
}
I want to change this to next code
sub funcA{
my ($A) = #_; #<--require 1
}
sub funcB{
my ($A, $B) = #_; #<--require 2
}
sub funcC{
my ($A, $B, $C) = #_; #<--require 3
}
my $hash_ref = {
'a' => \&funcA,
'b' => \&funcB,
'c' => \&funcC
}
sub funcRun{
my ($param) = #_;
my $other1, $other2, $other3; #<--will be assigned at ',,,,,'
,,,,,
$hash_ref->{$param}(ARGUMENTS); #<--my problem
}
But I can't think how to make ARGUMENTS section including variable number of arguments.
I considered each function to be defined in the funcRun code, but then I don't know the difference with if/else code. And I heard that passing 3 arguments values and accepting parameters in order from first, is not good from someone
Updated following a clarification. The very last code segment may be exactly what is asked for.
The design where a parameter decides what argument set to use from a list generated at runtime is what is giving you trouble; it's complicated. Not knowing about the actual problem I don't know what else to offer though (other than guesses). Perhaps clarify the use of this?
Having said that, one way to complete what you want is to store a specification of what arguments go with a function along with the function name in the hashref; another would be to have a separate structure with argument sets for each parameter.
For example
use warnings;
use strict;
use feature 'say';
my $dispatch = {
'a' => { name => \&funcA, lastidx => 0 },
'b' => { name => \&funcB, lastidx => 1 },
'c' => { name => \&funcC, lastidx => 2 }
};
sub funcRun {
my ($param) = #_;
my #args = qw(one two three);
my $func = $dispatch->{$param}{name};
my $lastidx = $dispatch->{$param}{lastidx};
$func->( #args[0..$lastidx] );
}
sub funcA { say "#_" }
sub funcB { say "#_" }
sub funcC { say "#_" }
funcRun($_) for qw(a b c);
Prints
one
one two
one two three
If you really need to pick arguments positionally from a list then use an array.
However, I suggest that you clarify what this is for so that we can offer a simpler design.
Following an explanation in a comment, a property I thought was accidental may in fact help.
If the function funcA indeed takes only the first argument, funcB the first two and funcC all three (from a list built at runtime), then you can nicely pass all to all of them
$func->( #args );
sub funcA {
my ($A) = #_; # arguments other than the first are discarded
...
}
Each function takes what it needs and the rest of the arguments are discarded.
Further, if functions in any way know which of a given list of arguments to take then again you can simply pass all of them. Then they can pick their arguments either positionally
sub funcB {
my ($A, undef, $B) = #_; # if it needs first and third
...
}
or by a named key
# Work out what arguments are for which function
my $args = { for_A => ..., for_B => ..., for_C => ... };
...
$func->( $args );
sub funcA {
my ($args) = #_
my $A = $args->{for_A};
...
}
where now arguments need be stored in a hash.
Finally and best, this can all be worked out ahead of the call
my $dispatch = { a => \&funcA, b => \&funcB, c => \&funcC };
sub funcRun {
my ($param) = #_;
# Work out arguments for functions
my $args = { a => ..., b => ..., c => ... };
$dispatch->{$param}->( $args->{$param} );
}
# subs funcA, funcB, funcC are the same
funcRun($_) for qw(a b c);
what requires minimal changes to your code (just store arguments in a hash).
Here neither the functions nor the dispatch table need knowledge of the possible argument lists, what is all resolved in funcRun. This avoids entangling functions with outside code.
Your problem stems from the fact that you're passing in a selection of values from arbitrary, unrelated variables. The solution, therefore, is to put all the data you might want to pass to you subroutines in a single data structure and define a mechanism for extracting the correct data for each call. You already have a solution which uses an array for this, but I think it's slightly easier to understand in a hash.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
sub funcA{
my ($A) = #_;
say $A;
}
sub funcB{
my ($A, $B) = #_;
say "$A / $B";
}
sub funcC{
my ($A, $B, $C) = #_;
say "$A / $B / $C";
}
my $hash_ref = {
'a' => { func => \&funcA, args => [ qw[ other1 ] ] },
'b' => { func => \&funcB, args => [ qw[ other1 other2 ] ] },
'c' => { func => \&funcC, args => [ qw[ other1 other2 other3 ] ] },
};
sub funcRun{
my ($param) = #_;
my %args = (
other1 => 'foo',
other2 => 'bar',
other3 => 'baz',
);
# $hash_ref->{$param}{args} is an array reference containing
# the names of the arguments you need.
# #{ ... } turns that array reference into an array.
# #args{ ... } uses that array to look up those keys in the
# %args hash (this is called a hash slice)
$hash_ref->{$param}{func}(#args{ #{ $hash_ref->{$param}{args} } });
}
funcRun($_) for qw[a b c];
But, to be honest, having stored your data in a hash, it's only a small step to passing the whole hash into every subroutine and letting them determine which data they want to use. Or even turning your hash into an object.

How to get name of the called aliased subroutine?

How could I get know which alias was used to call aliased subroutine? caller gives the original sub-name, but I'd like to see name used on calling.
Example:
use 5.010;
sub x_y_z {
return ( caller(0) )[3];
}
*foo_bar_baz = \&x_y_z;
say x_y_z(); # x_y_z
say foo_bar_baz(); # x_y_z, but need foo_bar_baz
Edit to address XY problem
I add another example to show my deeper intentsions. I want to create dispatch-table to route some tasks:
my $dispatch = {
x => {
y => {
z => sub {
&x_y_z;
},
}
},
a => {
b => {
c => {
d => sub {
&a_b_c_d;
},
}
}
}
}
sub foo {
my #arg = ( split '_', ( split( '::', ( caller(0) )[3] ) )[1] );
return #arg;
}
*x_y_z = \&foo;
*a_b_c_d = \&foo;
As you may imagine, this tree may grow pretty big. Now many leaves in dispatch-tree needs basically same sub, they differ just how they are called (named) and I'd like to have just one sub and alias it for specific task.
What you're trying to do is simply not possible within Perl's datamodel. An alias is just an alias, not an object with its own identity.
Note that it's possible to copy a subroutine and give it a new name, for example:
use Sub::Name;
*x_y_z = subname x_y_z => \&foo;
But you will have to do this manually.
It is not a good idea to depend on subnames for anything except for stack traces. Trying to build any logic on top of these names will likely end up in a hard to debug mess, not elegant software.
It might be better to pass the route name into the handler function as an explicit parameter, and to create a helper function to abstract over the necessary plumbing. For example:
my %routes;
sub route {
my ($name, $handler) = #_;
$routes{$name} = sub { $handler->($name => #_) };
return;
}
sub common_handler { ... }
route a_b_c => \&common_handler;
route x_y_z => \&common_handler;
route foo_bar => sub {
my ($route) = #_;
say "Custom handler invoked for route $route";
};
$routes{$name}->(#args);
If absolutely necessary you can of course implement such a route function so that it installs the handlers as a named subroutine. But at that point you are building some kind of framework like Moo(se), not an ordinary Perl module.
You can't. foo_bar_baz is an alias. caller reports the name of the subroutine as declared, not the name by which it was called. Note that not all subroutines have names and not all calls are by name. (Anonymous subs exist only as a CODE reference; they don't have an entry in the symbol table. Any sub—named or not—can be called via a reference.)
That said, you don't need aliasing here. What you really want is extra parameters for the database, table, etc., on which the sub should operate. The idiomatic way to do that is to wrap the generic sub and pass that information via the wrapper:
my %dispatch = (
a => { b => { c => sub { foo('a', 'b', 'c', #_) } } },
x => { y => { z => sub { foo('x', 'y', 'z', #_) } } },
);
$dispatch{a}{b}{c}->('foo');
$dispatch{x}{y}{z}->('bar');
sub foo {
my $db = shift;
my $table = shift;
my $task = shift;
my #params = #_;
say "$db $table $task: #params";
}

Perl Tkx: How to pass a variable as a parameter to a button's callback

Given this Perl/Tkx code fragment:
#itemList = ({'attrib1' => 'name1', 'attrib2' => 'value1'},
{'attrib1' => 'name2', 'attrib2' => 'value2'});
$row = 0;
foreach $item (#itemList) {
push(#btn_list, new_ttk__button(-text => $item->{'attrib1'}, -command => sub {do_something($item->{'attrib2'});}));
$btn_list[-1]->g_grid(-column => 0, -row => $row);
$row++;
}
(In the real program #itemList is populated from a user editable config file.)
I do see two buttons labeled 'name1' and 'name2'. But when I click on either button it seems that the parameter that is passed to the callback is always $itemList[1]->{'attrib2'}; i.e. 'attrib2' of the last element of the #itemList array. What I would like is to have the first button call do_something($itemList[0]->{'attrib2'} and the second call do_something($itemList[1]->{'attrib2'}.
What am I doing wrong, please and thank you?
You have encountered a subtle feature of for loops in Perl. First the solution: use my in the for loop. Then $item will be able to create a proper closure in the anonymous sub you declare later in the loop.
for my $item (#itemlist) {
push(#btn_list, new_ttk__button(
-text => $item->{'attrib1'},
-command => sub {do_something($item->{'attrib2'});}));
$btn_list[-1]->g_grid(-column => 0, -row => $row);
$row++;
}
Further explanation: Perl implicitly localizes the subject variable of a for loop. If you don't use my in the for loop, the loop will be using a localized version of a package variable. That makes your code equivalent to:
package main;
$main::item = undef;
#itemList = ({'attrib1' => 'name1', 'attrib2' => 'value1'},
{'attrib1' => 'name2', 'attrib2' => 'value2'});
$row = 0;
foreach (#itemList) {
local $main::item = $_;
push(#btn_list, new_ttk__button(
-text => $main::item->{'attrib1'},
-command => sub {do_something($main::item->{'attrib2'});}));
$btn_list[-1]->g_grid(-column => 0, -row => $row);
$row++;
}
# at the end of the loop, value of $main::item restored to undef
Your anonymous subs still refer to the $main::item package variable, whatever value that variable holds at the time that those subroutines are invoked, which is probably undef.
Shorter solution: use strict
Additional proof-of-concept. Try to guess what the following program outputs:
#foo = ( { foo => 'abc', bar => 123 },
{ foo => 'def', bar => 456 } );
my #fn;
foreach $foo (#foo) {
push #fn, sub { "42" . $foo->{bar} . "\n" };
}
foreach my $foo (#foo) {
push #fn, sub { "19" . $foo->{foo} . "\n" };
}
print $_->() for #fn;
Here's the answer:
42
42
19abc
19def

How can I extract all global variables from a script and get each data type in Perl?

I like to capture all global variables from an external Perl script with Perl. Currently I am hanging around the type detection.
How to determine the correct data type ('', 'SCALAR', 'HASH', 'ARRAY', 'CODE')?
Parser script:
my %allVariables = ();
{
do "scriptToBeParsed.pl";
foreach my $sym ( keys %main:: ) {
# Get all normal variables and scalar/hash/array references:
if ( ref( *{"$sym"} ) =~ m/^(?:|SCALAR|HASH|ARRAY)$/ ) {
$allVariables{"$sym"} = *{"$sym"};
}
}
}
Script to be parsed:
$someVariable1 = 'Yes, I like to be captured';
$otherVariable2 = \'And I also want to be captured';
%anotherVariable3 = ( 'Capture' => 'me' );
#lameVariable4 = ( 'Capture', 'me' );
$fooVariable5 = { 'Capture' => 'me' };
$barVariable6 = [ 'Capture', 'me' ];
$subVariable7 = sub { return "Don't capture me!" };
sub dontCaptureMe { return "Don't capture me!" }
In my example ref( *{"$sym"} ) returns always 'GLOB' (of course).
Another approach would be to use the has-like access of the typeglob, which is explained in Chapter 8 of brian d foy's Mastering Perl on page 131f.
package test;
no strict;
no warnings;
$someVariable1 = 'Yes, I like to be captured';
$otherVariable2 = \'And I also want to be captured';
%anotherVariable3 = ( 'Capture' => 'me' );
#lameVariable4 = ( 'Capture', 'me' );
$fooVariable5 = { 'Capture' => 'me' };
$barVariable6 = [ 'Capture', 'me' ];
$subVariable7 = sub { return "Don't capture me!" };
sub dontCaptureMe { return "Don't capture me!" }
say $dontCaptureMe;
my %allVariables = ();
{
do "scriptToBecomeParsed.pl";
foreach my $sym ( keys %test:: ) {
for (qw( SCALAR HASH ARRAY CODE IO)) {
if (*{"$sym"}{$_}) {
$allVariables{$_}->{"$sym"} = *{"$sym"}{$_};
}
}
}
}
print Data::Dumper::Dumper \%allVariables;
This will produce the following output:
$VAR1 = {
'CODE' => {
'dontCaptureMe' => sub { "DUMMY" }
},
'ARRAY' => {
'lameVariable4' => [
'Capture',
'me'
]
},
'HASH' => {
'anotherVariable3' => {
'Capture' => 'me'
}
},
'SCALAR' => {
'someVariable1' => \'Yes, I like to be captured',
'__ANON__' => \undef,
'subVariable7' => \sub { "DUMMY" },
'dontCaptureMe' => \undef,
'otherVariable2' => \\'And I also want to be captured',
'BEGIN' => \undef,
'barVariable6' => \[
'Capture',
'me'
],
'anotherVariable3' => \undef,
'lameVariable4' => \undef,
'fooVariable5' => \{
'Capture' => 'me'
}
}
};
like you said
ref( *{"$sym"} ) returns always 'GLOB' (of course).
Because perl stores everything in the symbol table in a glob, it is impossible to tell which data type something is. This is because in perl it is perfectly valid to have an array, scalar, hash or whatever else with the same name... because of this, perl stores everything in globs to avoid collisions. What you could do is loop through all of the symbols in the symbol table and test each glob against all the possible things that it could be (the set isn't too large) and see which ones are set.
Alternatively, a more practical approach might be to just load the perl script as text and parse for $, %, #, sub, open (filehandle) to see what type everything is.

How do I use an array as an object attribute in Perl?

I need some help regarding the arrays in Perl
This is the constructor I have.
BuildPacket.pm
sub new {
my $class = shift;
my $Packet = {
_PacketName => shift,
_Platform => shift,
_Version => shift,
_IncludePath => [#_],
};
bless $Packet, $class;
return $Packet;
}
sub SetPacketName {
my ( $Packet, $PacketName ) = #_;
$Packet->{_PacketName} = $PacketName if defined($PacketName);
return $Packet->{_PacketName};
}
sub SetIncludePath {
my ( $Packet, #IncludePath ) = #_;
$Packet->{_IncludePath} = \#IncludePath;
}
sub GetPacketName {
my( $Packet ) = #_;
return $Packet->{_PacketName};
}
sub GetIncludePath {
my( $Packet ) = #_;
#{ $Packet->{_IncludePath} };
}
(The code has been modified according to the suggestions from 'gbacon', thank you)
I am pushing the relative paths into 'includeobjects' array in a dynamic way. The includepaths are being read from an xml file and are pushed into this array.
# PacketInput.pm
if($element eq 'Include')
{
while( my( $key, $value ) = each( %attrs ))
{
if($key eq 'Path')
push(#includeobjects, $value);
}
}
So, the includeobject will be this way:
#includeobjects = (
"./input/myMockPacketName",
"./input/myPacket/my3/*.txt",
"./input/myPacket/in.html",
);
I am using this line for set include path
$newPacket->SetIncludePath(#includeobjects);
Also in PacketInput.pm, I have
sub CreateStringPath
{
my $packet = shift;
print "printing packet in CreateStringPath".$packet."\n";
my $append = "";
my #arr = #{$packet->GetIncludePath()};
foreach my $inc (#arr)
{
$append = $append + $inc;
print "print append :".$append."\n";
}
}
I have many packets, so I am looping through each packet
# PacketCreation.pl
my #packets = PacketInput::GetPackets();
foreach my $packet (PacketInput::GetPackets())
{
print "printing packet in loop packet".$packet."\n";
PacketInput::CreateStringPath($packet);
$packet->CreateTar($platform, $input);
$packet->GetValidateOutputFile($platform);
}
The get and set methods work fine for PacketName. But since IncludePath is an array, I could not get it to work, I mean the relative paths are not being printed.
If you enable the strict pragma, the code doesn't even compile:
Global symbol "#_IncludePath" requires explicit package name at Packet.pm line 15.
Global symbol "#_IncludePath" requires explicit package name at Packet.pm line 29.
Global symbol "#_IncludePath" requires explicit package name at Packet.pm line 30.
Global symbol "#_IncludePath" requires explicit package name at Packet.pm line 40.
Don't use # unquoted in your keys because it will confuse the parser. I recommend removing them entirely to avoid confusing human readers of your code.
You seem to want to pull all the attribute values from the arguments to the constructor, so continue peeling off the scalar values with shift, and then everything left must be the include path.
I assume that the components of the include path will be simple scalars and not references; if the latter is the case, then you'll want to make deep copies for safety.
sub new {
my $class = shift;
my $Packet = {
_PacketName => shift,
_Platform => shift,
_Version => shift,
_IncludePath => [ #_ ],
};
bless $Packet, $class;
}
Note that there's no need to store the blessed object in a temporary variable and then immediately return it because of the semantics of Perl subs:
If no return is found and if the last statement is an expression, its value is returned.
The methods below will also make use of this feature.
Given the constructor above, GetIncludePath becomes
sub GetIncludePath {
my( $Packet ) = #_;
my #path = #{ $Packet->{_IncludePath} };
wantarray ? #path : \#path;
}
There are a couple of things going on here. First, note that we're careful to return a copy of the include path rather than a direct reference to the internal array. This way, the user can modify the value returned from GetIncludePath without having to worry about mucking up the packet's state.
The wantarray operator allows a sub to determine the context of its call and respond accordingly. In list context, GetIncludePath will return the list of values in the array. Otherwise, it returns a reference to a copy of the array. This way, client code can call it either as in
foreach my $path (#{ $packet->GetIncludePath }) { ... }
or
foreach my $path ($packet->GetIncludePath) { ... }
SetIncludePath is then
sub SetIncludePath {
my ( $Packet, #IncludePath ) = #_;
$Packet->{_IncludePath} = \#IncludePath;
}
Note that you could have used similar code in the constructor rather than removing one parameter at a time with shift.
You might use the class defined above as in
#! /usr/bin/perl
use strict;
use warnings;
use Packet;
sub print_packet {
my($p) = #_;
print $p->GetPacketName, "\n",
map(" - [$_]\n", $p->GetIncludePath),
"\n";
}
my $p = Packet->new("MyName", "platform", "v1.0", qw/ foo bar baz /);
print_packet $p;
my #includeobjects = (
"./input/myMockPacketName",
"./input/myPacket/my3/*.txt",
"./input/myPacket/in.html",
);
$p->SetIncludePath(#includeobjects);
print_packet $p;
print "In scalar context:\n";
foreach my $path (#{ $p->GetIncludePath }) {
print $path, "\n";
}
Output:
MyName
- [foo]
- [bar]
- [baz]
MyName
- [./input/myMockPacketName]
- [./input/myPacket/my3/*.txt]
- [./input/myPacket/in.html]
In scalar context:
./input/myMockPacketName
./input/myPacket/my3/*.txt
./input/myPacket/in.html
Another way to reduce typing is to use Moose.
package Packet;
use Moose::Policy 'Moose::Policy::JavaAccessors';
use Moose;
has 'PacketName' => (
is => 'rw',
isa => 'Str',
required => 1,
);
has 'Platform' => (
is => 'rw',
isa => 'Str',
required => 1,
);
has 'Version' => (
is => 'rw',
isa => 'Int',
required => 1,
);
has 'IncludePath' => (
is => 'ro',
isa => 'ArrayRef[Str]',
default => sub {[]},
traits => [ 'Array' ],
handles => {
getIncludePath => 'elements',
getIncludePathMember => 'get',
setIncludePathMember => 'set',
},
);
__PACKAGE__->meta->make_immutable;
no Moose;
1;
Check out Moose::Manual::Unsweetened for another example of how Moose saves time.
If you are adamant in your desire to learn classical Perl OOP, read the following perldoc articles: perlboot, perltoot, perlfreftut and perldsc.
A great book about classical Perl OO is Damian Conway's Object Oriented Perl. It will give you a sense of the possibilities in Perl's object.
Once you understand #gbacon's answer, you can save some typing by using Class::Accessor::Fast:
#!/usr/bin/perl
package My::Class;
use strict; use warnings;
use base 'Class::Accessor::Fast';
__PACKAGE__->follow_best_practice;
__PACKAGE__->mk_accessors( qw(
IncludePath
PacketName
Platform
Version
));
use overload '""' => 'to_string';
sub to_string {
my $self = shift;
sprintf(
"%s [ %s:%s ]: %s",
$self->get_PacketName,
$self->get_Platform,
$self->get_Version,
join(':', #{ $self->get_IncludePath })
);
}
my $obj = My::Class->new({
PacketName => 'dummy', Platform => 'Linux'
});
$obj->set_IncludePath([ qw( /home/include /opt/include )]);
$obj->set_Version( '1.05b' );
print "$obj\n";