Comencemos imaginando el mundo que nos gustaría existir.
#! /usr/bin/env perl
use strict;
use warnings;
use Convert q(
type1, type2, type3
aax, ert, ddd
asx, eer, kkk
xkk, fff, lll
xxj, vtt, lle
);
Con esa porción de materia delante, debemos ser capaces de llamar a unas cuantas funciones útiles:
use Test::More;
diag type1_to_type2("aax");
diag type1_to_type3("asx");
diag type2_to_type3("fff");
diag type3_to_type1("lle");
Los resultados deben corresponder a lo que hay en la mesa.
my @tests = (
[ qw/ type1_to_type2 aax ert/],
[ qw/ type1_to_type3 asx kkk/],
[ qw/ type2_to_type3 fff lll/],
[ qw/ type3_to_type1 lle xxj/],
[ qw/ type2_to_type1 ert aax/],
);
my %sub_ok;
for (@tests) {
my($name,$from,$expect) = @$_;
my $sub;
{ no strict 'refs';
unless ($sub_ok{$name}++) {
ok defined &$name, "$name defined"
or next;
}
$sub = \&$name;
}
is $sub->($from), $expect, "$name($from)";
}
done_testing;
Para que esto suceda, el módulo Convert necesita tomar una especificación y generar los subs apropiados.
El código en Convert.pm
comienza con una repetición familiar.
According to the perlfunc documentation, use Module LIST
es equivalente a
BEGIN { require Module; Module->import(LIST); }
manera de convertir import
necesidades de tomar la mesa como uno de sus argumentos. (El primero, que ignoramos, es la cadena "Convert"
porque import
se llama como un método de clase.)
sub import {
my(undef,$spec) = @_;
my %map;
my @names;
_populate(\%map, \@names, $spec);
my $pkg = caller;
foreach my $n1 (@names) {
foreach my $n2 (@names) {
next if $n1 eq $n2;
my $sub = sub {
my($preimage) = @_;
return unless exists $map{$n1}{$n2}{$preimage};
$map{$n1}{$n2}{$preimage};
};
my $name = $pkg . "::" . $n1 . "_to_" . $n2;
{ no strict 'refs'; *$name = $sub; }
}
}
}
Con _populate
, se explica a continuación, construimos un hash cuyas claves son
- de -name
- a nombre
- imagen previa
Por ejemplo, la primera fila de datos en la memoria descriptiva (AAX, ert, ddd) corresponde a seis (= P) entradas:
$map{type1}{type2}{aax} = "ert"
$map{type1}{type3}{aax} = "ddd"
$map{type2}{type1}{ert} = "aax"
$map{type2}{type3}{ert} = "ddd"
$map{type3}{type1}{ddd} = "aax"
$map{type3}{type2}{ddd} = "ert"
Tener el hash, que a continuación, instalar submarinos (por ejemplo , type1_to_type2
) en el espacio de nombres de la persona que llama donde cada uno busca su argumento en el espacio apropiado y devuelve el image asignado, si existe.
En _populate
, tomamos los nombres de columna de la primera fila no vacía. Para las filas de datos restantes, cada par de valores entra en el mapa.
sub _populate {
my($map,$names,$spec) = @_;
my $line;
for (split /\s*\n\s*/, $spec) {
++$line;
my @fields = split /\s*,\s*/;
next unless @fields;
if (@$names) {
my %f;
@f{@$names} = @fields;
unless (@fields == @$names) {
warn "$0: line $line: number of fields and columns do not match!\n";
next;
}
foreach my $n1 (@$names) {
foreach my $n2 (@$names) {
next if $n1 eq $n2;
my($f1,$f2) = @f{$n1,$n2};
my $slot = \$map->{$n1}{$n2}{$f1};
warn "$0: line $line: discarding $$slot ($n1 -> $n2)\n"
if defined $$slot;
$$slot = $f2;
}
}
}
else {
@$names = @fields;
}
}
}
No olvide hacer que el módulo devuelva un valor verdadero al final.
1;
Finalmente, la salida!
# ert
# kkk
# lll
# xxj
ok 1 - type1_to_type2 defined
ok 2 - type1_to_type2(aax)
ok 3 - type1_to_type3 defined
ok 4 - type1_to_type3(asx)
ok 5 - type2_to_type3 defined
ok 6 - type2_to_type3(fff)
ok 7 - type3_to_type1 defined
ok 8 - type3_to_type1(lle)
ok 9 - type2_to_type1 defined
ok 10 - type2_to_type1(ert)
1..10
Esta solución también funciona bien, incluso si los datos solo son posicionales (por columna). Por supuesto, el hecho de que no se requieran llamadas de función aquí puede recomendarlo en términos de velocidad, aunque no estoy seguro de eso. –