¿Por qué los prototipos de funciones de Perl 5 son malos?


116

En otra pregunta de Stack Overflow, Leon Timmermans afirmó:

Te aconsejo que no uses prototipos. Tienen sus usos, pero no en la mayoría de los casos y definitivamente no en este.

¿Por qué podría ser esto cierto (o no)? Casi siempre proporciono prototipos para mis funciones de Perl, y nunca antes había visto a nadie decir nada malo sobre su uso.


Yo también tengo curiosidad. La única vez que no los uso es cuando llamo con un número variable de argumentos.
Paul Tomblin

7
¿Puedo recomendarle que lea el artículo "Los prototipos de Perl se consideran perjudiciales" ?
tchrist

Respuestas:


121

Los prototipos no son malos si se usan correctamente. La dificultad es que los prototipos de Perl no funcionan como la gente espera que lo hagan. Las personas con experiencia en otros lenguajes de programación tienden a esperar que los prototipos proporcionen un mecanismo para verificar que las llamadas a funciones sean correctas: es decir, que tengan el número y tipo de argumentos correctos. Los prototipos de Perl no son adecuados para esta tarea. Es el mal uso que es malo. Los prototipos de Perl tienen un propósito singular y muy diferente:

Los prototipos le permiten definir funciones que se comportan como funciones integradas.

  • Los paréntesis son opcionales.
  • El contexto se impone a los argumentos.

Por ejemplo, podría definir una función como esta:

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

y llámalo como

mypush @array, 1, 2, 3;

sin necesidad de escribir el \para tomar una referencia a la matriz.

En pocas palabras, los prototipos le permiten crear su propio azúcar sintáctico. Por ejemplo, el marco de Moose los usa para emular una sintaxis OO más típica.

Esto es muy útil pero los prototipos son muy limitados:

  • Deben ser visibles en tiempo de compilación.
  • Se pueden omitir.
  • La propagación del contexto a los argumentos puede provocar un comportamiento inesperado.
  • Pueden dificultar la llamada de funciones utilizando cualquier otra forma que no sea la estrictamente prescrita.

Vea Prototipos en perlsub para todos los detalles sangrientos.


2
Acepté esta respuesta porque creo que responde mejor a la pregunta: los prototipos no son intrínsecamente malos, es solo la forma en que los usa.
Alnitak

2
Por otro lado, los prototipos de alce son / awesome / p3rl.org/MooseX::Declare p3rl.org/MooseX::Method::Signatures
Kent Fredric


Entonces, ¿son un nombre inapropiado?
Peter Mortensen

69

El problema es que los prototipos de funciones de Perl no hacen lo que la gente cree que hacen. Su propósito es permitirle escribir funciones que se analizarán como las funciones integradas de Perl.

En primer lugar, las llamadas a métodos ignoran por completo los prototipos. Si está haciendo programación OO, no importa qué prototipo tengan sus métodos. (Por lo que no deberían tener ningún prototipo).

En segundo lugar, los prototipos no se aplican estrictamente. Si llama a una subrutina con &function(...), el prototipo se ignora. Por lo que realmente no brindan ningún tipo de seguridad.

En tercer lugar, son acciones espeluznantes a distancia. (Especialmente el $prototipo, que hace que el parámetro correspondiente se evalúe en contexto escalar, en lugar del contexto de lista predeterminado).

En particular, dificultan el paso de parámetros desde matrices. Por ejemplo:

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]);

huellas dactilares:

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

junto con 3 advertencias sobre main::foo() called too early to check prototype(si las advertencias están habilitadas). El problema es que una matriz (o porción de matriz) evaluada en contexto escalar devuelve la longitud de la matriz.

Si necesita escribir una función que actúe como una incorporada, use un prototipo. De lo contrario, no utilice prototipos.

Nota: Perl 6 tendrá prototipos completamente renovados y muy útiles. Esta respuesta se aplica solo a Perl 5.


Pero aún proporcionan una verificación útil de que la persona que llama y el suscriptor están usando la misma cantidad de argumentos, entonces, ¿qué hay de malo en eso?
Paul Tomblin

2
No; el consenso general es que los prototipos de funciones de Perl no proporcionan esencialmente ningún beneficio. Es mejor que no se moleste con ellos, al menos en Perl 5. Perl 6 podría ser una historia diferente (mejor).
Jonathan Leffler

5
Hay mejores formas de validar argumentos, como el módulo Params
friedo

10
Corrección: la división de matriz devuelve una lista , por lo que una división de matriz en un contexto escalar devuelve el elemento final de la lista. Su anteúltima invocación de foo()prints 2 porque ese es el elemento final en su segmento de dos elementos. Cambie a my @array = qw(foo bar baz)y verá la diferencia. (Aparte, esta es la razón por la que no inicializo matrices / listas en secuencias numéricas basadas en 0 o 1 en código demostrativo desechable. La confusión entre índices, recuentos y elementos en contextos me ha mordido más de una vez. Tonto pero cierto.)
pilcrow

2
@pilcrow: Edité la respuesta para usarla a b cpara aclarar tu punto.
Flimm

30

Estoy de acuerdo con los dos carteles anteriores. En general, $debe evitarse su uso. Los prototipos son sólo es útil cuando se utiliza argumentos de bloque ( &), pegotes ( *), o prototipos de referencia ( \@, \$, \%, \*)


En general, quizás, pero me gustaría mencionar dos excepciones: primero, el ($)prototipo crea un operador unario con nombre, que puede ser útil (ciertamente Perl los encuentra útiles; yo también, en ocasiones). En segundo lugar, al anular las incorporaciones (ya sea a través de la importación o utilizando CORE :: GLOBAL: :), en general debería ceñirse a cualquier prototipo que tuviera la incorporada, incluso si eso incluye a $, o podría sorprender al programador (usted mismo, incluso) con contexto de lista donde el integrado proporcionaría contexto escalar.
The Sidhekin

4

Algunas personas, al mirar un prototipo de subrutina de Perl, creen que significa algo que no significa:

sub some_sub ($$) { ... }

Para Perl, eso significa que el analizador espera dos argumentos. Es la forma de Perl de permitirle crear subrutinas que se comportan como integradas, todas las cuales saben qué esperar del código subsiguiente. Puedes leer sobre prototipos en perlsub

Sin leer la documentación, la gente adivina que los prototipos se refieren a la verificación de argumentos en tiempo de ejecución o algo similar que han visto en otros lenguajes. Como ocurre con la mayoría de las cosas que la gente adivina sobre Perl, resultan estar equivocadas.

Sin embargo, comenzando con Perl v5.20, Perl tiene una característica, experimental mientras escribo esto, que brinda algo más parecido a lo que los usuarios esperan y qué. Las firmas de subrutinas de Perl ejecutan el recuento de argumentos en tiempo, la asignación de variables y la configuración predeterminada:

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";
    }

Esta es la característica que probablemente desee si está considerando prototipos.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.