Demonstrating PERL with Tic-Tac-Toe, Part 2

The astute observer may have noticed that PERL is misspelled. In a March 1, 1999 interview with Linux Journal, Larry Wall explained that he originally intended to include the letter “A” from the word “And” in the title “Practical Extraction And Report Language” such that the acronym would correctly spell the word PEARL. However, before he released PERL, Larry heard that another programming language had already taken that name. To resolve the name collision, he dropped the “A”. The acronym is still valid because title case and acronyms allow articles, short prepositions and conjunctions to be omitted (compare for example the acronym LASER).

Name collisions happen when distinct commands or variables with the same name are merged into a single namespace. Because Unix commands share a common namespace, two commands cannot have the same name.

The same problem exists for the names of global variables and subroutines within programs written in languages like PERL. This is an especially significant problem when programmers try to collaborate on large software projects or otherwise incorporate code written by other programmers into their own code base.

Starting with version 5, PERL supports packages. Packages allow PERL code to be modularized with unique namespaces so that the global variables and functions of the modularized code will not collide with the variables and functions of another script or module.

Shortly after its release, PERL5 software developers all over the world began writing software modules to extend PERL’s core functionality. Because many of those developers (currently about 15,000) have made their work freely available on the Comprehensive Perl Archive Network (CPAN), you can easily extend the functionality of PERL on your PC so that you can perform very advanced and complex tasks with just a few commands.

The remainder of this article builds on the previous article in this series by demonstrating how to install, use and create PERL modules on Fedora Linux.

An example PERL program

See the example program from the previous article below, with a few lines of code added to import and use some modules named chip1, chip2 and chip3. It is written in such a way that the program should work even if the chip modules cannot be found. Future articles in this series will build on the below script by adding the additional modules named chip2 and chip3.

You should be able to copy and paste the below code into a plain text file and use the same one-liner that was provided in the previous article to strip the leading numbers.

00 #!/usr/bin/perl
01  
02 use strict;
03 use warnings;
04  
05 use feature 'state';
06  
07 use constant MARKS=>[ 'X', 'O' ];
08 use constant HAL9K=>'O';
09 use constant BOARD=>'
10 ┌───┬───┬───┐
11 │ 1 │ 2 │ 3 │
12 ├───┼───┼───┤
13 │ 4 │ 5 │ 6 │
14 ├───┼───┼───┤
15 │ 7 │ 8 │ 9 │
16 └───┴───┴───┘
17 ';
18  
19 use lib 'hal';
20 use if -e 'hal/chip1.pm', 'chip1';
21 use if -e 'hal/chip2.pm', 'chip2';
22 use if -e 'hal/chip3.pm', 'chip3';
23  
24 sub get_mark {
25    my $game = shift;
26    my @nums = $game =~ /[1-9]/g;
27    my $indx = (@nums+1) % 2;
28  
29    return MARKS->[$indx];
30 }
31  
32 sub put_mark {
33    my $game = shift;
34    my $mark = shift;
35    my $move = shift;
36  
37    $game =~ s/$move/$mark/;
38  
39    return $game;
40 }
41  
42 sub get_move {
43    return (<> =~ /^[1-9]$/) ? $& : '0';
44 }
45  
46 PROMPT: {
47    no strict;
48    no warnings;
49 
50    state $game = BOARD;
51  
52    my $mark;
53    my $move;
54  
55    print $game;
56  
57    if (defined &get_victor) {
58       my $victor = get_victor $game, MARKS;
59       if (defined $victor) {
60          print "$victor wins!\n";
61          complain if ($victor ne HAL9K);
62          last PROMPT;
63       }
64    }
65  
66    last PROMPT if ($game !~ /[1-9]/);
67  
68    $mark = get_mark $game;
69    print "$mark\'s move?: ";
70  
71    if ($mark eq HAL9K and defined &hal_move) {
72       $move = hal_move $game, $mark, MARKS;
73       print "$move\n";
74    } else {
75       $move = get_move;
76    }
77    $game = put_mark $game, $mark, $move;
78  
79    redo PROMPT;
80 }

Once you have the above code downloaded and working, create a subdirectory named hal under the same directory that you put the above program. Then copy and paste the below code into a plain text file and use the same procedure to strip the leading numbers. Name the version without the line numbers chip1.pm and move it into the hal subdirectory.

00 # basic operations chip
01 
02 package chip1;
03 
04 use strict;
05 use warnings;
06 
07 use constant MAGIC=>'
08 ┌───┬───┬───┐
09 │ 2 │ 9 │ 4 │
10 ├───┼───┼───┤
11 │ 7 │ 5 │ 3 │
12 ├───┼───┼───┤
13 │ 6 │ 1 │ 8 │
14 └───┴───┴───┘
15 ';
16  
17 use List::Util 'sum';
18 use Algorithm::Combinatorics 'combinations';
19 
20 sub get_moves {
21    my $game = shift;
22    my $mark = shift;
23    my @nums;
24 
25    while ($game =~ /$mark/g) {
26       push @nums, substr(MAGIC, $-[0], 1);
27    }
28 
29    return @nums;
30 }
31 
32 sub get_victor {
33    my $game = shift;
34    my $marks = shift;
35    my $victor;
36 
37    TEST: for (@$marks) {
38       my $mark = $_;
39       my @nums = get_moves $game, $mark;
40 
41       next unless @nums >= 3;
42       for (combinations(\@nums, 3)) {
43          my @comb = @$_;
44          if (sum(@comb) == 15) {
45             $victor = $mark;
46             last TEST;
47          }
48       }
49    }
50 
51    return $victor;
52 }
53 
54 sub hal_move {
55    my $game = shift;
56    my @nums = $game =~ /[1-9]/g;
57    my $rand = int rand @nums;
58 
59    return $nums[$rand];
60 }
61 
62 sub complain {
63    print "Daisy, Daisy, give me your answer do.\n";
64 }
65 
66 sub import {
67    no strict;
68    no warnings;
69 
70    my $p = __PACKAGE__;
71    my $c = caller;
72 
73    *{ $c . '::get_victor' } = \&{ $p . '::get_victor' };
74    *{ $c . '::hal_move' } = \&{ $p . '::hal_move' };
75    *{ $c . '::complain' } = \&{ $p . '::complain' };
76 }
77 
78 1;

The first thing that you will probably notice when you try to run the program with chip1.pm in place is an error message like the following (emphasis added):

$ Can't locate Algorithm/Combinatorics.pm in @INC (you may need to install the Algorithm::Combinatorics module) (@INC contains: hal /usr/local/lib64/perl5/5.30 /usr/local/share/perl5/5.30 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5) at hal/chip1.pm line 17.
BEGIN failed--compilation aborted at hal/chip1.pm line 17.
Compilation failed in require at /usr/share/perl5/if.pm line 15.
BEGIN failed--compilation aborted at game line 18.

When you see an error like the one above, just use the dnf command to search Fedora’s package repository for the name of the system package that provides the needed PERL module as shown below. Note that the module name and path from the above error message have been prefixed with */ and then surrounded with single quotes.

$ dnf provides '*/Algorithm/Combinatorics.pm'
...
perl-Algorithm-Combinatorics-0.27-17.fc31.x86_64 : Efficient generation of combinatorial sequences
Repo        : fedora
Matched from:
Filename    : /usr/lib64/perl5/vendor_perl/Algorithm/Combinatorics.pm

Hopefully it will find the needed package which you can then install:

$ sudo dnf install perl-Algorithm-Combinatorics

Once you have all the needed modules installed, the program should work.

How it works

This example is admittedly quite contrived. Nothing about Tic-Tac-Toe is complex enough to need a CPAN module. To demonstrate installing and using a non-standard module, the above program uses the combinations library routine from the Algorithm::Combinatorics module to generate a list of the possible combinations of three numbers from the provided set. Because the board numbers have been mapped to a 3×3 magic square, any set of three numbers that sum to 15 will be aligned on a column, row or diagonal and will therefore be a winning combination.

Modules are imported into a program with the use and require commands. The only difference between them is that the use command automatically calls the import subroutine (if one exists) in the module being imported. The require command does not automatically call any subroutines.

Modules are just files with a .pm extension that contain PERL subroutines and variables. They begin with the package command and end with 1;. But otherwise, they look like any other PERL script. The file name should match the package name. Package and file names are case sensitive.

Beware that when you are reading online documentation about PERL modules, the documentation often veers off into topics about classes. Classes are built on modules, but a simple module does not have to adhere to all the restrictions that apply to classes. When you start seeing words like method, inheritance and polymorphism, you are reading about classes, not modules.

There are two subroutine names that are reserved for special use in modules. They are import and unimport and they are called by the use and no directives respectively.

The purpose of the import and unimport subroutines is typically to alias and unalias the module’s subroutines in and out of the calling namespace respectively. For example, line 17 of chip1.pm shows the sum subroutine being imported from the List::Util module.

The constant module, as used on lines 07 of chip1.pm, is also altering the caller’s namespace (chip1), but rather than importing a predefined subroutine, it is creating a special type of variable.

All the identifiers immediately following the use keywords in the above examples are modules. On my system, many of them can be found under the /usr/share/perl5 directory.

Notice that the above error message states “@INC contains:” followed by a list of directories. INC is a special PERL variable that lists, in order, the directories from which modules should be loaded. The first file found with a matching name will be used.

As demonstrated on line 19 of the Tic-Tac-Toe game, the lib module can be used to update the list of directories in the INC variable.

The chip1 module above provides an example of a very simple import subroutine. In most cases you will want to use the import subroutine that is provided by the Exporter module rather than implementing your own. A custom import subroutine is used in the above example to demonstrate the basics of what it does. Also, the custom implementation makes it easy to override the subroutine definitions in later examples.

The import subroutine shown above reveals some of the hidden magic that makes packages work. All variables that are both globally scoped (that is, created outside of any pair of curly brackets) and dynamically scoped (that is, not prefixed with the keywords my or state) and all global subroutines are automatically prefixed with a package name. The default package name if no package command has been issued is main.

By default, the current package is assumed when an unqualified variable or subroutine is used. When get_move is called from the PROMPT block in the above example, main::get_move is assumed because the PROMPT block exists in the main package. Likewise, when get_moves is called from the get_victor subroutine, chip1::get_moves is assumed because get_victor exists in the chip1 package.

If you want to access a variable or subroutine that exists in a different package, you either have to use its fully qualified name or create a local alias that refers to the desired subroutine.

The import subroutine shown above demonstrates how to create subroutine aliases that refer to subroutines in other packages. On lines 73-75, the fully qualified names for the subroutines are being constructed and then the symbol table name for the subroutine in the calling namespace (the package in which the use statement is being executed) is being assigned the reference of the subroutine in the local package (the package in which the import subroutine is defined).

Notice that subroutines, like variables, have sigils. The sigil for subroutines is the ampersand symbol (&). In most contexts, the sigil for subroutines is optional. When working with references (as shown on lines 73-75 of the import subroutine) and when checking if a subroutine is defined (as shown on lines 57 and 71 of the PROMPT block), the sigil for subroutines is required.

The import subroutine shown above is just a bare minimum example. There is a lot that it doesn’t do. In particular, a proper import subroutine would not automatically import any subroutines or variables. Normally, the user would be expected to provide a list of the routines to be imported on the use line and that list is available to the import subroutine in the @_ array.

Final notes

Lines 25-27 of chip1.pm provide a good example of PERL’s dense notation problem. With just a couple of lines code, the board numbers on which a given mark has been placed can be determined. But does the statement within the conditional clause of the while loop perform the search from the beginning of the game variable on each iteration? Or does it continue from where it left off each time? PERL correctly guesses that I want it to provide the position ($-[0]) of the next mark, if any exits, on each iteration. But exactly what it will do can be very difficult to determine just by looking at the code.

The last things of note in the above examples are the strict and warnings directives. They enable extra compile-time and runtime debugging messages respectively. Many PERL programmers recommend always including these directives so that programming errors are more likely to be spotted. The downside of having them enabled is that some complex code will sometimes cause the debugger to erroneously generate unwanted output. Consequently, the strict and/or warnings directives may need to be disabled in some code blocks to get your program to run correctly as demonstrated on lines 67 and 68 of the example chip1 module. The strict and warnings directives have nothing to do with the program and they can be omitted. Their only purpose is to provide feedback to the program developer.

For Developers For System Administrators

12 Comments

  1. Perl has (unenforced) naming conventions for modules. A module with an all-lower-case name is known as a “pragma” and is expected to change the way the Perl compiler works in some way (see “strict” and “warnings” for two excellent examples). If you’re writing a module that just makes new functionality available to your program (as you are doing here) then the convention is to use an initial upper-case letter (so, Chip1.pm and “use Chip1”, etc.)

    It would be helpful if you could follow these conventions so as to not confuse beginners who find Perl through your (most welcome!) articles.

    • Hi Dave:

      Yes. So, sometimes letter casing is used to create a sort of “pseudo” namespace. This is the reason, for example, that it is recommended to name constants in all upper-case lettering and functions in all lower-case. Because PERL functions and constants exist in the same namespace, their names can clash/collide unless such a naming convention is used.

      Likewise, PERL’s built-in modules can clash/collide with user-defined modules because they too exist in a common namespace. The original convention was that built-in modules should be all lower-case and user-defined modules should have an initial upper-case letter so that the names would never conflict. Unfortunately, since PERL5’s release in 1994, many user-created modules have become somewhat “standard” packages that are installed on most systems by default. Consequently, a user-defined package is just as likely (if not more so) to collide with a “system” package (e.g. Eporter) if it has a leading upper-case letter. So the original rule isn’t as useful now as it used to be. None-the-less, if you intend to upload a module to CPAN, you must follow the original casing rules. Since I don’t intend (or want) these modules uploaded to CPAN, the lower-case name is fine.

      Some of PERL’s built-in modules do import functions/keywords (e.g. the “feature” module used in my example imports the “state” keyword).

  2. A couple of other comments.

    Calling subroutines using an ampersand is hasn’t been required since Perl 5 was released in 1994. And, as it can trigger some non-obvious behaviour, it is no longer considered best practice. Please consider replacing (for example) “&get_victor” with “get_victor()”. My blog post at https://perlhacks.com/2015/04/subroutines-and-ampersands/ explains the downsides in more detail.

    I don’t believe you need “no warnings” in your import() subroutine, and “no strict” would be better if you restricted it to “no strict ‘refs'” to only turn off the specific behaviour which is problematic here. Of course, none of this would be necessary if you used the standard Exporter mechanisms which you mention in the text.

    • Hi Dave:

      Yes, for various reasons, it is probably best not to use the leading & in most cases. It is still required when you are checking to see if a function is defined though. This usage is documented:

      https://perldoc.perl.org/5.30.0/functions/defined.html

      As to “no warnings”, one of its downsides is that it cannot “see” the future. In the not-yet-existant modules (chip2 and chip3), I intend to override the definition of the hal_move function. When I do so, the “warnings” pragma will start dumping undesired output (“warning” me that I have overridden the function) to the user’s console. To prevent this undesirable behavior, I must pre-disable the warnings in the current code.

      • Yeah, I’m sorry I completely misread what you were doing with the ampersands in your code. You’re right, using ampersands, in this case, is completely expected (and, indeed, necessary).

        • It’s all cool. Thanks for your feedback. I make mistakes too, and I don’t intend that my code be considered “professional”. I’m really just a sysadmin who occasionally “hacks” together a script (though I’ve been doing it for 20 years, so I do have a little experience).

      • But one of the advantages that “use warnings” has over the older “-w” is that you can turn specific warnings on and off. For the redefinition problem you’re anticipating, you can use “no warnings ‘redefine'” to just turn off that specific warning and still get the benefit of the pragma for any other problems you might accidentally introduce.

  3. Greate post. Keep posting such kind of info on your blog.
    Im really impressed by it.
    Hey there, You have performed a great job.

    I’ll certainly digg it and individually suggest to my friends.

    I’m confident they’ll be benefited from this website.

  4. Dilip

    Perl has got its syntax which appears cryptic rather than user friendly. But having said that it is much better compared to ruby. 😉
    But in the olden days, that was the way to go especially for scripting.
    As of today there are much better languages, personally I prefer something which is more user friendly and more easily understandable and usable to the beginners.
    In this era, given a choice perl would be my last choice of language to use and if ruby in the group it has got an upper hand 😉 .

    • John Smith

      I agree that PERL seems somewhat cryptic. But then, that can be said of most programming languages. I guess that each language has specific strengths, and that each language has a specific class of problems which it is particularly good at solving. It would be nice if that is true for PERL. What makes PERL particularly good in comparison to other scripting languages such as Ruby or Python?

      By the way, Ruby is my favorite programming language .
      I like these tutorials on fedoramagazine.org

      • Thanks John.

        The size and scope of the CPAN library is probably one of the things that make PERL more appealing to potential developers than anything else. There are a lot of free modules available (currently 192,257) that do a lot of very diverse and useful things. Other than that it is probably more just a matter of “taste”. PERL has a very free/unrestricted style that some developers really like (and that others cannot stand).

        I like Ruby and Python as well. They both seem like very well structured languages. I do very little work with them though. The only real work I’ve ever done with Ruby is hacking a Redmine CMS, but I had no trouble at all reading and editing the code despite having no specific training with it. I also like its system for managing “gems”.

  5. I pay a visit day-to-day a few web pages and blogs to read
    posts, but this webpage gives quality based posts.

Comments are Closed

The opinions expressed on this website are those of each author, not of the author's employer or of Red Hat. Fedora Magazine aspires to publish all content under a Creative Commons license but may not be able to do so in all cases. You are responsible for ensuring that you have the necessary permission to reuse any work on this site. The Fedora logo is a trademark of Red Hat, Inc. Terms and Conditions