[% setvar title Objects : Core support for method delegation %]

This file is part of the Perl 6 Archive

Note: these documents may be out of date. Do not use as reference!

To see what is currently happening visit http://www.perl6.org/

TITLE

Objects : Core support for method delegation

VERSION

  Maintainer: Damian Conway <damian@conway.org>
  Date: 4 Sep 2000
  Last Modified: 25 Sep 2000
  Mailing List: perl6-language-objects@perl.org
  Number: 193
  Version: 2
  Status: Frozen

ABSTRACT

This RFC proposes that Perl 6 offer built-in support (via a pragma) for delegating method calls to attributes of an object.

DESCRIPTION

Delegation of method calls to attributes is a powerful OO technique that is not well supported in most OO-capable languages, including Perl 5.

Delegation offers most of the advantages of inheritance (and, more particularly, multiple inheritance) without most of the headaches. It also offers some extra features that inheritance cannot provide.

The proposed delegation mechanism would work via a pragma:

	use delegation
		attr1 => [qw( method1 method2 method3 )],
		attr2 => [qw( method4 method5 )],
		attr3 => __ALL__,
		attr4 => __ALL__,
		# etc.
		;

This would cause method calls whose names match an element in the first list to be delegated to the "attr1" attribute of an object. Likewise, calls to a method whose name appears in the second list would be forwarded to the "attr2" attribute of the object.

That is, calls like:

        $obj->method3(@args);
        $obj->method5(@other_args);

would act as if they were:

        $obj->{attr1}->method3(@args);
        $obj->{attr2}->method5(@other_args);

(and, if these attribute objects also delegated, the process might repeat recursively until some deeply nested attribute actually provided a method to call).

Attributes which appear with the string "__ALL__" instead of a method list become "catch-alls". Unresolvable method calls are delegated to the first of these that is able to handle it.

So, for example, a call like:

        $obj->method6(@more_args);

would become equivalent to:

        $obj->{attr3}->method6(@more_args);

if $obj->{attr3} had a method6 method (or an AUTOLOAD), or else:

        $obj->{attr4}->method6(@more_args);

if $obj->{attr4} had a suitable method.

Unlike explicitly delegated methods, which are delegated on the first method look-up pass, delegation to catch-alls occurs on the second pass, just before the dispatch mechanism tries the package's AUTOLOAD method.

Note that the presence of one or more catch-all's does not prevent an AUTOLOAD being called, if none of the catch-alls can handle the requested method.

If the explicit methods, the catch-alls (and any AUTOLOAD) all fail to provide a suitable method, the normal dispatch would then continue into the object's ancestral classes (if any).

An attribute can appear several times in a use delegation statement, with all its delegation method lists being consolidated.

An attribute may also appear both with an explicit delegation list and as a catch-all. For example:

        use delegation
                attr1 => [qw(method1 method2)],
                attr2 => [qw(method3 method4)],
                attr1 => __ALL__,
                ;

This example specifies that calls to the methods method1 and method2 should be delegated to the "attr1" attribute, calls to the methods method3 and method4 should be delegated to the "attr2" attribute, and any remaining calls that are not handled before the AUTOLOAD pass should be delegated to the "attr1" attribute (if it can handle them).

New dispatch sequence

With delegation available, the method dispatch sequence becomes (changes from Perl 5 mechanism in italics):

Using delegation instead of inheritance

One powerful application of delegation is as a replacement for inheritance where the internals of a prospective base class are inaccessible or inconvenient, or the base class was not designed to be inherited and yet it must be.

For example, consider the task of creating an IO::File-like class that reads and writes to separate handles:

        use IO::Bi;

        my $handle = IO::Bi->new('infile', 'outfile');

        if (defined($_ = $handle->getline)) {
                $handle->print($_);
        }

        foreach ($handle->getlines) {
                $handle->print($_);
        }

IO::Bi can't inherit from IO::File, because it needs two file handles, with input methods going to one and output methods going to the other. That's impossible with inheritance (even using the dreaded "diamond inheritance pattern") because a class can inherit the state of any ancestral class only once, no matter how many paths that ancestor is inherited through. In C++ terms, all inheritance in Perl is "virtual".

With delegation, the solution is trivial:

        package IO::Bi;
        use IO::File;

        sub new {
                my ($class, $infile, $outfile) = @_;
                bless {
                        in  => IO::File->new($infile) || die,
                        out => IO::File->new("> $outfile") || die,
                }, $class;
        }

        use delegation
                in  => [qw( getline getlines getc ungetc eof read sysread
                           input_record_separator input_line_number )],
                out => __ALL__,
                ;

        sub error {
                my ($self) = @_;
                return $self->{in}->error || $self->{out}->error;
        }

        sub opened {
                my ($self) = @_;
                return $self->{in}->opened && $self->{out}->opened;
        }

Here, all the input-specific methods are passed to the filehandle stored in the "in" attribute. Everything else goes to the "out" attribute's filehandle.

Note that the class can still define explicit methods like error and opened, to catch cases where the method should be delegated to both attributes.

Replacing multiple inheritance

Another situation where delegation is useful is where it is necessary to inherit from two classes that are both implemented via pseudohashes. Multiple inheritance of pseudohashes is impossible (for good reasons), but delegation gives almost the same effect:

        package PseudoMI;

        use fields qw(ancestor1 ancestor2);
        use delegation (ancestor1 => __ALL__, ancestor2 => __ALL__);

        sub new {
                my PseudoMI $self = fields::new($_[0]);
                $self->{ancestor1} = PseudoBase1->new();
                $self->{ancestor2} = PseudoBase2->new();
                return $self;
        }

Delegation to avoid inheritance

Delegation is also powerful where inheritance is possible, but inconvenient because the base class is not designed to be inherited, or inheritance semantics (such as automagic base class dtor calls) are undesirable:

        package File::Lock::Mac;
        use File::Lock;
        use delegation  uninherited_but_used => __ALL__;

        sub new {
                my $class = shift @_;
                bless { uninherited_but_used => File::Lock->new(@_) }, $class;
        }

        use Mac::Files;

        sub lock {
                my ($self) = @_;
                Mac::Files::FSpSetFLock($self->filename);
        }

        sub DESTROY {
                my ($self) = @_;
                Mac::Files::FSpRstFLock($self->filename);
        }

Delegation for sanitization

Delegation also provides a mechanism for upgrading legacy code to take advantage of new OO features. Consider the encapsulated (private) hash entries proposed by RFC 188. These provide a simple mechanism for strong encapsulation but are only applicable to hashes.

But, with delegation, a legacy base class could be integrated into a Perl 6 class that is based on encapsulated hashes, regardless of how the legacy class itself is implemented.

Consider extending a Person class (with objects implemented as, say, blessed subroutines) with dog-tag information (to be implemented using privatized hashes):

        package DogTag;
        use Person;

        sub new {
                my ($class, $name, $rank, $snum) = @_;
                bless private {
                        name => Person->new($name),
                        rank => $rank,
                        snum => $snum,
                }, $class;
        }

        sub rank {
                my ($self, $newval) = @_;
                $self->{rank} = $newval if @_ > 1;
                return $self->{rank};
        }

        sub serial_num {
                my ($self, $newval) = @_;
                $self->{snum} = $newval if @_ > 1;
                return $self->{snum};
        }

        use delegation  name => __ALL__;

Note that the DogTag class is completely insulated from the implementation details of the Person class. The above code would be identical if Person objects were hashes, arrays, pseudohashes, flyweight scalars, blessed regexes or typeglobs, or even some arcane XS-generated C struct.

Yet from outside the DogTag class, the "name" data is encapsulated in exactly the same way as the rank and serial number information. And the methods of the Person class are directly usable on DogTag objects, just as if they had been inherited.

MIGRATION ISSUES

It is envisaged that delegation -- particularly the "sanitization" technique shown in the last example above -- would provide an important bridging mechanism for migrating existing Perl 5 OO class hierarchies to the new features and functionalities of Perl 6.

Of itself, the pragma would break no existing code.

IMPLEMENTATION

A module named Class::Delegation will soon appear on the CPAN.

It implements almost this entire proposal, except that it is forced to use $self->SUPER::AUTOLOAD(), rather than $self->NEXT::AUTOLOAD(), in cases where delegation fails. This prevents the module from achieving the full transparency that the pragma would offer.

The module's delegation speed is also very slow compared with what an built-in pragma would allow the interpreter to achieve.

REFERENCES

RFC 137: Overview : Perl OO should not be fundamentally changed.

RFC 188: Objects : Private keys and methods

RFC 189: Objects : Hierarchical calls to initializers and destructors

RFC 190: Objects : NEXT pseudoclass for method redispatch

Sean M. Burke's Class::Classless module.

The Self progamming language <www.objs.com>

The Objective-C progamming language

The Cecil progamming language

The Scheme progamming language

The Smalltalk-80 progamming language

www-xdiv.lanl.gov#S-3.2

www.cbuildermag.com

Lynn Andea Stein, "Delegation is Inhertance", Proc. OOPSLA '87, Orlando, FL, October 4-8, 1977, pp. 138-146