Tuesday, 15 February 2011

perl - Subroutines that take an optional block parameter -



perl - Subroutines that take an optional block parameter -

caveats associated prototypes accepted , notwithstandingc, can 2 below contrived subs exist within same package, i.e. provide optional block parameter sort does?

sub myprint { (@_) { print "$_\n"; } } sub myprint (&@) { $block = shift; (@_) { print $block->() . "\n"; } }

the intent provide similar calling convention sort, e.g. allow execution of:

my @x = qw(foo bar baz); print_list @x; # foo # bar # baz

...and:

my @y = ( {a=>'foo'}, {a=>'bar'}, {a=>'baz'} ); print_list { $_->{a} } @y; # foo # bar # baz

i redefine and/or prototype mismatch warnings if seek (which reasonable).

i suppose can do:

sub myprint { $block = undef; $block = shift if @_ && ref($_[0]) eq 'code'; (@_) { print (defined($block) ? $block->() : $_) . "\n"; } }

...but &@ prototype provides syntactic sugar; removing requires:

my @y = ( {a=>'foo'}, {a=>'bar'}, {a=>'baz'} ); print_list sub { $_->{a} }, @y; # note sub , comma

(i've tried ;&@, no avail -- still yields type of arg 1 main::myprint must block or sub {} (not private array).)

yes.

unfortunately it's bit of pain. need utilize keyword api introduced in perl 5.14. means need implement (and custom parsing it) in c , link perl xs.

fortunately doy wrote great wrapper perl keyword api, allowing implement keywords in pure perl. no c, no xs! it's called parse::keyword.

unfortunately has major bugs dealing closed on variables.

fortunately can worked around using padwalker.

anyway, here's example:

use v5.14; begin { bundle my::print; utilize exporter::shiny qw( myprint ); utilize parse::keyword { myprint => \&_parse_myprint }; utilize padwalker; # here's actual implementation of myprint function. # when caller includes block, first # parameter. when don't, we'll pass explicit undef # in first parameter, create sure it's nice , # unambiguous. helps distinguish between these 2 # cases: # # myprint { block } @list_of_coderefs; # myprint @list_of_coderefs; # sub myprint { $block = shift; defined($block) ? map($block->($_), @_) : @_; } # function handle custom parsing # myprint. # sub _parse_myprint { # there might whitespace after myprint # keyword, read , discard that. # lex_read_space; # variable undef if there no # block, we'll set coderef in if there # block. # $block = undef; # if next character opening brace... # if (lex_peek eq '{') { # ... inquire parse::keyword parse block. # (this includes parsing opening , closing # braces.) parse_block homecoming coderef, # need prepare (see later). # $block = _fixup(parse_block); # closing brace may followed whitespace. # lex_read_space; } # after optional block, there list # of things. parse that. parse_listexpr returns # coderef, when called homecoming # actual list. again, needs prepare up. # $listexpr = _fixup(parse_listexpr); # stuff need homecoming # parse::keyword. # homecoming ( # of above stuff happens @ compile-time! # next coderef gets called @ run-time, # , gets called in list context. whatever stuff # returns passed real # `myprint` function @_. # sub { $block, $listexpr->() }, # false value signal parse::keyword # myprint expression, not # total statement. if total statement, # wouldn't need semicolon @ end. (just # don't need semicolon after `foreach` # block.) # !!0, ); } # workaround big bug in parse::keyword! # coderefs returns bound lexical # variables @ compile-time. however, need access # variables @ run-time. # sub _fixup { # coderef generated parse::keyword. # $coderef = shift; # find out variables closed over. if didn't # close on variables, it's fine is, # , don't need prepare it. # $closed_over = padwalker::closed_over($coderef); homecoming $coderef unless keys %$closed_over; # otherwise need homecoming new coderef # grabs caller's lexical variables @ run-time, # pumps them original coderef, , # calls original coderef. # homecoming sub { $caller_pad = padwalker::peek_my(2); %vars = map +($_ => $caller_pad->{$_}), keys %$closed_over; padwalker::set_closed_over($coderef, \%vars); goto $coderef; }; } }; utilize my::print qw( myprint ); $start = "["; $end = "]"; myprint "a", "b", "c"; myprint { $start . $_ . $end } "a", "b", "c";

this generates next output:

a b c [a] [b] [c]

perl subroutine-prototypes

No comments:

Post a Comment