Por que os protótipos de funções do Perl 5 são ruins?

116

Em outra pergunta do Stack Overflow, Leon Timmermans afirmou:

Aconselho você a não usar protótipos. Eles têm seus usos, mas não na maioria dos casos e definitivamente não neste.

Por que isso pode ser verdade (ou não)? Quase sempre forneço protótipos para minhas funções Perl, e nunca antes vi alguém dizer algo ruim sobre usá-los.

Alnitak
fonte
Estou curioso também. A única vez que não os uso é quando estou chamando com um número variável de argumentos.
Paul Tomblin
7
Posso recomendar que você leia o artigo “Perl Prototypes Considered Harmful” ?
tchrist

Respostas:

121

Protótipos não são ruins se usados ​​corretamente. A dificuldade é que os protótipos do Perl não funcionam da maneira que as pessoas costumam esperar. Pessoas com experiência em outras linguagens de programação tendem a esperar que os protótipos forneçam um mecanismo para verificar se as chamadas de função estão corretas: isto é, se têm o número e o tipo certo de argumentos. Os protótipos do Perl não são adequados para esta tarefa. É o mau uso que é ruim. Os protótipos do Perl têm um propósito único e muito diferente:

Os protótipos permitem definir funções que se comportam como funções integradas.

  • Os parênteses são opcionais.
  • O contexto é imposto aos argumentos.

Por exemplo, você pode definir uma função como esta:

sub mypush(\@@) { ... }

e chamá-lo de

mypush @array, 1, 2, 3;

sem precisar escrever o \para fazer uma referência ao array.

Em suma, os protótipos permitem que você crie seu próprio açúcar sintático. Por exemplo, a estrutura do Moose os usa para emular uma sintaxe OO mais típica.

Isso é muito útil, mas os protótipos são muito limitados:

  • Eles precisam estar visíveis em tempo de compilação.
  • Eles podem ser contornados.
  • Propagar contexto para argumentos pode causar comportamento inesperado.
  • Eles podem dificultar a chamada de funções usando algo diferente da forma estritamente prescrita.

Veja Protótipos em perlsub para todos os detalhes sangrentos.

Michael Carman
fonte
2
Aceitei essa resposta porque acho que ela responde melhor à pergunta - os protótipos não são intrinsecamente ruins, é apenas como você os usa.
Alnitak
2
Os protótipos de Moose, por outro lado, são / awesome / p3rl.org/MooseX::Declare p3rl.org/MooseX::Method::Signatures
Kent Fredric
Então, eles são um nome impróprio?
Peter Mortensen
69

O problema é que os protótipos de função do Perl não fazem o que as pessoas pensam que fazem. Seu propósito é permitir que você escreva funções que serão analisadas como as funções integradas do Perl.

Em primeiro lugar, as chamadas de método ignoram completamente os protótipos. Se você estiver fazendo programação OO, não importa o protótipo de seus métodos. (Portanto, eles não devem ter nenhum protótipo.)

Em segundo lugar, os protótipos não são estritamente impostos. Se você chamar uma sub-rotina com &function(...), o protótipo será ignorado. Portanto, eles não fornecem nenhum tipo de segurança.

Terceiro, eles são uma ação assustadora à distância. (Especialmente o $protótipo, que faz com que o parâmetro correspondente seja avaliado no contexto escalar, em vez do contexto de lista padrão.)

Em particular, eles dificultam a passagem de parâmetros de arrays. Por exemplo:

my @array = qw(a b c);

foo(@array);
foo(@array[0..1]);
foo($array[0], $array[1], $array[2]);

sub foo ($;$$) { print "@_\n" }

foo(@array);
foo(@array[0..1]);
foo($array[0], $array[1], $array[2]);

estampas:

a b c
a b
a b c
3
b
a b c

junto com 3 avisos sobre main::foo() called too early to check prototype(se os avisos estiverem habilitados). O problema é que uma matriz (ou fatia de matriz) avaliada no contexto escalar retorna o comprimento da matriz.

Se você precisar escrever uma função que atue como embutida, use um protótipo. Caso contrário, não use protótipos.

Nota: Perl 6 terá protótipos completamente renovados e muito úteis. Essa resposta se aplica apenas ao Perl 5.

cjm
fonte
Mas eles ainda fornecem uma verificação útil de que o chamador e a sub-rotina estão usando o mesmo número de argumentos, então o que há de errado nisso?
Paul Tomblin
2
Não; o consenso geral é que os protótipos de função Perl não fornecem essencialmente nenhum benefício. Você também pode não se preocupar com eles, pelo menos em Perl 5. Perl 6 pode ser uma história diferente (melhor).
Jonathan Leffler
5
Existem maneiras melhores de validar argumentos, como o módulo Params :: Validate: search.cpan.org/~drolsky/Params-Validate-0.91/lib/Params/…
friedo
10
Correção: o corte da matriz retorna uma lista , então uma fatia da matriz em um contexto escalar retorna o elemento final da lista. Sua penúltima invocação de foo()impressões 2 porque esse é o elemento final em sua fatia de dois elementos. Mude para my @array = qw(foo bar baz)e você verá a diferença. (À parte, é por isso que eu não inicializo matrizes / listas para sequências numéricas baseadas em 0 ou 1 em código demonstrativo descartável. A confusão entre índices, contagens e elementos em contextos me incomodou mais de uma vez. Bobagem, mas é verdade.)
Pilcrow
2
@pilcrow: Eu editei a resposta para usar a b cpara deixar seu ponto mais claro.
Flimm de
30

Eu concordo com os dois cartazes acima. Em geral, o uso $deve ser evitado. Os protótipos são úteis apenas quando utilizando os argumentos de bloco ( &), (bolhas *) ou protótipos de referência ( \@, \$, \%, \*)

Leon Timmermans
fonte
Em geral, talvez, mas eu gostaria de mencionar duas exceções: primeiro, o ($)protótipo cria um operador unário nomeado, que pode ser útil (certamente Perl os acha úteis; eu também, ocasionalmente). Em segundo lugar, ao substituir os integrados (seja por meio de importação ou usando CORE :: GLOBAL: :), você deve, em geral, se limitar a qualquer protótipo que o integrado tenha, mesmo que inclua um $, ou você pode surpreender o programador (você, mesmo) com contexto de lista em que o integrado forneceria contexto escalar.
The Sidhekin,
4

Algumas pessoas, olhando para um protótipo de sub-rotina Perl, pensam que significa algo que não significa:

sub some_sub ($$) { ... }

Para Perl, isso significa que o analisador espera dois argumentos. É a maneira do Perl permitir que você crie sub-rotinas que se comportam como integradas, todas as quais sabem o que esperar do código subsequente. Você pode ler sobre protótipos em perlsub

Sem ler a documentação, as pessoas adivinham que os protótipos se referem à verificação de argumento em tempo de execução ou algo semelhante que eles viram em outras linguagens. Como acontece com a maioria das coisas que as pessoas imaginam sobre o Perl, elas se revelam erradas.

No entanto, a partir do Perl v5.20, o Perl tem um recurso, experimental no momento em que escrevo isso, que oferece algo mais parecido com o que os usuários esperam e o quê. As assinaturas de sub-rotina do Perl executam contagem de argumento de tempo, atribuição de variável e configuração padrão:

use v5.20;
use feature qw(signatures);
no warnings qw(experimental::signatures);

animals( 'Buster', 'Nikki', 'Godzilla' );

sub animals ($cat, $dog, $lizard = 'Default reptile') { 
    say "The cat is $cat";
    say "The dog is $dog";
    say "The lizard is $lizard";
    }

Este é o recurso que você provavelmente deseja se estiver considerando protótipos.

brian d foy
fonte