Perl - Making It a Bit More Modular

Just like any other serious programming language, Perl provides subroutines that have parameters and return values.  A subroutine is defined once in a program, and can be used repeatedly by being invoked from within any expression.

For our small, but rapidly growing, program, let's create a subroutine called good_word that takes a name and a guessed word, and returns true if the word is correct and false if not.  The definition of such a subroutine could look like this:

sub good_word {

	my($somename,$someguess) = @_;			# name the parameters
	$somename =~ s/\W.*//;				# zap everything after the first word
	$somename =~ tr/A-Z/a-z/;			# lowercase everything
	if ($somename eq "larry") {			# should not need to guess
		return 1;				# return value is true
	}	
	elsif {($words{$somename}) || "mfd") eq $someguess) {	
		return 1;				# return value is true
	}
	else {
		return 0:				# return value is false
	}

}

First, the definition of a subroutine consists of the reserved word sub followed by the subroutine name followed by a block of code (delimited by curly braces).  While this definition could appear anywhere in the program, most programmers put subroutines at the end of the program.

The first line within this particular subroutine is an assignment statement that copies the values of the two parameters being passed to the subroutine into two local variables named $somename and $someguess.  (The my() defines the two variables as private to the enclosing block - in this case, the entire subroutine - and the parameters are initially in a special local array called @_.

The next two lines clean up the name, just like the previous version of the program.

The if-elsif-else statement determines whether the guessed word ($someguess) is correct for the name ($somename).  Larry should not make it into this subroutine, but even if it does, whatever word was guessed is OK.

A return statement can be used to make the subroutine immediately return to its caller with the supplied value.  In the absence of an explicit return statement, the last expression evaluated in a subroutine is the return value.  We'll see how the return value is used after we finish describing the subroutine definition.

The test for the elsif part looks a little complicated; let's break it apart:

	($words{$somename} || "mfd") eq $someguess

The first thing inside the parentheses is our familiar hash lookup, yielding some value from %words based on a key of $somename.  The operator between that value and the string mfd is the || (logical-OR) operator similar to that used in C and awk and the various UNIX shells.  If the lookup from the hash has a value (meaning that the key $somename was in the hash), the value of the expression is that value.  If the key could not be found, the string of mfd is used instead.  This is a very Perl-like thing to do:  specify some expression, and then provide a default value using || in case the expression turns out to be false.

In any case, whether it's a value from the hash, or the default value mfd, we compare it to whatever was guessed.  If the comparison is true, we return 1, otherwise we return 0.

So, expressed as a rule, if the name is larry, or the guess matches the lookup in %words based on the name (with a default value of mfd if not found), then the subroutine returns 1, otherwise it returns 0.

Now let's integrate all this with the rest of the program:

#!/usr/bin/perl -w
%words = qw(
	Jason	camel
	Michele	llama
	Tracey	alpaca
	Karee	pea
	Patrick	alpaca
);
print "What is your name? ";
$name = <STDIN>;
chomp ($name);
if ($name =~ /^Larry\b/i) {			# back to the other way (;-)
	print "Hello, Larry!  How good of you to be here!\n";
}
else {
	print "Hello, \U$name!\n"; 		# ordinary greeting (with $name all UPPERCASE)
	print "What is the secret word? ";
	$guess = <STDIN>;
	chomp ($guess);
	while (! &good_word($name,$guess)) {
		print "Wrong, try again.  What is the secret word? ";
		$guess = <STDIN>;
		chomp ($guess);
	}					# end of while not correct
	print "Good guess!  Have a great day!!";
}
[ . . . insert definition of &good_word() here . . . ]

Notice that we've gone back to the regular expression to check for Larry, because now there's no need to pull apart the first name and convert it to lowercase, as far as the main program is concerned.

The big difference is the while loop containing the subroutine &good_value.  Here we see an invocation of the subroutine, passing two parameters, $name and $guess.  Within the subroutine, the value of $somename is set from the first parameter, in this case $name.  Likewise, $someguess is set from the second parameter, $guess.

You'll also want to note the ampersand (&) which is appended to the name of the subroutine when we "call" it!  What's that all about?  Well, recalling that Perl, itself, has many built-in functions, what do you suppose would happen if, by chance, we choose a name for a subroutine that is the same name as a Perl built-in function?  Our subroutine would be "called" instead of the Perl built-in function everytime!  To eliminate that possibility, always use an ampersand (&) to begin your subroutine names.

Recalling the rule mentioned earlier, the value returned by the subroutine (either 1 or 0), is logically inverted with the prefix ! (logical not) operator.  This operator returns true if the expression following is false, and returns false if the expression following is true.  The result of the negation controls the while loop.  You can read this as "while it's not a good word . . .".  Many well-written Perl programs read very much like English, provided you take a few liberties with either Perl or English.  (But you certainly won't win a Pulitzer Prize that way.)

Note that the program assumes that the value of the %words hash is set by the main program.

Such a cavalier approach to global variables doesn't scale very well, of course.  Generally speaking, variables not created with my are global to the whole program, while those my creates last only until the block in which they were declared exists.  Don't worry:  Perl does in fact support a rich variety of other kinds of variables, including those private to a file (or package), as well as variables private to a function that retain their values between invocations, which is what we could really use here.  However, because of the complexities involved in explaining those types of variables, you won't see them until the second course in this series - Programming Perl.

What if we wanted to share the secret word list among three programs?  If we store the word list as we have already done, then we will need to change all three programs when Karee decides that her secret word should be panda rather than pea.  This could get to be a hassle, especially if Karee were to change her mind often.

So, let's put the word list into a file and then read the file to get the word list into the program.  What elements do you suppose are needed to do this task?  See you next lesson!