Demonstrating PERL with Tic-Tac-Toe, Part 2

栏目: IT技术 · 发布时间: 4年前

内容简介:The astute observer may have noticed that PERL is misspelled. InName collisions happen when distinct commands or variables with the same name are merged into a singleThe same problem exists for the names of global variables and subroutines within programs

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 “ P ractical E xtraction A nd R eport L anguage” 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 onthe 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 sameone-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.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

How to Solve It

How to Solve It

Zbigniew Michalewicz、David B. Fogel / Springer / 2004-03-01 / USD 59.95

This book is the only source that provides comprehensive, current, and detailed information on problem solving using modern heuristics. It covers classic methods of optimization, including dynamic pro......一起来看看 《How to Solve It》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具