Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a method "final" in Perl?

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
like image 316
Беров Avatar asked Feb 23 '12 14:02

Беров


People also ask

How do you make a final method?

You use the final keyword in a method declaration to indicate that the method cannot be overridden by subclasses. The Object class does this—a number of its methods are final .

Can we make method as final?

We can declare a method as final, once you declare a method final it cannot be overridden. So, you cannot modify a final method from a sub class. The main intention of making a method final would be that the content of the method should not be changed by any outsider.

Can final class have non final methods?

All methods in a final class are implicitly final.

What will happen if we make class and method as final?

If we declare a method as final, then it cannot be overridden by any subclasses. And, if we declare a class as final, we restrict the other classes to inherit or extend it. In other words, the final classes can not be inherited by other classes.


3 Answers

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.

like image 155
NiklasMM Avatar answered Oct 25 '22 11:10

NiklasMM


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.

like image 35
Eric Strom Avatar answered Oct 25 '22 10:10

Eric Strom


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

like image 2
Беров Avatar answered Oct 25 '22 09:10

Беров