2012-02-23 12 views
5

Me preguntaba si es posible asegurarme de que un método en una clase que fabrique NO será parcheado (Monkey patch). ¿Puede Moose lograr esto?¿Cómo hacer un método "final" en Perl?

considerar lo siguiente:

{ 
    package Foo; 
    sub hello{print "HI"} 
1; 
} 

package main; 
sub Foo::hello {print "bye"} 

Foo::hello()#bye 
+4

Es bien sabido que Perl a menudo te ofrece toda la cuerda que deseas, y es solo una convención lo que te impide hacer lo que no debes hacer (por ejemplo, jugar con métodos "finales" o "privados"). Si no puede aceptar eso, entonces Perl probablemente no sea el idioma para usted. – runrig

+1

¿Hay algún problema que intente resolver o solo tiene curiosidad? –

+1

Esto parece un concepto ajeno que no tiene sentido en Perl. ¿Qué intentas/quieres hacer en realidad y por qué? ¿Cuál es el verdadero objetivo? – tchrist

Respuesta

4

Después de una investigación de la tela rápida que encontré this hilo en Perlmonks que dice:

En cuanto a declarar métodos final, no estoy seguro de cómo lo haría sin hacer algo realmente elegante para interceptar todas las adiciones a la tabla de símbolos. (¿Se puede hacer eso?).

También supongo que es imposible.

Al usar Moose puede aplicar Method Modifiers que le permiten definir las funciones que deben ejecutarse antes de que se llame a una función. No he probado esto, pero tal vez usted podría definir una función

before "hello" => sub{ # check if hello has been tampered with 
        } 

No sé exactamente cómo comprobar y si es que funciona, pero parece que vale la pena intentarlo!

Sin embargo, agregaría que dado que Perl es un lenguaje interpretado, cualquiera que use su paquete puede ver y editar la fuente, evitando cualquier precaución.

+0

¿No podría alguien simplemente ajustar otro método alrededor de su envoltura? – rjh

+0

no sé si puede sobrescribir una instrucción 'before'. El método real es anónimo y, por lo tanto, no se puede sobrescribir – Nick

+0

Sí @Nick, entiendo, que cualquiera pueda leer la fuente, etc ... Solo quiero advertir en voz alta que, dado que alguien manipuló la funcionalidad, ahora está solo. –

3

Perl realmente no le gusta el concepto de subrutinas final, pero puede intentarlo. Teniendo en cuenta lo siguiente:

BEGIN { 
    package final; 
    $INC{'final.pm'}++; 
    use Variable::Magic qw(wizard cast); 
    sub import { 
     my (undef, $symbol) = @_; 
     my ($stash, $name) = $symbol =~ /(.+::)(.+)/; 
     unless ($stash) { 
      $stash = caller().'::'; 
      $name = $symbol; 
      $symbol = $stash.$name; 
     } 
     no strict 'refs'; 
     my $glob = \*$symbol; 
     my $code = \&$glob; 
     my ($seen, @last); 

     cast %$stash, wizard store => sub { 
      if ($_[2] eq $name and $code != \&$glob) { 
       print "final subroutine $symbol was redefined ". 
         "at $last[1] line $last[2].\n" unless $seen++ 
      } 
      @last = caller 
     } 
    } 
} 

A continuación, podría escribir:

use warnings; 
use strict; 
{ 
    package Foo; 
    sub hello {print "HI"} 
    use final 'hello'; 
} 

package main; 
no warnings; 
sub Foo::hello {print "bye"} 

Foo::hello(); 

que imprimirá algo como:

 
final subroutine Foo::hello was redefined at filename.pl line 9. 
bye 

La advertencia está impreso justo antes de la subrutina redefinido se llama en primer lugar, no se cuando en realidad se redefine (debido a las limitaciones del trabajo de perl y Variable::Magic). Pero es mejor que nada.

no warnings; está ahí porque perl normalmente lanzará una advertencia cuando se redefinan las subrutinas. Así que tal vez decirle a tus usuarios que use warnings es lo suficientemente bueno. Como Larry ha dicho:

Perl no tiene un enamoramiento con la privacidad impuesta. Sería preferiría que se quedara fuera de su sala de estar porque no estaba invitado, no porque tenga una escopeta.

+2

+1 para Larry-quote. – tchrist

+0

Me siento obligado a votar su respuesta ya que ha hecho un esfuerzo adicional, incluso si tal cosa es completamente contraria a la naturaleza de Perl. – rjh

1

Aquí es mi solución a mono desactivar parches como he escrito en el comentario a mi pregunta.

#./FooFinal.pm 
package FooFinal; 
use strict; 
use warnings; 
sub import { warnings->import(FATAL => qw(redefine)); } 
sub hello { print "HI" } 
1; 

#./final_test.pl 
#!/usr/bin/env perl 
use strict; 
use warnings; 
use FooFinal; 
sub FooFinal::hello {print "bye".$/} 

FooFinal->hello();#bye 

print 'still living!!!' 

El resultado es que ./final_test.pl muere antes de imprimir "still living !!!". Sí, esto hace que todos los métodos sean "incompatibles", pero aún permite que el módulo se herede/extienda. Y sí, el usuario del módulo siempre puede cambiar de color o decir "sin advertencias" :) Pero aún así dijimos en voz alta "¡No estás invitado!"

Tal vez el título de la pregunta tenía que ser "Cómo deshabilitar Parches Mono en Perl?" ... Tal vez con más lectura de perlexwarn podríamos aplicar incluso una última característica ...

+0

Esto es ciertamente una manera de hacerlo. Debe tener en cuenta las otras formas en que puede pasar por alto: '{use FooFinal;}', 'use FooFinal();', 'requiera FooFinal;', 'BEGIN {* FooFinal :: hello = * any_other_glob}', y otros. Como dije en mi respuesta, a Perl realmente no le gusta la final :) –

+0

Sí, de acuerdo. El punto es que el desarrollador debe hacer algo explícitamente. Y al hacerlo, es consciente de que está solo. En JAVA pude "aplicar parche" a una clase copiándola en un archivo nuevo, reemplazar el método que deseo reemplazar y poner el archivo aún más en $ CLASSPATH. –

1

Fataling las advertencias se Redefinir darte lo que quieres Pero no habilitará las optimizaciones que un implementador de lenguaje deseará para poder optimizar las llamadas a métodos finalizados, como la indexación o la alineación.

Dado que p5p se niega a pensar en tales posibilidades de optimización (perl6 y p2 lo hace), esto no es práctico. Pero solo necesita leer solo el paquete hash %Foo:: y sus claves. Las redefiniciones del método arrojarán errores en tiempo de compilación, y el compilador puede optimizar las llamadas al método.

Tengo una rama en github que implementa const en el nivel de idioma, para léxicos y paquetes. https://github.com/rurban/perl/blob/typed/const/pod/perltypes.pod#const

Este era el plan, pero no hubo apoyo para ella: http://blogs.perl.org/users/rurban/2012/09/my-perl5-todo-list.html así que en horquilla perl5. http://perl11.org/p2/

Cuestiones relacionadas