CGI/Perl Guide | Learning Center | Forums | Advertise | Login
Site Search: in

  Main Index MAIN
INDEX
Search Posts SEARCH
POSTS
Who's Online WHO'S
ONLINE
Log in LOG
IN

Home: Perl Programming Help: Intermediate:
Passing a sub reference to a sub

 



PapaGeek
User

Apr 11, 2014, 5:48 AM

Post #1 of 10 (6034 views)
Passing a sub reference to a sub Can't Post

Is this the proper syntax for passing a subroutine reference to another subroutine and then executing the referenced subroutine while passing it a parameter?

In the real world, I have a sub that calculates the ROI’s for various stock symbols (3 months, 6 months, 1 year, etc.). In the first case I am calculating this data for a long list of stock symbols and after each calculation I want to update the progress bar on the window that called the sub. In the second case I am calculating the same ROI’s for a single stock symbol and I want to display each ROI as they are calculated.

The first case might take several minutes to complete, the second case will be done in less than a minute.



Code
use Modern::Perl '2013'; 

main();
exit;

sub main
{
tester(\&sub1);
tester(\&sub2);
}

sub tester
{
my ($sub) = @_;
say "call the referenced sub with a parameter";
&$sub("Did you hear me?");
}

sub sub1
{
my ($param) = @_;
say "Hello World! $param";
}

sub sub2
{
my ($param) = @_;
say "Hello Universe! $param";
}


Output is:


Code
call the referenced sub with a parameter 
Hello World! Did you hear me?
call the referenced sub with a parameter
Hello Universe! Did you hear me?



FishMonger
Veteran / Moderator

Apr 11, 2014, 6:47 AM

Post #2 of 10 (6030 views)
Re: [PapaGeek] Passing a sub reference to a sub [In reply to] Can't Post

Yes, that is the correct way to pass a subroutine reference . But don't use the '&' when executing subroutines unless you know about and want/need its side affects.

So, instead of:

Code
&$sub("Did you hear me?");


use:

Code
$sub->("Did you hear me?");



PapaGeek
User

Apr 11, 2014, 7:25 AM

Post #3 of 10 (6015 views)
Re: [FishMonger] Passing a sub reference to a sub [In reply to] Can't Post

Thanks for the reply, both alternative calls work the same as far as the output is concerned. But as usual, a good answer to a newbie question always raises more questions!

You said “unless I know about and want/need its side effects”. So my next Perl lesson is just that!

What are the side effects of the original calling syntax and why does the alternative calling syntax eliminate those side effects?


FishMonger
Veteran / Moderator

Apr 11, 2014, 8:01 AM

Post #4 of 10 (5999 views)
Re: [PapaGeek] Passing a sub reference to a sub [In reply to] Can't Post

The & bypasses (turns off) prototype checking and exposes the caller's (parent) @_ array to the child subroutine being called which can cause subtle intermittent bugs which are difficult to track down and debug.

There are times where it's appropriate to use & when calling a sub, but those cases are somewhat rare. In my 15 years of perl coding, I've never come across an occasion where I needed to use & when calling a sub.


Laurent_R
Veteran / Moderator

Apr 11, 2014, 10:01 AM

Post #5 of 10 (5957 views)
Re: [PapaGeek] Passing a sub reference to a sub [In reply to] Can't Post

Another way, which might be useful at times is to construct directly a subroutine reference.

Code
 

my $sub1 = sub
{
my ($param) = @_;
say "Hello World! $param";
}

my $sub2 = sub
{
my ($param) = @_;
say "Hello Universe! $param";
}

sub main
{
tester($sub1);
tester($sub2);
}

sub tester
{
my ($sub) = @_;
say "call the referenced sub with a parameter";
$sub->("Did you hear me?");
}


or even to pass directly the code as an argument to the function (here on only removed sub1):

Code
sub main  
{
tester(sub {say "Hello World! $_[0]"; });
tester($sub2);
}



(This post was edited by Laurent_R on Apr 12, 2014, 7:33 AM)


Laurent_R
Veteran / Moderator

Apr 12, 2014, 8:15 AM

Post #6 of 10 (5641 views)
Re: [FishMonger] Passing a sub reference to a sub [In reply to] Can't Post


In Reply To
The & bypasses (turns off) prototype checking and exposes the caller's (parent) @_ array to the child subroutine being called which can cause subtle intermittent bugs which are difficult to track down and debug.


I thought, when I read this yesterday, that this was probably wrong in the case of code refs, but I did not have time to check until now.

Consider this code:

Code
use strict; 
use warnings;

sub my_say ($) { print shift, "\n"}
my_say "foobar";

Thanks to the prototype of the my_day subroutine, I do not need parens around the argument passed to it and this happily prints "foobar".

Consider now the same with two arguments (from now on, for the sake of brevity, I will no longer repeat in this post the "use strict;" and "use warnings;" pragmas, but I am still using them in all the examples below):

Code
sub my_say ($) { print shift, "\n"} 
my_say ("foo", "bar");

I get the following compile time error:

Code
Too many arguments for main::my_say at -e line 6, near ""bar")" 
-e had compilation errors.

If I do now the same thing with the "&" subroutine calling syntax:

Code
sub my_say ($) { print shift, "\n"} 
&my_say ("foo", "bar");

I no longer get a compilation error and this prints "foo".
This shows that what you are describing is right for a regular function call.

Let's see now what happens with a code ref call.

The first example above again, with a coderef:

Code
sub my_say ($) { print shift, "\n"} 
my $code_ref = \&my_say;
$code_ref->("foobar");

This print "foobar".
The same now with two arguments:

Code
sub my_say ($) { print shift, "\n"} 
my $code_ref = \&my_say;
$code_ref->("foo", "bar");

This print happily "foo", with no error message. The mere fact of using a coderef seems to disable prototype checking.
And the same happens with the & syntax:

Code
sub my_say ($) { print shift, "\n"} 
my $code_ref = \&my_say;
&$code_ref("foo", "bar");

which prints "foo".
So there seems to be no difference between the

Code
$code_ref->("foo", "bar");

and the

Code
&$code_ref("foo", "bar");

syntaxes. Prototype checking is not active in both cases.

I think that what happens is that the compiler cannot see prototype violation with code refs, because the subroutine call is not seen at compile time, but is really seen by Perl at a later point, i.e. at run time.


(This post was edited by Laurent_R on Apr 12, 2014, 8:19 AM)


FishMonger
Veteran / Moderator

Apr 12, 2014, 9:05 AM

Post #7 of 10 (5631 views)
Re: [Laurent_R] Passing a sub reference to a sub [In reply to] Can't Post

That's interesting. I didn't know that code refs would also bypass prototype checks.

That seems to be one more reason not to rely on prototypes to restrict subroutine input parameters. If that requirement is needed, then it should be done in C which could be inlined by the use of the Inline::C module.
http://search.cpan.org/~sisyphus/Inline-0.54/C/C.pod


(This post was edited by FishMonger on Apr 12, 2014, 9:06 AM)


Laurent_R
Veteran / Moderator

Apr 12, 2014, 11:07 AM

Post #8 of 10 (5610 views)
Re: [FishMonger] Passing a sub reference to a sub [In reply to] Can't Post

I absolutely agree and I very rarely use prototypes for the same reason.

The only type of case where I find prototypes useful is the following situation. Assume the map function did not exist and that I wanted to implement it in pure Perl. I could try something like this to print even numbers between 2 and 10:

Code
print join " ", my_map (sub{$_ * 2}, 1..5); 

sub my_map {
my $code_ref = shift;
my @d ;
push @d, $code_ref->($_) for @_;
return @d;
}

This works, but the calling syntax is a bit ugly with the parens and "sub" keyword. With prototypes I can obtain the calling syntax of the map internal function:

Code
sub my_map (&@){ 
my $code_ref = shift;
my @d ;
push @d, $code_ref->($_) for @_;
return @d;
}
my @array = 1..5;
print join " ", my_map {$_ * 2} @array;

The my_map calling syntax is now very clean.

This is the only type of cases where I find prototypes really useful.


FishMonger
Veteran / Moderator

Apr 12, 2014, 11:47 AM

Post #9 of 10 (5602 views)
Re: [Laurent_R] Passing a sub reference to a sub [In reply to] Can't Post

Yes, overriding perl built-in functions is one of the few valid reasons for using prototypes.

However, it also means that in most of those cases you need to predeclare the subs near the top, which is not within the style of coding I want to use. I'm not saying it bad style, it's just not my choice of style. I prefer to declare/define subs at the end.

So far, I've never come across a situation where I needed or wanted to override the built-ins. But if I did, then I'd (in order of preference) 1) try to find a cpan module that already does what I need or 2) I'd look into using Inline:C to write the function or 3) write my own module (using XS code). My problem with #2 and #3 is that I'd first need to learn C. Using perl prototypes would be very last resort.

BTW, we seem to be moving off the original topic.

PapaGeek, for more info on this topic, take a look at this:
"Far More Than Everything You've Ever Wanted to Know about Prototypes in Perl"
http://www.perlmonks.org/?node_id=861966


(This post was edited by FishMonger on Apr 12, 2014, 11:52 AM)


Laurent_R
Veteran / Moderator

Apr 12, 2014, 3:09 PM

Post #10 of 10 (5550 views)
Re: [FishMonger] Passing a sub reference to a sub [In reply to] Can't Post

Hi FishMonger,
Well, the my_map function was just a toy example, there is no reason to re-implement an existing internal operator. Unless perhaps you want to implement a specific behavior, such as, say, a map version that does not alter the source array when the $_ is modified within the code block, or possibly a "lazy map" operator that calculates values only insofar they are needed, or some other yet undefined behavior... I used that example just because the behavior and calling syntax of map are quite well-known and they did not need much explanation.

I also don't like to predeclare subs near the top of the program in Perl. But this is usually not a problem for me, because if I ever wanted to use the (above or modified) my_map function, I would usually put it in a module so that its definition would occur when the "use this_module qw /.../;" statement is reached, i.e. usually before any exetutable Perl statement is run.

But you are right, we are getting slightly off-topic. I'll stop this discussion here.

 
 


Search for (options) Powered by Gossamer Forum v.1.2.0

Web Applications & Managed Hosting Powered by Gossamer Threads
Visit our Mailing List Archives