[% setvar title Objects : Private keys and methods %]

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 : Private keys and methods

VERSION

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

ABSTRACT

This RFC proposes two new keywords -- private and public -- that limit the accessibility of keys in a hash, and of methods. Their primary use would be to provide encapsulation of attributes and methods in hash-based objects.

DESCRIPTION

Private hash entries

It is proposed that Perl 6 provide a unary function, private, that restricts hash entries to the current package. The keyword could be applied to a single hash entry:

        private $hash{key};
        private $hash{$key};
        private $hashref->{key};

or to a hash slice:

        private @hash{qw(_name _rank _snum)};

or to a complete hash (either directly, or via a reference):

        private %hash;
        private { _name => "demo", _rank => "private", _snum => 123 };
        private bless { @attrs }, $class;
        bless private { @attrs }, $class;

In all cases, a call to private would return its single argument.

The effects of applying private to a single hash entry would be to:

After a hash has been marked in this way, the specific key(s) would only be directly accessible in the same package:

        package MyClass;
        
        sub new { bless private { secret => 'data' }, $_[0] }
        

        package main;
        
        my $obj = MyClass->new();
        print $obj->{secret};                   # dies, inaccessible entry

Attempts to autovivify keys of a private-ized hash would also be fatal:

        package MyClass;
        
        sub print_me { print $_[0]->{sekret} }  # dies, no such entry

        
        package main;
        
        my $obj = MyClass->new();
        print $obj->{public};                   # dies, can't create entry

Once a hash has been private-ized, the only way to extend its set of entries is via another call to private (or public -- see below):

        sub new {
                my ($class, %self) = @_;
                bless private \%self, $class;
                $self{seed} = rand;             # dies, can't autovivify
                private $self{seed} = rand;     # okay
                $self{seed} = rand;             # now okay
        }

        

Applying private to a hash slice would apply it individually to each entry in the slice. Applying private to an entire hash (directly, or via a reference) would apply it to every entry in the hash that is not already private (or public -- see below).

Private entries and inheritance

Private entries of hashes could be indirectly accessed in packages that inherit from the entry's package, by qualifying (i.e. prefixing) the key with the entry's package name. For example:

        package Base;
        
        sub new {
                my ($class, @data) = @_;
                bless private { data => [@data] }, $class;
        }
        
        
        package SortableBase;
        use base 'Base';
                
        sub sorted {
                my ($self) = @_;
                print sort @{ $self->{Base::data} };
        }
        

Note, however, that it would still be a fatal error to attempt to access a private entry outside its package's hierarchy, even with full qualification:

        package main;
        
        my $obj = Base->new(@data);
        
        print $obj->{Base::data};               # dies, not in derived class

Because private entries are only directly acccessible within their own package, a hash could be given two private entries with the same key, but associated with different packages. For example:

        package Base;
        
        sub new {
                my ($class, @data) = @_;
                bless private { data => [@data] }, $class;
        }
        
        sub data { return $_[0]->{data} };      # Base::data
        
        
        package Derived;
        use base 'Base';
                
        sub new {
                my ($class, $derdatum, @basedata) = @_;
                my $self = $class->SUPER::new(@basedata);
                private $self->{data} = $derdatum;
                return $self;
        }

        sub data { return $_[0]->{data} };      # Derived::data
        

Note that this solves the pernicious "data collision" problem in OO Perl.

Iteration of private-ized hashes

When a private-ized hash is iterated -- via each, keys, or values -- the only keys or values returned would be those that are directly or indirectly accessible within the current package. Furthermore, iterators that return keys would always return fully qualified keys.

For example:

        package Base;
        
        sub new {
                my ($class) = @_;
                bless private { a=>1, b=>2 }, $class;
        }
        
        sub iterate {
                my ($obj) = @_;
                print join ", ", keys %$obj;
        }
        
        
        package Derived;
        use base 'Base';
                
        sub new {
                my ($class) = @_;
                my $self = $class->SUPER::new();
                private $self->{c} = 3;
                return $self;
        }

        sub iterate {
                my ($obj) = @_;
                print join ", ", keys %$obj;
        }


        package main;

        sub iterate {
                my ($obj) = @_;
                print join ", ", keys %$obj;
        }

        
        my $obj = Derived->new();
        
        Base::iterate($obj);            # prints: "Base::a, Base:b"
        Derived::iterate($obj);         # prints: "Base::a, Base:b, Derived::c"
        main::iterate($obj);            # prints: ""

This ensures that the encapsulation provided by private isn't accidentally subverted during iteration.

Public accessibility

Although it is almost inevitably a Very Bad Idea and we shall probably All Come Regret To It Later, it ought to be possible to specify that certain entries of a private-ized hash are nevertheless public. This might, for example, be necessary when inheriting from legacy code.

To provide this capacity, a second built-in function -- public -- would be provided. It would act in most respects like private, but with the following differences:

In all other respects the public keyword has analogous effects to the private keyword. Specifically:

Private methods

The private keyword could also be applied to subroutines, to restrict where they may be called. Access rules similar to those for private hash entries would apply:

        package Base;
        
        sub new { ... }
        
        private sub check { ... }
        
        sub do_check {
                my ($self) = @_;
                $self->check();         # okay
                $self->Base::check();   # okay
                check();                # okay
                Base::check();          # okay
                
        
        package Derived;
        use base 'Base';
        
        sub do_check {
                my ($self) = @_;
                $self->check();         # dies, no suitable accessible method
                $self->Base::check();   # okay
                Base::check($self);     # okay
        }
                
        
        package main;
        
        my $obj = Base->new();
        $obj->check();                  # dies, no suitable accessible method
        $obj->Base::check();            # dies, no suitable accessible method
        Base::check($obj);              # dies, inaccessible subroutine

In other words, declaring a subroutine private causes it to be ignored during subroutine or method dispatch unless:

Note: an alternative would be to make private a subroutine attribute:

        sub check : private { ... }

However, it is felt that the modification private imposes is so significant that it warrants a prefix modifier. Besides, the private keyword will be easier to explain and remember if it is consistently used as a prefix for both hash entries and subroutines.

MIGRATION ISSUES

None.

IMPLEMENTATION

The Tie::SecureHash module implements much the same idea.

REFERENCES

Christiansen & Torkington, Perl Cookbook, pp. 470-472.

Conway, Object Oriented Perl, pp. 309-326.