From: Dave Rolsky Date: Fri, 6 Feb 2009 03:58:24 +0000 (+0000) Subject: Made an editorial pass over basics recipe 3 X-Git-Tag: 0.69~42 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=f6f9ec6a6344b7ee9ab4f7ea15357e5048568191;p=gitmo%2FMoose.git Made an editorial pass over basics recipe 3 --- diff --git a/lib/Moose/Cookbook/Basics/Recipe3.pod b/lib/Moose/Cookbook/Basics/Recipe3.pod index 2ce34a1..2b61fa0 100644 --- a/lib/Moose/Cookbook/Basics/Recipe3.pod +++ b/lib/Moose/Cookbook/Basics/Recipe3.pod @@ -42,33 +42,26 @@ Moose::Cookbook::Basics::Recipe3 - A lazy B example =head1 DESCRIPTION -In this recipe we take a closer look at attributes, and see how -some of their more advanced features can be used to create fairly -complex behaviors. +This recipe shows how various advanced attribute features can be used +to create complex and powerful behaviors. -The class in this recipe is a classic binary tree, each node in the -tree is represented by an instance of the B class. Each -instance has a C slot to hold an arbitrary value, a C -slot to hold the right node, a C slot to hold the left node, -and finally a C slot to hold a reference back up the tree. +The example class is a classic binary tree. Each node in the tree is +itself an instance of C. It has a C, which holds +some arbitrary value. It has C and C attributes, which +refer to its child trees, and a C. -Now, let's start with the code. Our first attribute is the C -slot, defined as such: +Let's take a look at the C attribute: has 'node' => ( is => 'rw', isa => 'Any' ); -If you recall from the previous recipes, this slot will have a read/write -accessor generated for it, and has a type constraint on it. The new item here is -the type constraint of C. C is the "root" of the -L type hierarchy. It means exactly what it says: -I value passes the constraint. Now, you could just as easily have left out -the C, leaving the C slot unconstrained and retaining this -behavior. But in this case, we are really including the type constraint for the -benefit of other programmers, not the computer. It makes clear my intent that -the C attribute can be of any type, and that the class is a polymorphic -container. +Moose generates a read-write accessor for this attribute. The type +constraint is C, which means literally means it can contain +anything. -Next, let's move on to the C slot: +We could have left out the C option, but in this case, we are +including ir for the benefit of other programmers, not the computer. + +Next, let's move on to the C attribute: has 'parent' => ( is => 'rw', @@ -77,27 +70,26 @@ Next, let's move on to the C slot: weak_ref => 1, ); -As you already know, this code tells you that C gets a read/write -accessor and is constrained to only accept instances of B. You will -of course remember from the second recipe that the C type constraint -is automatically created for us by Moose. +Again, we have a read-write accessor. This time, the C option +says that this attribute must always be an instance of +C. In the second recipe, we saw that every time we create +a Moose-based class, we also get a corresponding class type +constraint. -The next attribute option is new, though: the C option. -This option creates a method which can be used to check whether -a given slot (in this case C) has been initialized. In -this case it will create a method called C. Quite simple, -and quite handy too. +The C option is new. It creates a method which can be used +to check whether or not a given attribute has been initialized. In +this case, the method is named C. -This brings us to our last attribute option, also a new one. Since C is -a circular reference (the tree in C should already have a reference to -this one, in its C or C node), we want to make sure that it is also -a weakened reference to avoid memory leaks. The C attribute option -will do just that, C simply takes a boolean value (C<1> or C<0>) and -then alters the accessor function to weaken the reference to any value stored in -the C slot (1). +This brings us to our last attribute option, C. Since +C is a circular reference (the tree in C should +already have a reference to this one, in its C or C +attribute), we want to make sure that we weaken the reference to avoid +memory leaks. If C is true, it alters the accessor function +so that the reference is weakened when it is set. -Now, onto the C and C attributes. They are essentially identical, -save for different names, so I will just describe one here: +Finally, we have the the C and C attributes. They are +essentially identical except for their names, so we'll just look at +C: has 'left' => ( is => 'rw', @@ -107,98 +99,80 @@ save for different names, so I will just describe one here: default => sub { BinaryTree->new( parent => $_[0] ) }, ); -You already know what the C, C and C options do, but now we -have two new options. These two options are actually linked together, in fact: -you cannot use the C option unless you have set the C option. -Class creation will fail with an exception (2). +There are two new options here, C and C. These two +options are linked, and in fact you cannot have a C attribute +unless it has a C (or a C, but we'll cover that +later). If you try to make an attribute lazy without a default, class +creation will fail with an exception. (2) + +In the second recipe the B's C attribute had a +default value of C<0>. Given a non-reference, Perl copies the +I. However, given a reference, it does not do a deep clone, +instead simply copying the reference. If you just specified a simply +reference for a default, Perl would create it once and it would be +shared by all objects with that attribute. -Before I go into detail about how C works, let me first -explain how C works, and in particular why it is wrapped -in a CODE ref. +As a workaround, we use an anonymous subroutine to generate a new +reference every time the default is called. -In the second recipe the B's C slot had a -default value of C<0>. Since Perl will copy strings and numbers -by value, this was all we had to say. But for any other item -(ARRAY ref, HASH ref, object instance, etc) you would need to -wrap it in a CODE reference, so this: + has 'foo' => ( is => 'rw', default => sub { [] } ); + +In fact, using a non-subroutine reference as a default is illegal in Moose. has 'foo' => ( is => 'rw', default => [] ); -is actually illegal in Moose. Instead, what you really want is this: +This will blow up, so don't do it. - has 'foo' => ( is => 'rw', default => sub { [] } ); +You'll notice that we use C<$_[0]> in our default sub. When the +default subroutine is executed, it is called as a method on the +object. + +In our case, we're making a new C object in our default, +with the current tree as the parent. + +Normally, when an object is instantiated, any defaults are evaluted +immediately. With our C class, this would be a big +problem! We'd create the first object, which would immediately try to +populate its C and C attributes, which would create a new +C, which would populate I C and C +slots. Kaboom! + +By making our C and C attributes C, we avoid this +problem. If the attribute has a value when it is read, the default is +never executed at all. + +We still have one last bit of behavior to add. The autogenerated +C and C accessors are not quite correct. When one of +these is set, we want to make sure that we update the parent of the +C or C attribute's tree. -This ensures that each instance of this class will get its own ARRAY ref in the -C slot. - -One other feature of the CODE ref version of the C option is that when -the subroutine is executed (to get the default value), we pass in the instance -where the slot will be stored. This can come in quite handy at times, as -illustrated above, with this code: - - default => sub { BinaryTree->new( parent => $_[0] ) }, - -The default value being generated is a new C instance for the -C (or C) slot. Here we set up the correct relationship by passing -the current instance as the C argument to the constructor. - -Now, before we go on to the C option, I want you to think -for a moment. When an instance of this class is created, and the -slots are being initialized, the "normal" behavior would be for -the C and C slots to be populated with a new instance -of B. In creating that instance of the C or -C slots, we would need to create new instances to populate -the C and C slots of I instances. This would -continue in an I until you had -exhausted all available memory on your machine. - -This is, of course, not good :) - -Which brings us to the C attribute option. The C option does just -what it says: it lazily initializes the slot within the instance. This means -that it waits till absolutely the I possible moment to populate the -slot. So if you, the user, store a value in the slot, everything works normally, -and what you pass in is stored. However, if you I the slot I -storing a value in it, then at that I moment (and no sooner), the slot -will be populated with the value of the C option. - -This option is what allows the B class to instantiate -objects without fear of the I -mentioned earlier. - -So, we have described a quite complex set of behaviors here, and not one method -had to be written. But wait, we aren't quite done yet; the autogenerated -C and C accessors are not completely correct. They will not install -the parental relationships that we need. We could write our own accessors, but -that would require us to implement all those features we got automatically (type -constraints, lazy initialization, and so on). Instead, we use method modifiers -again: +We could write our own accessors, but then why use Moose at all? +Instead, we use method modifiers: before 'right', 'left' => sub { my ( $self, $tree ) = @_; $tree->parent($self) if defined $tree; }; -This is a C modifier, just like we saw in the second recipe, but with -two slight differences. First, we are applying this to more than one method at a -time. Since both the C and C methods need the same feature, it -makes sense. The second difference is that we are not wrapping an inherited -method anymore, but instead a method of our own local class. Wrapping local -methods is no different, the only requirement is that the wrappee be created -before the wrapper (after all, you cannot wrap something which doesn't exist, +This is a C modifier, just like we saw in the second recipe, +but with two slight differences. First, we are applying the modifier +to more than one method at a time, because both C and C +attributes need the same behavior. The other difference is that we are +not wrapping an inherited method, but rather a method from our own +local class. Wrapping local methods is no different, the only +requirement is that the wrappee must exist before the wrapper is +defined (after all, you cannot wrap something which doesn't exist, right?). -Now, as with all the other recipes, you can go about using -B like any other Perl 5 class. A more detailed example of its -usage can be found in F. +As with all the other recipes, B can be used just like any +other Perl 5 class. A more detailed example of its usage can be found +in F. =head1 CONCLUSION -This recipe introduced you to some of the more advanced behavioral -possibilities of Moose's attribute mechanism. I hope that it has -opened your mind to the powerful possibilities of Moose. In the next -recipe we explore how we can create custom subtypes and take -advantage of the plethora of useful modules out on CPAN with Moose. +This recipe introduced several of Moose's advanced features. We hope +that this inspires you to think of other ways these features can be +used to simplify your code. =head1 FOOTNOTES @@ -206,20 +180,20 @@ advantage of the plethora of useful modules out on CPAN with Moose. =item (1) -Weak references are tricky things, and should be used sparingly -and appropriately (such as in the case of circular refs). If you -are not careful, you will have slot values disappear "mysteriously" -because perls reference counting garbage collector has gone and -removed the item you are weak-referencing. +Weak references are tricky things, and should be used sparingly and +appropriately (such as in the case of circular refs). If you are not +careful, attribute values could disappear "mysteriously" because +Perl's reference counting garbage collector has gone and removed the +item you are weak-referencing. In short, don't use them unless you know what you are doing :) =item (2) -You I use the C option without the C option if -you like, as we showed in the second recipe. +You I use the C option without the C option if you +like, as we showed in the second recipe. -And actually, you can use C instead of C. See +Also, you can use C instead of C. See L for details. =back