Sign In/My Account | View Cart  
advertisement


Listen Print

Apocalypse 12
by Larry Wall | Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20

Editor's Note: this Apocalypse is out of date and remains here for historic reasons. See Synopsis 12 for the latest information.

Attributes

In Perl 6, "attributes" are what we call the instance variables of an object. (We used that word to mean something else in Perl 5--we're now calling those things "traits" or "properties".)

As with classes and methods, attribute declarations are apparently declarative. Underneath they actually call a method in the metaclass to install the new definition. The Perl 6 implementation of attributes is not based on a hash, but on something more like a symbol table. Attributes are stored in an opaque datatype rather like a struct in C, or an array in Perl 5--but you don't know that. The datatype is opaque in the sense that you shouldn't care how it's laid out in memory (unless you have to interface with an outside data structure--like a C struct). Do not confuse opacity with encapsulation. Encapsulation only hides the object's implementation from the outside world. But the object's structure is opaque even to the class that defines it.

One of the large benefits of this is that you can actually take a C or C++ data structure and wrap accessor methods around it without having to copy anything into a different data structure. This should speed up things like XML parsing.

Declaration of Attributes

In order to provide this opaque abstraction layer, attributes are not declared as a part of any other data structure. Instead, they are modeled on real variables, whose storage details are implicitly delegated to the scope in which they are declared. So attributes are declared as if they were normal variables, but with a strange scope and lifetime that is neither my nor our. (That scope is, of course, the current object, and the variable lives as long as the object lasts.) The class will implicitly store those attributes in a location distinct from any other class's attributes of the same name, including any base or derived class. To declare an attribute variable, declare it within the class definition as you would a my variable, but use the has declarator instead of my:


    class Dog is Mammal {
        has $.tail;
        has @.legs;
        ...
    }

The has declarator was chosen to remind people that attributes are in a "HASA" relationship to the object rather than an "ISA" relationship.

The other difference from normal variables is that attributes have a secondary sigil that indicates that they are associated with methods. When you declare an attribute like $.tail, you're also implicitly declaring an accessor method of the same name, only without the $ on the front. The dot is there to remind you that it's also a method call.

As with other declarations, you may add various traits to an attribute:


    has $.dogtag is rw;

If you want all your attributes to default to "rw", you can put the attribute on the class itself:


    class Coordinates is rw {
        has int $.x;
        has int $.y;
        has int $.z;
    }

Essentially, it's now a C-style struct, without having to introduce an ugly word like "struct" into the language. Take that, C++. :-)

You can also assign to a declaration:


    has $.master = "TheDamian";

Well, actually, this looks like an assignment, but it isn't. The effect of this is to establish a default; it is not executed at runtime. (Or more precisely, it runs when the class closure is executed by the metaclass, so it gets evaluated only once and the value is stored for later use by real instances. More below.)

Use of Attributes

The attribute behaves just like an ordinary variable within the class's instance methods. You can read and write the attributes just like ordinary variables. (It is, however, illegal to refer to an instance attribute variable (that is, a "has" variable) from within a class method. Class methods may only access class attributes, not instance attributes. See below.)

Bare attributes are automatically hidden from the outside world because their sigiled names cannot be seen outside the class's package. This is how Perl 6 enforces encapsulation. Outside the class the only way to talk about an attribute is through accessor methods. Since public methods are always virtual in Perl, this makes attribute access virtual outside the class. Always. (Unless you give the optimizer enough hints to optimize the class to "final". More on that later.)

In other words, only the class itself is allowed to know whether this attribute is, in fact, implemented by this class. The class may also choose to ignore that fact, and call the abstract interface, that is, the accessor method, in which case it might actually end up calling some derived class's overriding method, which might in turn call back to this class's accessor as a super method. (So in general, an accessor method should always refer to its actual variable name rather than the accessor method name to avoid infinite recursion.)

You may write your own accessor methods around the bare attributes, but if you don't, Perl will generate them for you based on the declaration of the attribute variable. The traits of the generated method correspond directly to the traits on the variable.

By default, a generated accessor is read-only (because by default any method is read-only). If you mark an attribute with the trait "is rw" though, the corresponding generated accessor will also be marked "is rw", meaning that it can be used as an lvalue.

In any event, even without "is rw" the attribute variable is always writable within the class itself (unless you apply the trait is constant to it).

As with private classes and methods, attributes are declared private using a colon on the front of their names. As with any private method, a private accessor is completely ignored outside its class (or, by extension, the classes trusted by this class).

To carry the separate namespace idea through, we incorporate the colon as the secondary sigil in declarations of private attributes:


    has $:x;

Then we can get rid of the verbose is private altogether. Well, it's still there as a trait, but the colon implies it, and is required anyway.) And we basically force people to document the private/public distinction every place they reference $:x instead of $.x, or $obj.:meth instead of $obj.meth.

We've seen secondary sigils before in earlier Apocalypses. In each case they're associated with a bizarre usage of some sort. So far we have:


    $*foo       # a truly global global (in every package)
    $?foo       # a regex-scoped variable
    $^foo       # an autodeclared parameter variable
    $.foo       # a public attribute
    $:foo       # a private attribute

As a form of the dreaded "Hungarian notation", secondary sigils are not introduced lightly. We define secondary sigils only where we deem instant recognizability to be crucial for readability. Just as you should never have to look at a variable and guess whether it's a true global, you should never have to look at a method and guess which variables are attributes and which ones are variables you just happen to be in the lexical scope of. Or which attributes are public and which are private. In Perl 6 it's always obvious--at the cost of a secondary sigil.

We do hereby solemnly swear to never, never, ever add tertiary sigils. You have been warned.

Default Values

You can set default values on attributes by pseudo-assignment to the attribute declaration:


    has Answer $.ans = 42;

These default values are associated as "build" traits of the attribute declaration object. When the BUILD submethod is initializing a new object, these prototype values are used for uninitialized attributes. The expression on the right is evaluated immediately at the point of declaration, but you can defer evaluation by passing a closure, which will automatically be evaluated at the actual initialization time. (Therefore, to initialize to a closure value, you have to put a closure in a closure.)

Here's the difference between those three approaches. Suppose you say:


    class Hitchhiker {
        my $defaultanswer = 0;
        has $.ans1 = $defaultanswer;
        has $.ans2 = { $defaultanswer };
        has $.ans3 = { { $defaultanswer } };
        $defaultanswer = 42;
        ...
    }

When the object is eventually constructed, $.ans1 will be initialized to 0, while $.ans2 will be initialized to 42. (That's because the closure binds $defaultanswer to the current variable, which still presumably has the value 42 by the time the BUILD routine initializes the new object, even though the lexical variable "$defaultanswer" has supposedly gone out of scope by the time the object is being constructed. That's just how closures work.)

And $.ans3 will be initialized not to 42, but to a closure that, if you ever call it, will also return 42. So since the accessor $obj.ans3() returns that closure, $obj.ans3().() will return 42.

The default value is actually stored under the "build" trait, so this:


    has $.x = calc($y);

is equivalent to this:


    has $.x is build( calc($y) );

and this:


    has $.x = { calc($y) };

is equivalent to either of these:


    has $.x is build( { calc($y) } );
    has $.x will build { calc($y) };

As with all closure-valued container traits, the container being declared (the $.x variable in this case) is passed as the topic to the closure (in addition to being the target that will be initialized with the result of the closure, because that's what build does). In addition to the magical topic, these build traits are also magically passed the same named arguments that are passed to the BUILD routine. So you could say


    has $.x = { calc($^y) };

to do a calculation based on the :y(582) parameter originally passed to the constructor. Or rather, that will be passed to the constructor someday when the object is eventually constructed. Remember we're really still at class construction time here.

As with other initializers, you can be more specific about the time at which the default value is constructed, as long as that time is earlier than class construction time:


    has $.x = BEGIN { calc() }
    has $.x = CHECK { calc() }
    has $.x = INIT  { calc() }
    has $.x = FIRST { calc() }
    has $.x = ENTER { calc() }

which are really just short for:


    has $.x is build( BEGIN { calc() } )
    has $.x is build( CHECK { calc() } )
    has $.x is build( INIT  { calc() } )
    has $.x is build( FIRST { calc() } )
    has $.x is build( ENTER { calc() } )

Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20

Next Pagearrow