Adding a Method Keyword to Perl 5

| 19 Comments

On p5p, David Golden suggested adding use feature 'method'; as a simple extension to Perl 5 which adds a method keyword to declare a method, then shift off the invocant and make it available within the body of the method.

I wrote a patch to enable method in feature.pm. The patch looks longer than it is. The important user-side example is in the simple test suite I added:

package SomeClass;

use strict;
use warnings;

use feature 'method';

sub new {
    my ($class, $value) = @_;
    bless \$value, $class;
}

method get_value {
    return $$self;
}

method dup_value
{
    return    $$self
    . reverse $$self;
}

method set_value :lvalue
{
    $$self;
}

method ctor
{
    my $value = shift;
    bless \$value, $self;
}

package main;

my $sc = SomeClass->new( 'instance variable' );

can_ok($sc, 'get_value');
can_ok('SomeClass', 'set_value');
is($sc->get_value, 'instance variable', 'simple method should work');
is($sc->dup_value, 'instance variableelbairav ecnatsni',
    'method using additional $self should work');
$sc->set_value = 'foo';
is($sc->get_value, 'foo', ':lvalue attribute should work');

$sc = SomeClass->ctor( 'method constructor' );
is($sc->get_value, 'method constructor',
    'method should work for constructor too');

done_testing();

As you can see, with use feature 'method'; in scope, the method keyword is a synonym for sub which implicitly adds a single line at the start of the body of the method: my $self = shift;.

Making this work meant adding method to the tokenizer as a keyword recognized only in the scope with use feature 'method'; enabled. That explains the patches to lib/feature.pm, keywords.h, perl_keyword.pl, and regen/keywords.pl, as well as the the second half of the changes to toke.c.

The first half of the changes to toke.c are a copy and paste and specialization of the special handling of the sub keyword. I had to do this for two reasons. First, the sub handling always returns a token representing SUB to the parser, and I needed to return something special. I chose the token METH instead. Second, it makes little sense to support prototypes on methods, as Perl 5 cannot resolve prototypes across method dispatch. I removed the portion of the sub tokenizing related to that. (This batch of code isn't an example of screechingly obviousness; it could become cleaner as I see more unnecessary code to remove.)

With all of that in place, the tokenizer provides a stream of tokens to the acutal parser in perly.y. The patch adds an alternation to one production and two supplementary productions.

The alternation to statement processing starts when the parser encounters the METH token. A method starts with meth, runs the special startsubparse production to set up the new subroutine scope, has a name, has an optional attribute list, and then has a method body. The latter is a new production.

A method body has curly braces delimiting it. It then runs a special new production called addimplicitshift (did I mention the phrase "screechingly obvious"?), runs an existing production called remember to start a new lexical scope, then gathers a list of statements, and finally encounters the ending curly brace.

The addimplicitshift production is the most interesting part of this whole process. It uses the core lex_stuff_pvs() function to add a single line of Perl 5 code: my $self = shift;. Perl will parse that line as if it had always been the first line of the body of the method, even though the parser added it itself.

This means that if you wrote:

use feature 'method';

method some_method
{
    my $self = shift;
    ...
}

... you'll get the warning you expect about a redeclaration of a lexical, with the correct the line number.

Does this work well with CPAN modules which declare their own method keywords? It's not incompatible with them, in the sense that the only code in the world which gets any use out of use feature 'method'; right now is the test in my patch. Yet something like MooseX::Declare may (I haven't tested it) be incompatible if and only if you use MooseX::Declare and use feature 'method' in the same scope.

A larger question is whether this is a valuable feature. Certainly it offers two modest advantages, by marking the intent of the developer to distinguish between a method and a function and by removing a line of boilerplate from every method. (Given that most of my methods are between three and ten lines long, saving double-digit percentages of their length is a tremendous savings of code.)

Will this feature get in Perl 5.14? I don't know. I have my doubts—the implementation could be cleaner, the ultimate syntax could change to use something other than my $self = shift;, and there's undoubtedly a long line of people who believe that the only way to prototype a proposed change to the Perl 5 core is to write a CPAN module (at which point a long line of people is happy to say "Why would you want that in core? There's already a CPAN module for it!").

Regardless of all of that, I can think of a fair few advantages to doing something like this in the core. I'll explain those tomorrow.

19 Comments

I think the worst offender in "weird syntax", "new comers unfriendly" and "boilerplate" is rather the new constructor.

and there's undoubtedly a long line of people who believe that the only way to prototype a proposed change to the Perl 5 core is to write a CPAN module

To which I would reply "you mean like the 7 modules on CPAN that already implement some super/subset o this behavior?" ... in fact I think I just did make that reply.

Hi chromatic

Ok, so you want to hide the line:
my $self = shift;
but it's going to confuse beginners. This makes me very sad.

The trouble is that it creates a type of action-at-a-distance, hence violating The Principle of Least Surprise, which in turn worsens Perl's reputation for being obscurantistic.

Cheers
Ron

Hi chromatic

After submitting, I get a confirmation page, which is good. But I can't see a logout button. Is this my problem (if, so, apologies), or a Chrome display problem, or the designer's?

Cheers
Ron

Ok, since we'll have "method", how about "class" also? (Note that Ruby has "class" but no "method").

I think you should call it `meth` not `method` it's much shorter, I don't want to type method ... :P

Ignore Caleb, everyone knows he just wants to make us all meth addicts. =P

Just a minor nit -- it certainly won't make it in for 5.14 as the "contentious code change" freeze point has past. But I hope the discussion will continue to see if it could be a part of 5.16 in 2012.

@stevenharyanto, what do you think "class" should do that "package" does not? Also, note that in 5.14, you can write this:

package Foo 1.23 { ... }

Anything inside the block is in the "Foo" namespace.

What do you think about second or after argument processing like MX::Declare?

  method ctor($value) {
      bless \$value, $self;
  }

is more pretty.

Forgive me if I'm missing something, I'm quite tired at the moment so my brain may not be working at full capacity. But is there any reason the new core keyword couldn't be implemented in a way so that extension could, for example, set $^H{OVERRIDE_KEYWORD}{method} = __PACKAGE__ (or something along those lines) to tell perl to deactivate parsing of the method keyword? This way I could do the following:

    package MyClass;
    use 5.16.0;            # activates core method { }
    use MyMethodProvider;  # deactivates core method, inserts own handler
    ...

This would also make it possible to have a default method keyword and only use extended (and maybe more costly) syntax in a smaller scope.

Having recently adopted Method::Signatures::Simple, I am now completely addicted to the tedium-reducing method keyword and would be happy to see anything to increase its adoption in the Perl world, including this patch.

I posted a patch once to add a class keyword. I have no intention of resubmitting it.

I like that, but first things first. Getting a new keyword (even one enabled by feature) is difficult enough without doing additional work.

no feature 'method'; should suffice.

@dagolden: I think chromatic explains it pretty nicely (although the case is on "method"): http://www.modernperlbooks.com/mt/2011/01/what-else-a-method-keyword-could-do.html

TL;DR an explicit "class" keyword allows us to express the intention of creating a class instead of just a package, and other things can follow from that.

Instead of using the my $self = shift; as the method boilerplate, wouldn't $_ be less surprising? Since $_ is the default variable in most places, why not using it in method too?

I know that $_ won't have lexical scope as $self would if your patch was used, but this doesn't seem to be a problem in other uses of $_ and one can just add a my $self = $_; boilerplate on the first line of the method.

Just wondering whether i missed something...

That's an interesting idea. I'll think more on it.

My initial impression is mild dislike, though there is some prior art in Perl 6's implicit shortcut for attribute access (and the semi-magical method binding in languages such as Java).

The best argument for $self is the prior art in almost all Perl 5 OO code I've ever read.

Alex,

Would this invalidate the use of $_ within the method or at least make it *very* confusing? Think of the use of $_ in grep or map.

C.

Modern Perl: The Book

cover image for Modern Perl: the book

The best Perl Programmers read Modern Perl: The Book.

affiliated with ModernPerl.net

Categories

Pages

About this Entry

This page contains a single entry by chromatic published on January 20, 2011 10:11 AM.

How to Identify a Good Perl Programmer was the previous entry in this blog.

What Else a Method Keyword Could Do is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.


Sponsored by Blender Recipe Reviews and the Trendshare how to invest guide

Powered by the Perl programming language

what is programming?