Search code examples
perlhigher-order-functionscurrying

Perl function that takes a BLOCK as the second parameter?


I want to write a function whose first parameter is a description, and the second parameter is a code block. I want the finished code to read like:

verify "description" { boolean-assertion-block };

I'm specifically looking to avoid the sub keyword.

I can put the description AFTER the code block, no problem:

sub verify (&$) { ... }

But when I reverse the prototype symbol order:

sub verify ($&) { ... }

Then I get an error message:

Type of arg 2 to main::verify must be sub {} (not anonymous hash ({})) at ...

Clearly, Perl has special handling for the first argument being a code block.

So, maybe I can make it a curried function?

sub verify ($) {
    my $message = shift;
    return sub (&) { . . . }
}

But then I get a syntax error between the description and the code block:

syntax error at ... near ""..." { "

I tried altering the calling syntax to try to help out the compiler:

test "...", { BLOCK };
test("..."){ BLOCK };
test("...")({ BLOCK });
( test "..." )({ BLOCK });

No joy. Can Perl even do what I want to do?


Solution

  • The (&) prototype only has such niceness when used for the first argument in a sub. From perldoc perlsub:

    The interesting thing about "&" is that you can generate new syntax with it, provided it's in the initial position

    One way to provide a similar level of niceness would be:

    sub verify ($%) {
      my ( $message, %opts ) = @_;
      my $coderef = $opts{using};
      ...;
    }
    
    sub using (&) {
      my ( $coderef ) = @_;
      return ( using => $coderef );
    }
    
    # The `verify` sub accepts a name followed by a hash of options:
    #
    verify(
      "your message here",
      "using" => sub { ... },
    );
    
    # The `using` sub returns a two-element list that will satisfy
    # that hash of options:
    #
    verify "your message here", using {
      ...;
    };
    

    If you desperately want to allow a syntax exactly like:

    verify "description" { boolean-assertion-block };
    

    ... then it is still possible, but requires the dark arts. Keyword::Simple is probably your best bet, but Devel::Declare and Filter::Simple are options.