Como faço para acessar uma constante em Perl cujo nome está contido em uma variável?
-
25-09-2019 - |
Pergunta
Eu tenho um conjunto de constantes declaradas em Perl:
use constant C1 => 111;
use constant C2 => 222;
..
use constant C9 => 999;
my $which_constant = "C2";
Como faço para construir uma expressão perl que, com base em $which_constant
, deriva o valor de uma constante nomeada com o valor dessa variável - por exemplo, "222".
Observe que não posso alterar nenhuma das condições acima - elas são uma simplificação de um cenário real: tenho um módulo (sobre o qual não tenho controle) sobre o qual essas constantes são importadas. O `nome de uma das constantes é fornecido pelo usuário na linha de comando. Preciso acessar o valor das constantes apropriadas.
Eu estive batendo na cabeça contra a parede (principalmente em torno de todos os tipos de construções estranhas do Glob), mas nenhum deles funciona.
PS se a solução acessar as constantes dentro de seu módulo nativo - digamos My::Constants::C2
(sem precisar importá -los), melhor, mas não necessário - posso importar as constantes corretas para main::
usando facilmente My::Constants->import($which_constant)
. E sim, para finalizar, as constantes de TE não são exportadas por padrão, precisando assim da chamada de importação explícita ().
Algumas das coisas que tentei:
main::$which_constant
- erro de sintaxemain::${which_constant}
- erro de sintaxe${*$which_constant}
- retorna valor vazio*$which_constant
- Retorna "*main :: c2"${*${*which_constant}}
- vazio
Solução
Constantes definidas por constant.pm
são apenas sub -rotinas. Você pode usar a sintaxe da invocação do método se tiver o nome da constante em uma string:
#!/usr/bin/perl -l
use strict; use warnings;
use constant C1 => 111;
use constant C2 => 222;
print __PACKAGE__->$_ for qw( C1 C2 );
# or print main->$_ for qw( C1 C2 );
Dessa forma, se você tentar usar uma constante que não estiver definida, você receberá um erro.
Outras dicas
As "constantes" Perl são realmente sub -rotinas que retornam um valor constante. O compilador Perl é capaz de substituí -los pelo valor apropriado no momento da compilação. No entanto, como você deseja obter o valor com base em uma pesquisa de nomes de tempo de execução, você deve fazer:
&{$which_constant}();
(E claro que você precisa no strict 'refs'
em algum lugar.)
A sugestão de Sinan de usar a semântica da invocação de métodos para se locomover strict 'refs'
Os limites são a solução mais limpa e fácil de ler.
Minha única preocupação com isso foi que a penalidade de velocidade para usar essa abordagem pode ser um problema. Todos nós já ouvimos falar sobre as penalidades de desempenho do método e os benefícios de velocidade das funções inlinesáveis.
Por isso, decidi executar um benchmark (código e resultados a seguir).
Os resultados mostram que constantes normais e inlinadas são executadas duas vezes mais rápidas que as chamadas de método com um nome de sub -rotina literal e quase três vezes mais rápido que o método chama com nomes de sub -rotinas variáveis. A abordagem mais lenta é uma deref padrão e invocação de no strict "refs";
.
Mas, mesmo a abordagem mais lenta é bastante rápida em mais de 1,4 milhão de vezes um segundo no meu sistema.
Esses benchmarks obliteram minha única reserva sobre o uso da abordagem de chamada de método para resolver esse problema.
use strict;
use warnings;
use Benchmark qw(cmpthese);
my $class = 'MyConstant';
my $name = 'VALUE';
my $full_name = $class.'::'.$name;
cmpthese( 10_000_000, {
'Normal' => \&normal_constant,
'Deref' => \&direct_deref,
'Deref_Amp' => \&direct_deref_with_amp,
'Lit_P_Lit_N' => \&method_lit_pkg_lit_name,
'Lit_P_Var_N' => \&method_lit_pkg_var_name,
'Var_P_Lit_N' => \&method_var_pkg_lit_name,
'Var_P_Var_N' => \&method_var_pkg_var_name,
});
sub method_lit_pkg_lit_name {
return 7 + MyConstant->VALUE;
}
sub method_lit_pkg_var_name {
return 7 + MyConstant->$name;
}
sub method_var_pkg_lit_name {
return 7 + $class->VALUE;
}
sub method_var_pkg_var_name {
return 7 + $class->$name;
}
sub direct_deref {
no strict 'refs';
return 7 + $full_name->();
}
sub direct_deref_with_amp {
no strict 'refs';
return 7 + &$full_name;
}
sub normal_constant {
return 7 + MyConstant::VALUE();
}
BEGIN {
package MyConstant;
use constant VALUE => 32;
}
E os resultados:
Rate Deref_Amp Deref Var_P_Var_N Lit_P_Var_N Lit_P_Lit_N Var_P_Lit_N Normal
Deref_Amp 1431639/s -- -0% -9% -10% -29% -35% -67%
Deref 1438435/s 0% -- -9% -10% -28% -35% -67%
Var_P_Var_N 1572574/s 10% 9% -- -1% -22% -29% -64%
Lit_P_Var_N 1592103/s 11% 11% 1% -- -21% -28% -63%
Lit_P_Lit_N 2006421/s 40% 39% 28% 26% -- -9% -54%
Var_P_Lit_N 2214349/s 55% 54% 41% 39% 10% -- -49%
Normal 4353505/s 204% 203% 177% 173% 117% 97% --
Resultados gerados com o ActivePerl 826 no Windows XP, YMMV.