How can I create a Perl subroutine that accepts more than one block?
Asked Answered
N

5

11

With prototypes, you can create a subroutine that accepts a block of code as its first parameter:

sub example (&) {
   my $code_ref = shift;
   $code_ref->();
}
example { print "Hello\n" };

How can I do the same thing, but with more than one block of code? I want to use blocks of codes, not variables or sub { ... }.

This does not work:

sub example2 (&&) {
   my $code_ref = shift;
   my $code_ref2 = shift;
   $code_ref->();
   $code_ref2->();
}
example2 { print "One\n" } { print "Hello\n" };

It gives this error:

Not enough arguments for main::example2
Nanette answered 20/11, 2014 at 16:44 Comment(13)
You don't actually need prototypes do use code references, you can just pass them as named or unnamed subs.Larva
What you call "blocks of code" is exactly what sub { ... } is, only you don't type the sub keywordLarva
@TLP: you mean like this? example2 sub { ... }, sub { ...}; I realise that, I just want to do without the sub keyword or variables. I'm guessing this can't be done.Nanette
That would be my guess as well. The prototypes are not easy to use, and the general advice is: Don't use them. I would hazard a guess that they are not well maintained, and/or developed, as most Perl programmers consider them a dead end and don't use them.Larva
Is there any reason why you can't (or don't want to) use a dispatch table?Schilt
the usual way around this is to use a second subroutine to launder (so to speak) the second code ref, like sub try(&@){ ...do stuff with two code refs... } sub catch(&){$_[0]}, usable like try { ... } catch { ... }; (don't forget the semicolon). Having the second sub's name there to label what that block of code does is IMO an advantage over just foo { ... } { ... } (which doesn't work). If you can see @WumpusQ.Wumbley 's deleted answer, this is exactly what he suggests. Err, as is true of @Borodin's answer, which I somehow totally missed.Agone
@ysth: I think there is no "usual". It looks like you're copying from Try::Tiny? That is as it should be: there is no place for subroutine prototypes in any mainstream Perl code.Tierney
@ysth: I have no problem with you posting a solution that touches on, or completely overlaps my own. As long as the principles are correct, additional angles on the same solution can only help people searching for an answer to a similar problem. Unfortunately the chase for reputation points is counter-productive to the vision of Stack Exchange as a library of solutions. Please take this as permission to copy and refine any answers that I have posted.Tierney
as prototypes go, (&) and (&@) are pretty harmless.Agone
@ysth: The harm is in the disguise that such prototypes provide. I would hate to be faced with the task of refactoring code that used a mechanism like that.Tierney
@Flimm: Many of us have ask why, and you have been offered several solutions that may or may not work. Don't you think you owe us an answer to the why?Tierney
Two hours on, and despite several questions asking you why, you still haven't offered an explanation for your question. I can't support you any longer, and have changed my +1 to a -1. I hope you find a solution to your problem elsewhere.Tierney
@Borodin: The answer to why is mainly just curiosity. It seems to me odd that you can specify the prototype (&&), but you can only pass a block of code without the sub keyword once, and I wondered if I was missing something, the documentation wasn't helpful.Nanette
T
9

I hope you realise that this is just code seasoning, and all you are achieving is a tidier syntax at the expense of clarity?

Perl won't allow you to pass more than one bare block to a subroutine, but the second actual parameter could be a call to a subroutine that also takes a single block and simply returns the code reference.

This program demonstrates. Note that I have chosen please and also as names for the subroutines. But you must use something that is both appropriate to your own code's functionality and very unlikely to clash with forthcoming extensions to the core language.

use strict;
use warnings;

sub please(&$) {
  my ($code1, $code2) = @_;
  $code1->();
  $code2->();
}

sub also(&) {
  $_[0];
}

please { print "aaa\n" } also { print "bbb\n" };

output

aaa
bbb
Tierney answered 20/11, 2014 at 17:16 Comment(3)
Accepted because the answer is: "Perl won't allow you to pass more than one bare block to a subroutine". The also thing is clever, but it behaves exactly as sub in this case, so I might as well use subNanette
But why is that so? It seems Perl treats the next block as hash reference for reasons unknown to me.Accost
Could it be that a block in Perl isn't a data type (as opposed to a sub)? Consider this: $x = { print "foobar\n" } - It executes the code and creates a hash reference 1 => undef.Accost
G
2

works for me...

sub example2  {
   my $code_ref = shift;
   my $code_ref2 = shift;
   $code_ref->();
   $code_ref2->();
}
example2 ( sub { print "One\n" }, sub { print "Hello\n" });

Just for the purposes of TMTOWTDI here is a method that works in somewhat the way that the OP requested

First, make a source filter

package poop;

use Filter::Util::Call;

sub import {
    my ($type) = @_;
    my ($ref)  = [];
    filter_add( bless $ref );
}

sub filter {
    my ($self) = @_;
    my ($status);
if (( $status = filter_read() ) > 0) { 
        if(!/sub/ && /example2/) {
            s/\{/sub \{/g;
            }
        }

    $status;
}
1;

Second the filter must be used

use poop;

sub example2  {
   my $code_ref = shift;
   my $code_ref2 = shift;
   $code_ref->();
   $code_ref2->();
}
example2 ( { print "One\n" }, { print "Hello\n" });

ps. this is horrific and noone would wish to see this actually in production

Gamache answered 20/11, 2014 at 16:56 Comment(0)
S
1

This works for me:

     example { print "One\n" } sub { print "Hello\n" };
Sporophore answered 20/11, 2014 at 17:3 Comment(7)
Did you try it and see if it works? It does not for me.Larva
I updated my answer... Maybe you could tell us why it's important to pass blocks instead of anonymous subs. It's Ruby, isn't it?Sporophore
The question is not about Ruby - it's about Perl (indicated in the title and tags).Ethno
@LenJaffe: Ideas like this -- although, I admit, not this particular one -- form the core of the concept of programming languages. The OP has a job that must be completed in their left hand, and an entity that reliably follows rules in their right. What should you do for them?Tierney
Walk him and pitch to the rhino.Sporophore
@LenJaffe: I asked the question because I wondered if there was an easy way that I was just missing. I'm all for laziness.Nanette
Actually this is not an answer: The first part is wrong (and should be deleted), and the second is no solution either, because it uses sub (which is known to work). See question.Accost
A
1

You could just eval the blocks

use experimental 'signatures';
use feature 'say'; 

sub doublethecode ($block1, $block2) { eval { $block1; }; eval { $block2;} } ;
doublethecode \&{ print "OH " }, \&{ say for qw/HAI YOU LOLCAT :-P/  }; 

output:

OH, HAI
YOU
LOLCAT
:-P

Then just ignore the \& and you're there.

As of v5.20 You can use signatures and :prototype attributes together. The signatures feature gives a more "common" syntax and a minimalist sort of argument checking. Maybe one day a future perl will have a "builtin" (optional, highly flexible) type system of some kind to go with it, but prototypes aren't anything like that right now. So something like:

sub doublethecode :prototype(\&\&) ($cr1, $cr2) { $cr1 ; $cr2 ; }

is not what it seems. Since prototypes, signatures and signatures with :prototype(&&) may not be giving you what think you're getting, this might be good enough:

sub doublethecode { eval { shift };  eval{ shift } } ;
doublethecode &{ print "perl" }, &{ print "6" }

output:

perl6

To make sure perl doesn't think {} is a hash, the & is necessary. But actually ... what is wrong with using anonymous subroutines sub { } here?

Albrecht answered 20/11, 2014 at 19:0 Comment(5)
What do you think sub doublethecode ($block1, $block2) does?Tierney
@borodin oops I think we were editing at the same time sorry ... I think sub doublethecode ($block1, $block2) { ... } is just a new way of assigning @_ , using it with attributes :prototype(&&) doesn't seem to make this exercise any better :-)Albrecht
Please look at the documentation for the experimental pragma. It is equivalent to no warnings 'experimental'; use feature 'signatures';. And even then your code will die with messages like Unknown warnings category 'experimental' or Feature "signatures" is not supported unless you have a preceding use 5.020. Yes, to run your solution you need to be running the very latest release of Perl which came out only in May this year. There is riding the cutting edge, and there is flaunting all warnings about new facilities. You know where you are.Tierney
@Borodin, Point well taken - I'm familiar with what the pragma does, I should have pointed it out, my bad. I think using sub {} is a pretty clear and well-used perl idiom. In the ever more serious ECMA/Javascript world anonymous functions (which are much more wordy) are all the rage. In perl sub{...}, sub{...} is a practically self-documenting feature and we have it. The OP should probably explain why they are not using it.Albrecht
I assume you mean "the ever more serious ECMAScript / JavaScript world"? JavaScript now has the position that FORTRAN had a few decades ago, and C after it: there are endless shortcomings, but the popularity card trumps everything.Tierney
C
1

Fwiw, in Perl 6:

sub doublecode (&foo, &bar) { foo(), bar() }
doublecode { print 1 }, { print 2 } # prints 12

But see my partial list of caveats about Perl 6.

Carson answered 27/11, 2014 at 3:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.