sub returning nothing

G

Gunnar Strand

Hi,

IANAPG (I Am Not A Perl Guru), but I found something which got me a
little worried. This has probably been discussed ad nauseum, but I
haven't found anything about it in the FAQ. sub's returning nothing
can be dangerous in some contexts. Consider this code:

#!/usr/bin/perl
use strict;
use warnings;

sub isok {
if ( $_[0] ) {
print "Ok\n";
} else {
print "Nok\n";
}
}

sub myvoid {
}

isok( myvoid(), 1 );
isok( ! myvoid(), 1 );

This script will print

Ok
Ok

and not any 'Nok'. I assume that's because 'nothing' is magically
removed from @_, leaving only the '1', right? I am trying out
the Test::Unit suite and had an empty sub in the code under test
while using assert in my test case, and all tests passed:

$self -> assert( $target -> the_test(), "Test OK" );
$self -> assert( ! $target -> the_test(), "Test NOK" );

IMHO, it would have been nice if perl could have issued some kind
of warning, since relying on 'nothing' as a return value seems...
well, not-so-common. Maybe perl is making heavy use of it internally?

Regards,

/Gunnar
 
J

Jeff 'japhy' Pinyan

sub isok {
if ( $_[0] ) {
print "Ok\n";
} else {
print "Nok\n";
}
}

sub myvoid {
}

isok( myvoid(), 1 );
isok( ! myvoid(), 1 );

This script will print

Ok
Ok

and not any 'Nok'. I assume that's because 'nothing' is magically
removed from @_, leaving only the '1', right? I am trying out

Because isok() isn't enforcing any calling context on its arguments,
myvoid() is called in list context (since it's in the argument list to a
function), and nothingness in list context is (), and empty list.
 
B

Brian McCauley

Gunnar Strand said:
Hi,

IANAPG (I Am Not A Perl Guru), but I found something which got me a
little worried. This has probably been discussed ad nauseum,

Yes I can recall one really nausiating discussion of this.
but I
haven't found anything about it in the FAQ. sub's returning nothing
can be dangerous in some contexts. Consider this code:

#!/usr/bin/perl
use strict;
use warnings;

sub isok {
if ( $_[0] ) {
print "Ok\n";
} else {
print "Nok\n";
}
}

sub myvoid {
}

That is not a subroutine returning nothing.

That is an empty function.

In a list context an empty function retunrns its arguments.

In a scalar context it returns undef.

How ever I wouldn't consider this to be defined bevaviour. I would be
inclined to consider the behaviour of an empty function wrt what it
returns to be undefined.

So let us instead consider a function that returns nothing.

sub myvoid {
return;
}

isok( myvoid(), 1 );
isok( ! myvoid(), 1 );

This script will print

Ok
Ok

and not any 'Nok'. I assume that's because 'nothing' is magically
removed from @_, leaving only the '1', right?

No magic. If you call a function in a list context and it returns
'nothing' then nothing is an empty list. If you append a 1 to an
emplt list you get a single elemement list containing the value 1.
I am trying out
the Test::Unit suite and had an empty sub in the code under test
while using assert in my test case, and all tests passed:

$self -> assert( $target -> the_test(), "Test OK" );
$self -> assert( ! $target -> the_test(), "Test NOK" );

IMHO, it would have been nice if perl could have issued some kind
of warning, since relying on 'nothing' as a return value seems...
well, not-so-common.

I'm affraid that calling functions in a list context is common in
perl.
 
G

Gunnar Strand

Brian said:
That is not a subroutine returning nothing.

That is an empty function.

In a list context an empty function retunrns its arguments.

It returns an empty list in a list context according to perlmod,
and perl -e 'sub a{};print a(1, 2, 3)' prints nothing.
In a scalar context it returns undef.

How ever I wouldn't consider this to be defined bevaviour. I would be
inclined to consider the behaviour of an empty function wrt what it
returns to be undefined.
So let us instead consider a function that returns nothing.

sub myvoid {
return;
}



No magic. If you call a function in a list context and it returns
'nothing' then nothing is an empty list. If you append a 1 to an
emplt list you get a single elemement list containing the value 1.

Thanks for the clarifaction. I didn't think about the list
context for the sub arguments.
I'm affraid that calling functions in a list context is common in
perl.

Agreed. But it is going to make it more difficult to write test
cases for methods erroneously using return; (or empty) instead of
a valid value, as some tests will have to create the needed
context (or at least when using Test::Unit::TestCase.)

Thanks for the link to the discussion, that was interesting reading.

Cheers,

/Gunnar
 
B

Brian McCauley

Gunnar Strand said:
It returns an empty list in a list context according to perlmod,
and perl -e 'sub a{};print a(1, 2, 3)' prints nothing.

You are right, it does in 5.8. I was unaware that it has changed.

Which, of course, is now bourne out to be very good advice wrt 5.6
because the behaviour in 5.8 is indeed different.

You say the new behaviour is documented in perlmod? I think you meant
perlsub. :)

--
\\ ( )
. _\\__[oo
.__/ \\ /\@
. l___\\
# ll l\\
###LL LL\\
 
A

Anno Siegel

Brian McCauley said:
You are right, it does in 5.8. I was unaware that it has changed.

It doesn't for me in 5.8.1, that is, it prints "123". (I'll check
5.8.3 later). I cannot imagine that an incompatible change like this
would be made. Could some shell deviltry be obscuring the output?
Which, of course, is now bourne out to be very good advice wrt 5.6
because the behaviour in 5.8 is indeed different.

You say the new behaviour is documented in perlmod? I think you meant
perlsub. :)

I can't find the *old* (and, I suspect, still current) behavior described
in the corresponding version of perldoc, and I was never aware of it
until now. (Speak of not knowing about fundamental language details...)
It could be taken as a consequence of the "last expression evaluated"
rule, though the argument list is nowhere visibly evaluated in the sub.

Anno
 
G

Gunnar Strand

Anno said:
Brian McCauley said:
Brian McCauley wrote:


[...]

sub myvoid {
}

That is not a subroutine returning nothing.
That is an empty function.
In a list context an empty function retunrns its arguments.

It returns an empty list in a list context according to perlmod,
and perl -e 'sub a{};print a(1, 2, 3)' prints nothing.

You are right, it does in 5.8. I was unaware that it has changed.


It doesn't for me in 5.8.1, that is, it prints "123". (I'll check
5.8.3 later). I cannot imagine that an incompatible change like this
would be made. Could some shell deviltry be obscuring the output?

I am using v5.8.4 built for i386-linux-thread-multi, so it
sounds like it have changed very recently.
I can't find the *old* (and, I suspect, still current) behavior described
in the corresponding version of perldoc, and I was never aware of it
until now. (Speak of not knowing about fundamental language details...)
It could be taken as a consequence of the "last expression evaluated"
rule, though the argument list is nowhere visibly evaluated in the sub.

Brian's correct, it's in the perlsub page (mental lapse on
my part :).

Regards,

/Gunnar
 
T

Tassilo v. Parseval

Also sprach Gunnar Strand:
I am using v5.8.4 built for i386-linux-thread-multi, so it
sounds like it have changed very recently.

This appears to have been changed in 5.8.2. 5.8.1 is the last release
where empty functions return their arguments for me.

Tassilo
 
B

Brian McCauley

Tassilo v. Parseval said:
Also sprach Gunnar Strand:

I don't think any sane person would claim that the old behaviour was
defined so I don't really see that compatability is an issue.
This appears to have been changed in 5.8.2. 5.8.1 is the last release
where empty functions return their arguments for me.

But, strangely, 5.8.2 is not the first (even) release where empty
functions don't return their arguments. They didn't in 5.8.0 either.

--
\\ ( )
. _\\__[oo
.__/ \\ /\@
. l___\\
# ll l\\
###LL LL\\
 
B

Ben Morrow

<snippage:

sub isok {
print $_[0] ? 'OK' : 'NOK';
print "\n";
}

sub void {
return;
}

now isok(void, 1) prints 'OK' not 'NOK'
Agreed. But it is going to make it more difficult to write test
cases for methods erroneously

It is not erroneous. It is often necessary: if you wish a function to
return false in list context, it must return the empty list.
using return; (or empty) instead of
a valid value, as some tests will have to create the needed
context (or at least when using Test::Unit::TestCase.)

I'm not quite understanding your usage of isok... AFAICS, isok takes one
argument, and you are calling it with two; so I wouldn't be surprised
when it doesn't do what you expect.

isok void;

correctly prints 'NOK';

isok 1 == void;

correctly prints 'NOK' as well.

I will assume you're not being stupid :), and that in fact your isok
function is more like:

sub isok {
print +( $_[0] ? 'ok' : 'not ok' ), " # $_[1]\n";
}

and your problem is that

isok void, 'testing void function';

prints 'ok' not 'not ok'. The answer here is that isok should have been
prototyped with ($$): in that case, the first arg would be evaluated in
scalar context, and would have been 'undef' not '()', and it would have
worked correctly.

If for some reason you don't wish to use prototypes then you must do the
work of adding scalar() where necessary yourself.

Ben
 
G

Gunnar Strand

Ben said:
[snip]
Agreed. But it is going to make it more difficult to write test
cases for methods erroneously


It is not erroneous. It is often necessary: if you wish a function to
return false in list context, it must return the empty list.

Of course, but in this case I was writing a test case for a sub
which was expected to return a scalar, so here it was in fact
erroneous. I just didn't expect the result I got.
using return; (or empty) instead of
a valid value, as some tests will have to create the needed
context (or at least when using Test::Unit::TestCase.)


I'm not quite understanding your usage of isok... AFAICS, isok takes one
argument, and you are calling it with two; so I wouldn't be surprised
when it doesn't do what you expect.
isok void;

correctly prints 'NOK';

isok 1 == void;

correctly prints 'NOK' as well.

I will assume you're not being stupid :), and that in fact your isok
function is more like:

sub isok {
print +( $_[0] ? 'ok' : 'not ok' ), " # $_[1]\n";
}

Good point, I should have used the second argument of isok in my
example for clarity. Thanks for your confidence, but sometimes I
wonder :)
and your problem is that

isok void, 'testing void function';

prints 'ok' not 'not ok'. The answer here is that isok should have been
prototyped with ($$): in that case, the first arg would be evaluated in
scalar context, and would have been 'undef' not '()', and it would have
worked correctly.

Unfortunately what you are suggesting will not work since I am using
Test::Unit::TestCase::assert() (as isok), which can accept either one
or more arguments, so it has no chance of telling which way it was
called.
If for some reason you don't wish to use prototypes then you must do the
work of adding scalar() where necessary yourself.

Yes, I used the assert( myvoid() || '', "Function failed" ) trick I saw
in the thread Brian posted about this issue. It is just that a day
will come when I forget to add the scalar context and think my sub is
working hard, while it is actually in a coma at the hospital because I
didn't give it a brain.

Regards,

/Gunnar
 
B

Ben Morrow

Quoth Gunnar Strand said:
Ben said:
I will assume you're not being stupid :), and that in fact your isok
function is more like:

sub isok {
print +( $_[0] ? 'ok' : 'not ok' ), " # $_[1]\n";
}

Good point, I should have used the second argument of isok in my
example for clarity. Thanks for your confidence, but sometimes I
wonder :)
and your problem is that

isok void, 'testing void function';

prints 'ok' not 'not ok'. The answer here is that isok should have been
prototyped with ($$): in that case, the first arg would be evaluated in
scalar context, and would have been 'undef' not '()', and it would have
worked correctly.

Unfortunately what you are suggesting will not work since I am using
Test::Unit::TestCase::assert() (as isok), which can accept either one
or more arguments, so it has no chance of telling which way it was
called.

Actually, the problem with assert is that it is a method, so it can't be
prototyped. If it were a function, it should have a prototype of ($;$):
one scalar and one optional scalar argument.

If this is bugging you, I would recommend writing call_assert:

sub call_assert ($$;$) {
shift->assert(@_);
}
Yes, I used the assert( myvoid() || '', "Function failed" )

Hmm, that seems a little obscure, when this is precisely what the scalar
builtin is for:

$self->assert( scalar myvoid => 'Function failed' );

Ben
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,900
Latest member
Nell636132

Latest Threads

Top