Demostración de PERL con Tic-Tac-Toe, Parte 2

El observador astuto puede haber notado que PERL está mal escrito. En una entrevista del 1 de marzo de 1999 con Linux JournalLarry Wall explicó que originalmente tenía la intención de incluir la letra “A” de la palabra “Y” en el título “PAGSpractico miextracción ADakota del Norte Rinforme Lidioma” tal que el acrónimo deletrearía correctamente la palabra PEARL. Sin embargo, antes de lanzar PERL, Larry escuchó que otro lenguaje de programación ya había tomado ese nombre. Para resolver el colisión de nombres, dejó caer la “A”. El acrónimo sigue siendo válido porque el caso del título y las siglas permiten omitir artículos, preposiciones cortas y conjunciones (comparar para example el acrónimo LÁSER).

Las colisiones de nombres ocurren cuando distintos comandos o variables con el mismo nombre se fusionan en uno solo. espacio de nombres. Dado que los comandos de Unix comparten un espacio de nombres común, dos comandos no pueden tener el mismo nombre.

El mismo problema existe para los nombres de variables globales y subrutinas dentro de programas escritos en lenguajes como PERL. Este es un problema especialmente importante cuando los programadores intentan colaborar en grandes proyectos de software o incorporar código escrito por otros programadores en su propia base de código.

Empezando con versión 5PERL admite paquetes. Los paquetes permiten que el código PERL se modularice con espacios de nombres únicos para que las variables y funciones globales del código modularizado no entren en conflicto con las variables y funciones de otro script o módulo.

Poco después de su lanzamiento, los desarrolladores de software PERL5 de todo el mundo comenzaron a escribir módulos de software para ampliar la funcionalidad central de PERL. Debido a que muchos de esos desarrolladores (actualmente alrededor de 15,000) han hecho que su trabajo esté disponible gratuitamente en la Red integral de archivos de Perl (CPAN)puede extender fácilmente la funcionalidad de PERL en su PC para que pueda realizar tareas muy avanzadas y complejas con solo unos pocos comandos.

El resto de este artículo se basa en el artículo anterior de esta serie al demostrar cómo instalar, usar y crear módulos PERL en Fedora linux

Un example programa PERL

Ver el example programa del artículo anterior a continuación, con algunas líneas de código agregadas para importar y usar algunos módulos llamados chip1, chip2 y chip3. Está escrito de tal manera que el programa debería funcionar incluso si no se pueden encontrar los módulos de chip. Los artículos futuros de esta serie se basarán en el siguiente script al agregar los módulos adicionales llamados chip2 y chip3.

Debería poder copiar y pegar el siguiente código en un archivo de texto sin formato y usar la misma línea que se proporcionó en el artículo anterior para eliminar los números iniciales.

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 "$moven";
74    } else {
75       $move = get_move;
76    }
77    $game = put_mark $game, $mark, $move;
78  
79    redo PROMPT;
80 }

Una vez que haya descargado y funcionando el código anterior, cree un subdirectorio llamado hal en el mismo directorio en el que colocó el programa anterior. Luego copie y pegue el siguiente código en un archivo de texto sin formato y utilice el mismo procedimiento para eliminar los números principales. Nombre la versión sin los números de línea chip1.pm y muévala al subdirectorio hal.

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;

Lo primero que probablemente notará cuando intente ejecutar el programa con chip1.pm en su lugar es un mensaje de error como el siguiente (énfasis agregado):

$ 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.

Cuando vea un error como el anterior, simplemente use el comando dnf para buscar Fedoraen el repositorio de paquetes para el nombre del paquete del sistema que proporciona el módulo PERL necesario, como se muestra a continuación. Tenga en cuenta que el nombre del módulo y la ruta del mensaje de error anterior tienen el prefijo */ y luego rodeado de comillas simples.

$ 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

Con suerte, encontrará el paquete necesario que luego puede instalar:

$ sudo dnf install perl-Algorithm-Combinatorics

Una vez que haya instalado todos los módulos necesarios, el programa debería funcionar.

Cómo funciona

Esta example es ciertamente bastante artificial. Nada sobre Tic-Tac-Toe es lo suficientemente complejo como para necesitar un módulo CPAN. Para demostrar la instalación y el uso de un módulo no estándar, el programa anterior utiliza el combinaciones rutina de la biblioteca de la Algoritmo::Combinatoria módulo para generar una lista de las posibles combinaciones de tres números del conjunto proporcionado. Debido a que los números de tablero se han asignado a un 3 × 3 cuadrado mágicocualquier conjunto de tres números que sumen 15 se alineará en una columna, fila o diagonal y, por lo tanto, será una combinación ganadora.

Los módulos se importan a un programa con el utilizar y exigir comandos La única diferencia entre ellos es que el comando de uso llama automáticamente a la subrutina de importación (si existe) en el módulo que se está importando. El comando require no llama automáticamente a ninguna subrutina.

Los módulos son solo archivos con un .pm extensión que contiene subrutinas y variables PERL. Comienzan con el paquete manda y termina con 1;. Pero por lo demás, se ven como cualquier otro script PERL. El nombre del archivo debe coincidir con el nombre del paquete. Los nombres de paquetes y archivos distinguen entre mayúsculas y minúsculas.

Tenga en cuenta que cuando lea documentación en línea sobre módulos PERL, la documentación a menudo se desvía hacia temas sobre clases. Las clases se basan en módulos, pero un módulo simple no tiene que cumplir con todas las restricciones que se aplican a las clases. Cuando empiezas a ver palabras como método, herencia y polimorfismo, estás leyendo sobre clases, no sobre módulos.

Hay dos nombres de subrutinas que están reservados para uso especial en módulos. Son import y unimport y son llamados por las directivas use y no respectivamente.

El propósito de las subrutinas de importación y anulación de la importación suele ser crear un alias y eliminar el alias de las subrutinas del módulo dentro y fuera del espacio de nombres de llamada, respectivamente. Para examplela línea 17 de chip1.pm muestra la subrutina sum siendo importada del módulo List::Util.

El módulo constante, como se usa en las líneas 07 de chip1.pm, también modifica el espacio de nombres de la persona que llama (chip1), pero en lugar de importar una subrutina predefinida, crea un tipo especial de variable.

Todos identificadores Inmediatamente después de las palabras clave de uso en los ejemplos anteriores, se encuentran los módulos. En mi sistema, muchos de ellos se pueden encontrar en el directorio /usr/share/perl5.

Tenga en cuenta que el mensaje de error anterior indica “@INC contiene:” seguido de una lista de directorios. C ª es una variable especial de PERL que enumera, en orden, los directorios desde los que deben cargarse los módulos. Se utilizará el primer archivo que se encuentre con un nombre coincidente.

Como se demuestra en la línea 19 del juego Tic-Tac-Toe, el liberación El módulo se puede utilizar para actualizar la lista de directorios en la variable INC.

El módulo chip1 anterior proporciona una example de una subrutina de importación muy simple. En la mayoría de los casos, querrá utilizar la subrutina de importación proporcionada por el Exportador módulo en lugar de implementar el suyo propio. En lo anterior, se utiliza una subrutina de importación personalizada. example para demostrar los conceptos básicos de lo que hace. Además, la implementación personalizada facilita la anulación de las definiciones de subrutinas en ejemplos posteriores.

La subrutina de importación que se muestra arriba revela parte de la magia oculta que hace que los paquetes funcionen. Todas las variables que tienen un alcance global (es decir, creadas fuera de cualquier par de corchetes) y un alcance dinámico (es decir, sin el prefijo de las palabras clave my o state) y todas las subrutinas globales tienen un prefijo automático con un nombre de paquete. El nombre del paquete predeterminado si no se ha emitido ningún comando de paquete es main.

De manera predeterminada, se asume el paquete actual cuando se usa una variable o subrutina no calificada. Cuando se llama a get_move desde el bloque PROMPT en lo anterior example, se asume main::get_move porque el bloque PROMPT existe en el paquete principal. Del mismo modo, cuando se llama a get_moves desde la subrutina get_victor, se asume chip1::get_moves porque get_victor existe en el paquete chip1.

Si desea acceder a una variable o subrutina que existe en un paquete diferente, debe usar su nombre completo o crear un alias local que haga referencia a la subrutina deseada.

La subrutina de importación que se muestra arriba demuestra cómo crear alias de subrutina que se refieren a subrutinas en otros paquetes. En las líneas 73-75, se construyen los nombres totalmente calificados para las subrutinas y luego se tabla de símbolos El nombre de la subrutina en el espacio de nombres de llamada (el paquete en el que se ejecuta la declaración de uso) se le asigna el referencia de la subrutina en el paquete local (el paquete en el que se define la subrutina de importación).

Observe que las subrutinas, como las variables, tienen sigilos. El sigilo para las subrutinas es el símbolo de ampersand (&). En la mayoría de los contextos, el sigilo de las subrutinas es opcional. al trabajar con referencias (como se muestra en las líneas 73-75 de la subrutina de importación) y al verificar si una subrutina está definida (como se muestra en las líneas 57 y 71 del bloque PROMPT), se requiere el sigilo para las subrutinas.

La subrutina de importación que se muestra arriba es solo un mínimo example. Hay mucho que no hace. En particular, una subrutina de importación adecuada no importaría automáticamente ninguna subrutina o variable. Normalmente, se esperaría que el usuario proporcione una lista de las rutinas que se importarán en la línea de uso y esa lista está disponible para la subrutina de importación en la matriz @_.

notas finales

Las líneas 25-27 de chip1.pm proporcionan una buena example del problema de notación densa de PERL. Con solo un par de líneas de código, se pueden determinar los números de tablero en los que se ha colocado una marca determinada. Pero, ¿realiza la declaración dentro de la cláusula condicional del ciclo while la búsqueda desde el comienzo de la variable del juego en cada iteración? ¿O continúa desde donde lo dejó cada vez? PERL adivina correctamente que quiero que proporcione la posición (PS-[0]) de El próximo marca, si existe alguna, en cada iteración. Pero lo que hará exactamente puede ser muy difícil de determinar simplemente mirando el código.

Las últimas cosas a tener en cuenta en los ejemplos anteriores son las directivas estrictas y de advertencia. Habilitan extra tiempo de compilación y tiempo de ejecución depuración mensajes respectivamente. Muchos programadores de PERL recomiendan incluir siempre estas directivas para que sea más probable que se detecten los errores de programación. La desventaja de tenerlos habilitados es que algún código complejo a veces hará que el depurador genere erróneamente resultados no deseados. En consecuencia, es posible que sea necesario deshabilitar las directivas estrictas y/o de advertencia en algunos bloques de código para que su programa se ejecute correctamente, como se demuestra en las líneas 67 y 68 del example módulo chip1. Las directivas de advertencias y estrictas no tienen nada que ver con el programa y se pueden omitir. Su único propósito es proporcionar retroalimentación al desarrollador del programa.

Related Posts