I'm writing a perl subroutine and I would like to have the flexibility to either pass in values as a hash, or just as single values. I would like to know how the arguments are passed to the subroutine, so that I can handle the cases separately. For example:
#case 1, pass in hash
test(arg1 => 'test', arg2 => 'test2');
#case 2, just pass in single values
test('test', 'test2');
sub test {
#if values passed in as a hash, handle one way
if(...) {
}
#if values passed in as single values, do something else
else {
}
}
Is there a way to detect this in perl? Thanks!
What I would do using an anonymous HASH reference :
#case 1, pass in hash
test({arg1 => 'test', arg2 => 'test2'});
#case 2, just pass in single values
test('test', 'test2');
sub test {
my $arg = shift;
if(ref $arg eq 'HASH') {
...;
}
#if values passed in as single values, do something else
else {
...;
}
}
See
http://perldoc.perl.org/perlref.html
http://perldoc.perl.org/perlreftut.html
The other answer is perfectly fine (and I've plusplussed it), but in the spirit of There's More That One Way To Do Itâ„¢, and in the interest of pimping my own wares...
use v5.14;
use strict;
use warnings;
use Kavorka qw( multi fun );
# define a function with positional arguments
multi fun test (Str $arg1, Str $arg2) {
say "positional";
say "\$arg1 is $arg1";
say "\$arg2 is $arg2";
}
# define a function with named arguments
multi fun test (Str :$arg1, Str :$arg2) {
say "named";
say "\$arg1 is $arg1";
say "\$arg2 is $arg2";
}
# Call the function with positional arguments
test('foo', 'bar');
# Call the function with named arguments
test(arg1 => 'foo', arg2 => 'bar');
# Call the function with named arguments again
test({ arg1 => 'foo', arg2 => 'bar' });
Related
How can I create a subroutine that can parse arguments like this:
&mySub(arg1 => 'value1', arg2 => 'value2' ...);
sub mySub() {
# what do I need to do here to parse these arguments?
# no arguments are required
}
Simply assign the input array to a hash:
sub my_sub {
my %args = #_;
# Work with the %args hash, e.g.
print "arg1: ", $args{arg1};
}
If you want to provide default values, you can use:
sub my_sub {
my %args = ( 'arg1' => 'default arg1',
'arg2' => 'default arg2',
#_ );
# Work with the (possibly default) values in %args
}
Maybe you'll also find very useful the Method::Signatures module, which will allow you to do something like that:
func MySub (Str :$arg1 = 'default arg1', Str :$arg2 = 'default arg2') {
print "arg1: ", $arg1};
}
I one source code i saw the next construction:
our %hash;
BEGIN {
%hash = (
KEY1 =>
sub { return $_[0]->somefunc( 1, $_[2]->attr1, $_[2]->attr2 ); },
KEY2 =>
sub { return $_[0]->somefunc( 0, $_[2]->attr1, $_[2]->attr2 ); },
...
);
}
What are those $_[0] (they are the first arg of anonymous sub) - but here are in the BEGIN block... so, what is its value at "compilation" phase?
The $hash{KEY1} get a subroutine reference, but to what subroutine?
EDIT
Now (i hope) understand. Just dumped the content of the %hash with Data::Dumper::Concise and got the next:
...
KEY1 => sub {
package MyPkg;
use warnings;
use strict;
return $_[0]->somefunc(1, $_[2]->attr1, $_[2]->attr2);
},
KEY2 => sub {
package MyPkg;
use warnings;
use strict;
return $_[0]->somefunc(0, $_[2]->attr1, $_[2]->attr2);
},
...
So, the construction returns a reference to anonymous sub, what when will be executed returns the result of execution of $_[0]->somefunc with the supplied args.
sub {} creates an anonymous subroutine and returns a reference to it (just as [] and {} do with arrays and hashes).
The $_[0], etc., are the arguments to that sub.
So if you call $hash{KEY1}->('foo','bar','baz'), $_[0] will be 'foo'.
The fact that the anonymous sub was generated at compile time isn't relevant.
I'm doing some template programming in RT (http://bestpractical.com/rt), and it uses Perl. Unfortunately, I've only dallied with Perl very occasionally.
I'm trying to call a sub procedure that starts off with:
sub PrepareEmailUsingTemplate {
my %args = (
Template => '',
Arguments => {},
#_
);
Since this is a part of the lib, I don't get to change it.
The call I'm making to it is:
my ($template, $msg) = RT::Interface::Email->PrepareEmailUsingTemplate(
Template => 'CCReplyFirstMessage' );
return (0, $msg) unless $template;
And I'm getting "Odd number of elements in hash assignment at /opt/rt4/sbin/../lib/RT/Interface/Email.pm line 552. (/opt/rt4/sbin/../lib/RT/Interface/Email.pm:552), with is the first line of the sub.
I know I'm doing something whacky in passing the parameter. How should I be passing it?
PrepareEmailUsingTemplate is not a class method, it is a simple function. You want to call it like this:
my ($template, $msg) = RT::Interface::Email::PrepareEmailUsingTemplate(
Template => 'CCReplyFirstMessage' );
return (0, $msg) unless $template;
When you call it with the ->, your #_ will end up with three values: your two for the hash and the class name at the beginning. The result of calling it as a class method will be something like this:
my %args = (
Template => '',
Arguments => {},
'RT::Interface::Email::PrepareEmailUsingTemplate',
Template => 'CCReplyFirstMessage'
);
And that's where your "odd number of elements in hash assignment" error comes from.
Try:
my ($template, $msg) = RT::Interface::Email::PrepareEmailUsingTemplate(Template => 'CCReplyFirstMessage');
The function isn't written to be called with ->.
If you are going to call the sub as a class method, you need to expect the additional implicit class argument:
my $class = shift;
my %args = ( ..., #_ );
I have a perl script (simplified) like so:
my $dh = Stats::Datahandler->new(); ### homebrew module
my %url_map = (
'/(article|blog)/' => \$dh->articleDataHandler,
'/video/' => \$dh->nullDataHandler,
);
Essentially, I'm going to loop through %url_map, and if the current URL matches a key, I want to call the function pointed to by the value of that key:
foreach my $key (keys %url_map) {
if ($url =~ m{$key}) {
$url_map{$key}($url, $visits, $idsite);
$mapped = 1;
last;
}
}
But I'm getting the message:
Can't use string ("/article/") as a subroutine ref while "strict refs" in use at ./test.pl line 236.
Line 236 happens to be the line $url_map{$key}($url, $visits, $idsite);.
I've done similar things in the past, but I'm usually doing it without parameters to the function, and without using a module.
Since this is being answered here despite being a dup, I may as well post the right answer:
What you need to do is store a code reference as the values in your hash. To get a code reference to a method, you can use the UNIVERSAL::can method of all objects. However, this is not enough as the method needs to be passed an invocant. So it is clearest to skip ->can and just write it this way:
my %url_map = (
'/(article|blog)/' => sub {$dh->articleDataHandler(#_)},
'/video/' => sub {$dh->nullDataHandler(#_)},
);
This technique will store code references in the hash that when called with arguments, will in turn call the appropriate methods with those arguments.
This answer omits an important consideration, and that is making sure that caller works correctly in the methods. If you need this, please see the question I linked to above:
How to take code reference to constructor?
You're overthinking the problem. Figure out the string between the two forward slashes, then look up the method name (not reference) in a hash. You can use a scalar variable as a method name in Perl; the value becomes the method you actually call:
%url_map = (
'foo' => 'foo_method',
);
my( $type ) = $url =~ m|\A/(.*?)/|;
my $method = $url_map{$type} or die '...';
$dh->$method( #args );
Try to get rid of any loops where most of the iterations are useless to you. :)
my previous answer, which I don't like even though it's closer to the problem
You can get a reference to a method on a particular object with can (unless you've implemented it yourself to do otherwise):
my $dh = Stats::Datahandler->new(); ### homebrew module
my %url_map = (
'/(article|blog)/' => $dh->can( 'articleDataHandler' ),
'/video/' => $dh->can( 'nullDataHandler' ),
);
The way you have calls the method and takes a reference to the result. That's not what you want for deferred action.
Now, once you have that, you call it as a normal subroutine dereference, not a method call. It already knows its object:
BEGIN {
package Foo;
sub new { bless {}, $_[0] }
sub cat { print "cat is $_[0]!\n"; }
sub dog { print "dog is $_[0]!\n"; }
}
my $foo = Foo->new;
my %hash = (
'cat' => $foo->can( 'cat' ),
'dog' => $foo->can( 'dog' ),
);
my #tries = qw( cat dog catbird dogberg dogberry );
foreach my $try ( #tries ) {
print "Trying $try\n";
foreach my $key ( keys %hash ) {
print "\tTrying $key\n";
if ($try =~ m{$key}) {
$hash{$key}->($try);
last;
}
}
}
The best way to handle this is to wrap your method calls in an anonymous subroutine, which you can invoke later. You can also use the qr operator to store proper regexes to avoid the awkwardness of interpolating patterns into things. For example,
my #url_map = (
{ regex => qr{/(article|blog)/},
method => sub { $dh->articleDataHandler }
},
{ regex => qr{/video/},
method => sub { $dh->nullDataHandler }
}
);
Then run through it like this:
foreach my $map( #url_map ) {
if ( $url =~ $map->{regex} ) {
$map->{method}->();
$mapped = 1;
last;
}
}
This approach uses an array of hashes rather than a flat hash, so each regex can be associated with an anonymous sub ref that contains the code to execute. The ->() syntax dereferences the sub ref and invokes it. You can also pass parameters to the sub ref and they'll be visible in #_ within the sub's block. You can use this to invoke the method with parameters if you want.
How do I pass a function, a, to function, b, and have b call a in Perl?
Here's a complete, working script that demonstrates what you're asking.
sub a { print "Hello World!\n"; }
sub b {
my $func = $_[0];
$func->();
}
b(\&a);
Here's an explanation: you take a reference to function a by saying \&a. At that point you have a function reference; while normally a function would be called by saying func() you call a function reference by saying $func->()
The -> syntax deal with other references as well. For example, here's an example of dealing with array and hash references:
sub f {
my ($aref, $href) = #_;
print "Here's an array reference: $aref->[0]\n"; # prints 5
print "Here's a hash ref: $href->{hello}\n"; # prints "world"
}
my #a = (5, 6, 7);
my %h = (hello=>"world", goodbye=>"girl");
f(\#a, \%h);
You can't pass a function to another function directly. Instead, you pass a reference to a function. To call the function you dereference it (as a CODE ref) using ->();
sub a { print #_ }
sub b {
my $f = shift; # assuming that $f is a function reference...
$f->(#_); # call it with the remaining arguments
}
b(\&a, "hello, world!"); # prints "hello, world!"
Perl doesn't have pass-by-name semantics but you can emulate them using a hash. The method for calling the function is the same. You dereference it.
sub a { print #_ }
sub b {
my %arg = #_;
$arg{function}->(#{$arg{arguments}});
}
b(function => \&a, arguments => ["hello, world!"]);
ObPerl6: Perl 6 will have pass-by-name semantics.
You can access subroutines references as \&my_method in Perl, and call those references with $myref->();. Try this:
perl -e'sub a { print "foo in a"; }; sub b { shift->(); }; b(\&a);'
Good luck!
Following up to Eli Courtwright's example: If you only use the first function once, you could also call b with an anonymous function, like this:
b( sub { print "Hello World\n"; } );