7

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?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Francisco Zarabozo
  • 3,676
  • 2
  • 28
  • 54

3 Answers3

10
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.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • 1
    I'd be VERY interested on reading that code. Of course, I cannot ask you to invest time in something with no other purpose than satisfying my hunger of knowing more quirky corners of Perl. So, if you ever feel like doing it just because it can be done, I hope you please show me. Thanks for your answer. :-) – Francisco Zarabozo Aug 17 '14 at 16:43
  • 1
    @Francisco Zarabozo, [Syntax::Feature::Void](http://search.cpan.org/perldoc?Syntax::Feature::Void) has been uploaded to CPAN. It will appear on your favorite mirror shortly if it's not already. (A day at the most) – ikegami Aug 17 '14 at 19:42
  • Well, except for a couple of lines, it's virtually identical to my earlier Syntax::Feature::Loop, so it wasn't hard. Added an explanation of how it works to my answer. – ikegami Aug 18 '14 at 03:21
2

You have to make sure that the return code of the function is definitely not used, e.g.

context();
1;

And of course it makes no sense to return 'void' if nothing is wanted as return (!defined wantarray), because this return value will no be used.

Steffen Ullrich
  • 114,247
  • 10
  • 131
  • 172
  • Well, that's pretty logic. But, the question is: is there a way to force void context other that actually be in void context? – Francisco Zarabozo Aug 17 '14 at 13:23
  • You can force a function to be in void context but you cannot make it believe it is in void context when it is not. Would not make much sense. – Steffen Ullrich Aug 17 '14 at 13:24
  • Would you say that statement is absolutely true for any possible situation? :-) – Francisco Zarabozo Aug 17 '14 at 13:27
  • 2
    I think there is no distinction between "is in void context" and "believes is in void context". Its just a flag. So I assume this statement is absolutely true. But one could be surprised which contexts are void and which only seem to be void at the first look. – Steffen Ullrich Aug 17 '14 at 13:29
1

What you are actually asking

Quoting man perldata:

When you use the use warnings pragma or Perl's -w command-line option, you may see warnings about useless uses of constants or functions in "void context". Void context just means the value has been discarded, such as a statement containing only "fred"; or getpwuid(0);. It still counts as scalar context for functions that care whether or not they're being called in list context.

User-defined subroutines may choose to care whether they are being called in a void, scalar, or list context. Most subroutines do not need to bother, though. That's because both scalars and lists are automatically interpolated into lists. See wantarray for how you would dynamically discern your function's calling context.

So you’re asking if it is possible to throw value of a function call away completely if the call is performed in list or scalar context otherwise.

The answer is yes!

In scalar context, only last element of a list is used, other elements are evaluated in void context. Even more true: The “list” actually never was a list. Read more on behavior of comma operator in scalar context in man perlop for explanation. It is also explained in other words near the end of description section in man perlfunc. And finally perldoc -f scalar mentions this too.

In list context, there is no such direct way. You need to use the same trick as above to get an arbitrary scalar (preferably 0) and then get rid of it so that it does not influence the contents of your list. Empty list repetition is what you’re looking for. (By the way repetition operator evaluates its second operand in scalar context.)

sub test_context() {
    wantarray and die "list\n";
    defined wantarray and die "scalar\n";
    die "void\n";
}
$\ = "\n"; # to make output prettier
### Uncomment the one you want to test.
# -- Somewhat canonical examples of contexts
#[test_context]; # list (+ warning of class 'void')
#print test_context; # list
#scalar(test_context); # scalar (forces scalar context anywhere)
#my $x = test_context; # scalar
#test_context; # void
#
# -- Examples of forcing void context
# Replace test_context with a fixed scalar and try again to see that even if
# the function returned a value, it would get discarded. Ignore the 'void' warning.
#print my $x = (test_context, 42);
#print '^', () x (test_context, 0), '$';

Caveat: no void()

You cannot create a void function, whose usage would resemble that one of scalar.

sub void {
    ();
}
print void(test_context);

This would result in test being called in list context, because function parameters are always evaluated in list context, unless told otherwise in prototype. And prototypes cannot force void context.

You can only implement such a thing by changing Perl’s syntax, which is possible but quite complicated.

The best approximation you can get with default Perl syntax is presented in ikegami’s answer.

Why would you want such a thing?

I assume this question arose from pure idle curiosity and maybe from desire to understand Perl contexts better. It has no real use whatsoever, in my opinion. As example in perldoc -f wantarray implies, the undefined return value representing void context is intended to be used to speed up the computation, e.g. to avoid output generation if side-effects can be performed without it.

return unless defined wantarray; # don't bother doing more
my @a = complex_calculation();
return wantarray ? @a : "@a";
Palec
  • 12,743
  • 8
  • 69
  • 138