How to make a method "final" in Perl?
Asked Answered
H

4

6

I was wondering if it is possible to make sure a method in a class I make will NOT be monkey patched (Monkey patch). Can Moose achieve this?

Consider the following:

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

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

Foo::hello()#bye
Henryhenryetta answered 23/2, 2012 at 14:51 Comment(4)
It's pretty well known that Perl often gives you all the rope you want, and it's only convention that prevents you from doing what you shouldn't (e.g. messing with "final" or calling "private" methods). If you can't accept that, then perl is probably not the language for you.Marylouisemaryly
Is there a problem you are trying to solve, or are you just curious?Frenchman
This seems like an alien concept that doesn’t make sense in Perl. What are you actually trying/wanting to do, and why? What’s the real goal?Approximal
Hey people, II think I found a pretty elegant solution which I will post now. Please vote to show me if this is the right answer but I think it is. All your answers were useful in that they made me think on the problemJoselynjoseph
R
5

After a quick web research i found this thread on Perlmonks that states:

As for declaring methods final, I'm not sure how you would do it without doing something really fancy to intercept all additions to the symbol table. (Can that even be done?).

I would also assume that it is impossible.

Using Moose you can apply Method Modifiers that allow you to define functions that must run before a function is called. I have not tried this but maybe you could define a function

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

I don't know exactly how to check it and if it even works, but it looks like it's worth a try!

However I would add, that since perl is an interpreted language anyone who uses your package can view and edit the source, making any precaution circumventable.

Ringer answered 23/2, 2012 at 15:7 Comment(20)
Couldn't someone just wrap another method around your wrapped one?Clavicorn
i don't know if you can overwrite a before statement. The actual method is anonymous and thus can't be overwrittenRinger
Yes @Nick, I understand, that anyone can read the source etc... I just want to warn loudly that since someone tampered the functionality he is on his own now.Joselynjoseph
@Nick, Methods can't be anonymous. In your example, it's named hello.Addiel
I am pretty sure they can be, it says it so right here: perldoc.perl.org/perlsub.html#SYNOPSIS and in the example hello is the original method from the question not mineRinger
Those are anonymous subs. A sub can be anonymous. A sub can be a method. It cannot be both.Dotard
@Nick, Read your own link. It doesn't mention anonymous in anything talking about methods. The hello method is a wrapper that calls your sub and the sub that previously served as the hello method.Addiel
@ikegami, Whoops, my bad! Mixed up method and subs there... you are of course right about the methods.Ringer
@Addiel AHEM! I can invoke an anonymous method call: my $methaddr = sub { ... }; $self−>$methaddr(@args) (;╱{· It has no name.Approximal
@tchrist, Which class is $methaddr associated with? Ah, I see, it's not a method.Addiel
@Berov: "I just want to warn loudly that since someone tampered the functionality he is on his own now." This is a good idea. However, your code isn't the place to do this. Warn them in the docs.Lucrecialucretia
@Addiel => perl seems to disagree with you, run perl -MO=Concise -e '$x->$y' and see for yourself. That this is documented in perlobj under Method Invocation also helps Tom's case.Rhizomorphous
@Eric Strom, I didn't say $self−>$methaddr(@args) wasn't a method call. I said the anon sub is a not a method. -MO=Concise does not contradict me.Addiel
@Addiel => No sub is ever exclusively a method, be it named or anonymous. It becomes a method when called as one. And $self->${\sub {...}} is most certainly an anonymous sub being called as a method.Rhizomorphous
@Eric Strom, It's not called as method. No class is referenced. No object is involved. No inheritance comes into play. (Take a look at $foo=sub{}; undef->$foo()) You might be using the syntax of a method call, but that doesn't mean it's called as a method, and it definitely doesn't make it a method. You really should look up the definition of a method. By the way, before doesn't call the function using that syntax. It simply uses $code_ref->(@_).Addiel
@Addiel => First you wrote I didn't say $self−>$methaddr(@args) wasn't a method call. and then you said It's not called as method. You seem to need to make up your mind...Rhizomorphous
@Eric Strom, Then they're obviously not mutually exclusive.Addiel
@Addiel => a sub is called as a method if called from the method or method_named ops. It does not matter how method or method_named determine which sub to call, it is still called as a method. Feel free to petition p5p to change the name of the method op if you wish, but till then, method is method.Rhizomorphous
@Eric Strom, Again, you really need to look the definition of method. That's not a method call at all. You describing the "method" op, not a method call. Petition what? There's nothing to change as far as I know.Addiel
Semantics and pedantics. I hereby declare that all anonymous subs are associated with the UNIVERSAL class, and therefore you can use any anonymous sub as a method for any object. Now stop it before I invoke Godwin.Marylouisemaryly
R
5

Perl doesn't really like the concept of final subroutines, but you can try. Given the following:

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

You could then write:

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

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

Foo::hello();

Which will print something like:

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

The warning is printed right before the redefined subroutine is first called, not when it is actually redefined (due to limitations of the way perl and Variable::Magic work). But it is better than nothing.

The no warnings; is in there since perl will normally throw a warning when subroutines are redefined. So maybe telling your users to use warnings is good enough. As Larry has said:

Perl doesn't have an infatuation with enforced privacy. It would prefer that you stayed out of its living room because you weren't invited, not because it has a shotgun.

Rhizomorphous answered 23/2, 2012 at 23:32 Comment(1)
I feel compelled to vote your answer up as you're went the extra mile, even if such a thing is completely against Perl's nature.Clavicorn
H
2

Here is my solution to disable Monkey Patching as I wrote in the comment to my question.

#./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!!!'

The result is that ./final_test.pl dies before printing "still living!!!". Yes this makes all the methods "un-patchable", but still allows the module to be inherited/extended. And yes user of the module can always change its sours or say "no warnings":) But still we said loudly "You are not invited!"

Maybe the question title needed to be "How to disable Monkey Patching in Perl?"... Maybe with more reading of perlexwarn we could implement even a final feature...

Henryhenryetta answered 24/2, 2012 at 12:5 Comment(2)
This is certainly a way to do it. You should be aware of the other ways it can be bypassed: {use FooFinal;}, use FooFinal ();, require FooFinal;, BEGIN {*FooFinal::hello = *any_other_glob}, and others. As I said in my answer, perl really doesn't like final :)Rhizomorphous
Yes, agree. The point is that the developer must do something explicitly. And doing so he is aware that he is on his own. In JAVA I could "monkey-patch" a class by copying it in an new file,replace the method I want to replace and put the file further in the $CLASSPATH.Joselynjoseph
C
2

Fataling the redefine warnings will give you what you want. But it will not enable the optimizations a language implementor will want to be able to optimize finalized method calls, such as indexing or inlining.

Since p5p refuses to think about such optimization possibilities (perl6 and p2 does it), this is not practical. But you simply need to readonly the package hash %Foo:: and its keys. Method redefinitions will throw compile-time errors then, and the compiler can optimize the method calls.

I have a branch on github which implements const on the language level, for lexicals and packages. https://github.com/rurban/perl/blob/typed/const/pod/perltypes.pod#const

This was the plan, but there was no support for it: http://blogs.perl.org/users/rurban/2012/09/my-perl5-todo-list.html so I forked perl5. http://perl11.org/p2/

Cite answered 31/1, 2013 at 15:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.