5

I want to create a subroutine like grep {} @ or map {} @ that can handle code and/or boolean input. Somehow the internet doesn't have much info on this.

I tried to create the sub below, but it can't even handle the first test. I get the error Can't locate object method "BoolTest" via package "input" (perhaps you forgot to load "input"?) at C:\path\to\file.pl line 16..

How does this think it's an object? Am I not creating BoolTest correctly?

# Example senarios
BoolTest { 'input' =~ /test[ ]string/xi };
BoolTest { $_ =~ /test[ ]string/xi } @array;
BoolTest(TRUE);

# Example subroutine
sub BoolTest
{
   if ( ref($_[0]) == 'CODE') {
       my $code = \&{shift @_}; # ensure we have something like CODE
       if ($code->()) { say 'TRUE'; } else { say 'FALSE'; }
   } else {
       if ($_[0]) { say 'TRUE'; } else { say 'FALSE'; }
   }
}
Eric Fossum
  • 2,395
  • 4
  • 26
  • 50
  • 2
    `my $code = \&{shift @_};` is a complicated way of doing `my $code = shift;` (The reference (`\ `) and the dereference (`&{...}`) cancel out.) – ikegami Jan 29 '15 at 20:02
  • @ikegami According to this answer http://stackoverflow.com/a/6101734/127465, it is not entirely futile to use `\&{ ... }` as it ensures that the thing in braces can actually be coerced to a CODE ref. – ThomasH Feb 24 '17 at 20:35
  • 1
    @ThomasH, That's an awful way of doing that, and it's completely unnecessary. It's an awful way of checking if a something can be a sub ref because it can actually create an undefined sub in the process! And it's useless since the `$code->()` on the next line will also die in the same circumstances as `\&{ shift }` would. – ikegami Feb 24 '17 at 21:34

1 Answers1

6

To pass a code reference, you can use the following:

sub BoolTest { ... }

BoolTest sub { 'input' =~ /test[ ]string/xi };
BoolTest sub { $_ =~ /test[ ]string/xi }, @array;
BoolTest(TRUE);

You could have the sub have a similar syntax to map BLOCK LIST, by using the &@ prototype.

sub BoolTest(&@) { ... }

BoolTest { 'input' =~ /test[ ]string/xi };
BoolTest { $_ =~ /test[ ]string/xi } @array;

This creates the same anonymous subs are earlier, so return, last, etc will behave the same as in the first snippet.

Note that the prototyped version won't accept

BoolTest(TRUE);

unless you override the prototype

&BoolTest(TRUE);

But you shouldn't expect your caller to do that. Based on your example, you could have them use the following, but a second sub might be better.

BoolTest { TRUE };
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Can I do `BoolTest {}` without the prototypes? I heard protos are bad practice. – Eric Fossum Jan 29 '15 at 19:25
  • 3
    It's a bad practice to use prototypes *to validate arguments* because prototypes change parsing rules. In this case, you actually want to change the parsing rules, so all's good. – ikegami Jan 29 '15 at 19:25
  • 2
    This is one of the cases when you really should use prototypes. – mob Jan 29 '15 at 19:26
  • Now I get `Use of uninitialized value $_` for the `{ $_ == 1 }` example. Isn't the code suppose to go in unexecuted? – Eric Fossum Jan 29 '15 at 19:36
  • 3
    Your code never sets `$_`. Presumably you want to set it to each of the remaining values of `@_` (which can be done using `for (@_) { $code->() }`). – ikegami Jan 29 '15 at 19:59