Table of Contents

Multiple Returns

That's all the major learning out the way with. The next step is relatively easy, but we need to add new functionality to the program in order to demonstrate it. What we will do is work out how long it will take the glider pilot to fly the distance. For this calculation, we need to know his airspeed. That can be a third parameter. The actual calculation will be part of howfar. An easy change:

($height,$ratio,$airspeed)=@ARGV;
$cnv1=3.2;			
$cnv2=1.8;

($distance,$time)=&howfar($height,$ratio,$airspeed);
print "Glide ratio $ratio:1, $distance from $height taking $time\n";

($distance,$time)=&howfar($height,$ratio+1,$airspeed);
print "Glide ratio ",$ratio+1,":1, $distance from $height taking $time\n";

($distance,$time)=&howfar($height,$ratio-1,$airspeed);
print "Glide ratio ",$ratio-1,":1, $distance from $height taking $time\n";

sub howfar {
	my ($height,$ratio,$airspeed)=@_;
	my ($distance,$time);			    # how to 'my' multiple variables
	$airspeed*=$cnv2;		 	    # convert knots to kmph
	$height  =int($height/$cnv1);
	$distance=int($height*$ratio/1000);
	$time	 =int($distance/($airspeed/60));    # simple time conversion
	# print "Time:$time, Distance:$distance\n"; # uncomment this later
}

This doesn't work correctly. First, the changes. The result from howfar is now assigned to two variables. Subroutines return a list, and so assigning to some scalar variables between parens separated by commas will work. This is exactly the same as reading the command line arguments from @ARGV .

We are also passing a new parameter, $airspeed. There is a another conversion and a one-line calculation to provide the amount of minutes it will take to fly $distance.

If you look carefully, you can perhaps work out what the problem is. There was a clue in the Regex section, when /e was explained.

The problem is that Perl returns the result of the last expression evaluated. In this case, the last expression is the one calculating $time, so the value $time is returned, and it is the only value returned. Therefore, the value of $time is assigned to $distance, and $distance itself doesn't actually get a value at all.

Re-run the program but this time uncomment the line in the subroutine which prints $distance and $time. You'll noticed the value is 1, which means that the expression was successful. Perl is faithfully returning the value of the last expression evaluated.

This is all well and good, but not what we need. What is required is a method of telling Perl what needs to be returned, rather than what Perl thinks would be a good idea:

($height,$ratio,$airspeed)=@ARGV;
$cnv1=3.2;			
$cnv2=1.8;

($distance,$time)=&howfar($height,$ratio,$airspeed);
print "Glide ratio $ratio:1, $distance from $height taking $time\n";

($distance,$time)=&howfar($height,$ratio+1,$airspeed);
print "Glide ratio ",$ratio+1,":1, $distance from $height taking $time\n";

($distance,$time)=&howfar($height,$ratio-1,$airspeed);
print "Glide ratio ",$ratio-1,":1, $distance from $height taking $time\n";

sub howfar {
	my ($height,$ratio,$airspeed)=@_;
	my ($distance,$time);			 # how lexically scope multiple variables
	$airspeed*=$cnv2;			 # convert knots to kmph
	$height  =int($height/$cnv1);
	$distance=int($height*$ratio/1000); 	 # output result in kilometres not metres
	$time	 =int($distance/($airspeed/60)); # simple time conversion
	return ($distance,$time);		 # explicit return
}

A simple fix. Now, we tell Perl what to return, with the aptly named return function. With this function we have complete control over what is returned and when. It is quite usual to use if statements to control different return values, but we won't bother with that here.

There is a subtle flaw in the program above. It is not backwards compatible with the old method of calling the subroutine. Run this:

($height,$ratio,$airspeed)=@ARGV;
$cnv1=3.2;			
$cnv2=1.8;

($distance,$time)=&howfar($height,$ratio,$airspeed);
print "Glide ratio $ratio:1, $distance from $height taking $time\n";

($distance,$time)=&howfar($height,$ratio+1,$airspeed);
print "Glide ratio ",$ratio+1,":1, $distance from $height taking $time\n";

$distance=&howfar($height,$ratio-1);	# old way of calling it
print "With a glide ratio of ",$ratio-1,":1 you can fly $distance from $height\n";

sub howfar {
	my ($height,$ratio,$airspeed)=@_;
	my ($distance,$time);			 
	$airspeed*=$cnv2;			 
	$height  =int($height/$cnv1);
	$distance=int($height*$ratio/1000); 	 
	$time	 =int($distance/($airspeed/60)); 
	return ($distance,$time);
}

A division by 0 results third time around. This is of course because $airspeed doesn't exist, so of course it will effectively be 0. Making your subroutines backwards compatible is important in large programs, or if you are writing an add-in module for other people to use. You can't expect everyone to retrofit additional parameters to their subroutine calls just because you decided to be a bit creative one day.

There are many ways to fix the problem, and this is just one:

($height,$ratio,$airspeed)=@ARGV;
$cnv1=3.2;			
$cnv2=1.8;

($distance,$time)=&howfar($height,$ratio,$airspeed);
print "Glide ratio $ratio:1, $distance from $height taking $time\n";

($distance,$time)=&howfar($height,$ratio+1,$airspeed);
print "Glide ratio ",$ratio+1,":1, $distance from $height taking $time\n";

$distance=&howfar($height,$ratio-1);
print "With a glide ratio of ",$ratio-1,":1 you can fly $distance from $height\n";

print "Direct print: ",join ",",&howfar(5000,55,60)," not bad for no engine!\n";

sub howfar {
	my ($height,$ratio,$airspeed)=@_;
	my ($distance,$time);			 # how to 'my' multiple variables
	$airspeed*=$cnv2;			 # convert knots to kmph
	$height  =int($height/$cnv1);
	$distance=int($height*$ratio/1000); 	 # output result in kilometres not metres
	if ($airspeed > 0) {
		$time	 =int($distance/($airspeed/60));
		return ($distance,$time);
	} else {
		return $distance;
	}
}

Here we just test the $airspeed to ensure we won't be doing any divisions by 0. It also affects what we return. There is also a new print statement, which shows that you don't need to assign to intermediate variables, or even pass variables as parameters. Constants, evil things that they are, work just as well. I already mentioned this, but a demonstration doesn't hurt. Unless you work for an electric chair manufacturer.

The astute reader.....:-) Every time I read that I wonder what I've missed. Usually something obscure which the author knows nobody will ever notice, but likes to belittle the reader. No exception here! Anyway, you may be wondering why this would not have sufficed instead of the if statement:

sub howfar {
	my ($height,$ratio,$airspeed)=@_;
	my ($distance,$time);			 # how to 'my' multiple variables
	$airspeed*=$cnv2;			 # convert knots to kmph
	$height  =int($height/$cnv1);
	$distance=int($height*$ratio/1000); 	 # output result in kilometres not metres
	$time	 =int($distance/($airspeed/60)) if $airspeed > 0;
	return ($distance,$time);
}

After all, the first item returned is $distance, so therefore it should be the first one assigned via:

$distance=&howfar($height,$ratio-1);

and $time should just disappear into the bit bucket.

The answer lies with scalars and lists. We are returning a list, but assigning it to a scalar. What happens when you do that? The scalar takes on the last value of the list. The last value of the list being returned is of course $time, which is has been declared but not otherwise touched. Therefore, it is nothing and appears as such on the printed statement. A small program to demonstrate that point:

$word=&wordfunc("Greetings");
print "The word is $word\n";

(@words)=&wordfunc("Bonjour");
print "The words are @words\n";

sub wordfunc {
my $word=shift;		# when in a subroutine, shifts @_ if no target specified
	my @words;				# how to my an array
	@words=split //,$word;			# splits on the nothings between each letter
	($first,$last)=($words[0],$words[$#words]);  # see section on Arrays if required
	return ($first,$last);			# Returns just the first and last
}

As you can see, the first call prints the letter 's', which is the last element of the list that is returned. You could of course use a list consisting of just one element:

($word)=&wordfunc("Greetings");

Now we are assigning a list to a list, so perl starts at the first element and keeps assigning till it runs out of elements. The parens turns a lonely scalar into an element of a list. You might consider always assigning the results of subroutines this way, as you never know when the subroutine might change. I know I've just evangelised about how subroutines shouldn't change, but if you take care and the subroutine write takes care, there definitely won't be any problems!

That's about it for good old my . There is a lot more to learn about it but that's enough to get started. You now know about a little about variable visibility, and I don't mean changeable weather.