Search code examples
perlperl-context

Is there a way to force void context in Perl?


I'm wondering this only out of geek curiosity on the way Perl works and how far you can go with things like this.

There are some functions that are written to act differently on each one of the three contexts.

With the following code as a very simple example:

use 5.012;

say context();
say scalar context();

sub context {
    if (wantarray) {
        return 'list';
    } elsif (defined wantarray) {
        return 'scalar';
    } else {
        return 'void'; # Destined to be discarded
    }
}

OUTPUT:

list
scalar

Can you think of a way to provoke a third say that outputs void after context() is called?

I understand this is quite a contradiction, as void context would probably mean you are not really returning/assigning anything. But as I understand from what I've read on the way Perl works, it's not about nothing being returned, but the return value being discarded after being executed in void context.

So, I'm wondering: Is there a way to force void context in the same way you can force list or scalar context, when you happen to actually be on list or scalar context at the moment of the call to a function?


Solution

  • sub void(&) { $_[0]->(); () }
    
    say        context();
    say scalar context();
    say void { context() };
    

    More advanced code can give us better syntax:

    use syntax qw( void );
    
    say        context();
    say scalar context();
    say void   context();
    

    On a side note, the following shows that scalar is not so much a function as a compile-time directive:

    $ diff -u0 \
       <( perl -MO=Concise,-exec -Msyntax=void -E'say        f()' 2>&1 ) \
       <( perl -MO=Concise,-exec -Msyntax=void -E'say scalar f()' 2>&1 )
    --- /dev/fd/63  2014-08-17 12:34:29.124827443 -0700
    +++ /dev/fd/62  2014-08-17 12:34:29.128827401 -0700
    @@ -7 +7 @@
    -6  <1> entersub[t6] lKS/TARG    <-- "l" for list context
    +6  <1> entersub[t7] sKS/TARG    <-- "s" for scalar context
    

    And the same goes for use syntax qw( void )'s void:

    $ diff -u0 \
       <( perl -MO=Concise,-exec -Msyntax=void -E'say        f()' 2>&1 ) \
       <( perl -MO=Concise,-exec -Msyntax=void -E'say void   f()' 2>&1 )
    --- /dev/fd/63  2014-08-17 12:34:41.952692723 -0700
    +++ /dev/fd/62  2014-08-17 12:34:41.952692723 -0700
    @@ -7 +7 @@
    -6  <1> entersub[t6] lKS/TARG    <-- "l" for list context
    +6  <1> entersub[t6] vKS/TARG    <-- "v" for void context
    

    How use syntax qw( void ); works

    The real work is done by Syntax::Feature::Void's Void.xs, whose key lines follow:

    STATIC OP* parse_void(pTHX_ GV* namegv, SV* psobj, U32* flagsp) {
        return op_contextualize(parse_termexpr(0), G_VOID);
    }
    
    STATIC OP* ck_void(pTHX_ OP* o, GV* namegv, SV* ckobj) {
        return remove_sub_call(o);
    }
    
    BOOT: {
        const char voidname[] = "Syntax::Feature::Void::void";
        CV* const voidcv = get_cvn_flags(voidname, sizeof(voidname)-1, GV_ADD);
        cv_set_call_parser(voidcv, parse_void, &PL_sv_undef);
        cv_set_call_checker(voidcv, ck_void, &PL_sv_undef);
    }
    
    1. It declares sub void using get_cvn. (The sub never gets defined.) Code in the Void.pm will export the sub to the calling lexical scope.

    2. It tells Perl that calls to void follow a user-defined syntax using cv_set_call_parser.

    3. It tells Perl that calls to void need to be manipulated after they are compiled using cv_set_call_checker.

    4. When Perl encounters a call to void, the user-defined parser extracts a term using parse_termexpr, then changes the context of the term to void using op_contextualize.

    5. Afterwards, the checker removes the call to void from the opcode tree, while leavings its argument (the term) behind.