more-cookin
Stevan Little [Mon, 27 Mar 2006 18:36:20 +0000 (18:36 +0000)]
lib/Moose/Cookbook/Recipe3.pod
t/003_basic.t

index 3625a30..d2bfcad 100644 (file)
@@ -12,24 +12,26 @@ Moose::Cookbook::Recipe3 - A binary tree
   use warnings;
   use Moose;
   
+  has 'node' => (is => 'rw', isa => 'Any');
+  
   has 'parent' => (
-         is        => 'rw',
-         isa       => 'BinaryTree',    
+      is        => 'rw',
+      isa       => 'BinaryTree',       
       predicate => 'has_parent',
-         weak_ref  => 1,
+      weak_ref  => 1,
   );
   
   has 'left' => (
-         is        => 'rw',    
-         isa       => 'BinaryTree',            
+      is        => 'rw',       
+      isa       => 'BinaryTree',               
       predicate => 'has_left',  
       lazy      => 1,
       default   => sub { BinaryTree->new(parent => $_[0]) },       
   );
   
   has 'right' => (
-         is        => 'rw',    
-         isa       => 'BinaryTree',            
+      is        => 'rw',       
+      isa       => 'BinaryTree',               
       predicate => 'has_right',   
       lazy      => 1,       
       default   => sub { BinaryTree->new(parent => $_[0]) },       
@@ -42,6 +44,195 @@ Moose::Cookbook::Recipe3 - A binary tree
 
 =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. 
+
+The class in this recipe is a classic binary tree, each node in the 
+tree is represented by an instance of the B<BinaryTree> class. Each 
+instance has a C<node> slot to hold an abitrary value, a C<right> 
+slot to hold the right node, a C<left> slot to hold the left node, 
+and finally a C<parent> slot to hold a reference back up the tree. 
+
+Now, lets start with the code, our first attribute is the C<node> 
+slot, defined as such:
+
+  has 'node' => (is => 'rw', isa => 'Any');
+
+If you recall from the previous recipies, this slow 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<Any>. In the type 
+constraint heirarchy in L<Moose::Utils::TypeConstraints>, the C<Any> 
+constraint is the "root" of the hierarchy. It means exactly what it 
+says, it allows anything to pass. Now, you could just as easily of left 
+the C<isa> out, and left the C<node> slot unconstrainted and gotten the 
+same behavior. But here, we are really including the type costraint 
+for the benefit of other programmers, not the computer. It makes 
+clear my intent that the C<node> can be of any type, and that the 
+class is a polymorphic container. Next, lets move onto the C<parent> 
+slot. 
+
+  has 'parent' => (
+      is        => 'rw',
+      isa       => 'BinaryTree',       
+      predicate => 'has_parent',
+      weak_ref  => 1,
+  );
+
+As you already know from reading the previous recipes, this code 
+tells you that C<parent> gets a read/write accessor, is constrainted 
+to only accept instances of B<BinaryTree>. You will of course remember 
+from the second recipe that the C<BinaryTree> type constraint is 
+automatically created for us by Moose.
+
+The next attribute option is new though, the C<predicate> option. 
+This option creates a method, which can be used to check to see if 
+a given slot (in this case C<parent>) has a defined value in it. In 
+this case it will create a method called C<has_parent>. Quite simple, 
+and also quite handy too.
+
+This brings us to our last attribute, and also a new one. Since the 
+C<parent> is a circular reference (the tree in C<parent> should 
+already have a reference in either it's C<left> or C<right> nodes), 
+we want to make sure that it is also a weakened reference to avoid 
+memory leaks. The C<weak_ref> attribute option will do just that,  
+C<weak_ref> simply takes a boolean value (C<1> or C<0>) and it will 
+then add the extra capability to the accessor function to weaken 
+the reference of any value stored in the C<parent> slot (1).
+
+Now, onto the C<left> and C<right> attributes. They are essentially 
+the same things, only with different names, so I will just describe 
+one here.
+
+  has 'left' => (
+      is        => 'rw',       
+      isa       => 'BinaryTree',               
+      predicate => 'has_left',  
+      lazy      => 1,
+      default   => sub { BinaryTree->new(parent => $_[0]) },       
+  );
+
+You already know what the C<is>, C<isa> and C<>predicate> options 
+do, but now we have two more new options. These two options are 
+actually linked together, in fact, you cannot use the C<lazy> 
+option unless you have set the C<default> option. The class 
+creation will fail with an exception (2).
+
+Before I go into detail about how C<lazy> works, let me first 
+explain how C<default> works, and in particular why it is wrapped 
+in a CODE ref.
+
+In the second recipe the B<BankAccount>'s C<balance> 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) Perl will copy by 
+reference. This means that if I were to do this:
+
+  has 'foo' => (is => 'rw', default => []);
+
+Every single instance of that class would get a pointer to the 
+same ARRAY ref in their C<foo> slot. This is almost certainly 
+B<not> the behavior you intended. So, the solution is to wrap 
+these defaults into an anon-sub, like so:
+
+  has 'foo' => (is => 'rw', default => sub { [] });
+
+This assures that each instance of this class will get it's own 
+ARRAY ref in the C<foo> slot. 
+
+One other feature of the sub ref version of the C<default> option 
+is that when the subroutine is executed (to get back the expected 
+default value), we also pass in the instance where the slot will 
+be stored. This added feature can come in quite handy at times, as 
+is illustrated above, with this code:
+
+  default => sub { BinaryTree->new(parent => $_[0]) },  
+  
+The default value being generated is a new C<BinaryTree> instance 
+for the C<left> (or C<right>) slot. Here we set up the parental 
+relationship by passing the current instance to the constructor. 
+
+Now, before we go on to the C<lazy> 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<left> and C<right> slots to be populated with a new instance
+of B<BinaryTree>. In creating that instance of the C<left> or 
+C<right> slots, we would need to create new instances to populate 
+the C<left> and C<right> slots of I<those> instances. This would 
+continue in an I<infinitely recursive spiral of death> until you had 
+exhausted all available memory on your machine.
+
+This is, of course, not good :)
+
+Which brings us to the C<lazy> attribute option. The C<lazy> option 
+does just what it says. It lazily initializes the slot within the 
+instance. This means that it waits till the I<absolute> last possible 
+moment to populate the slot. This means that if you, the user, write 
+to the slot, everything happens as normal and what you pass in is stored. 
+However, if you I<read> the slot, then at that I<exact> moment (and no 
+sooner), the slot will be populated with the value of the C<default> 
+option.
+
+This option is what allows the B<BinaryTree> class to instantiate 
+objects without fear of the I<infinitely recursive spiral of death> 
+I mentioned earlier.
+
+So, we have descibed a quite complex set of behaviors here, and not 
+one method has needed to be written. But wait, we can't get away that 
+easily. The autogenerated C<right> and C<left> 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 (the type 
+constraint, the lazy initialization, etc). So instead we use the 
+method modifiers again.
+  
+  before 'right', 'left' => sub {
+      my ($self, $tree) = @_;
+      $tree->parent($self) if defined $tree;   
+  };
+
+This is a C<before> 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<left> and C<right> 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 right?).
+
+Now, as with all the other recipes, you can go about using 
+B<BinaryTree> like any other Perl 5 class. A more detailed example of 
+usage can be found in F<t/003_basic.t>.
+
+=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.
+  
+=head1 FOOTNOTES
+
+=over 4
+
+=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. 
+
+In short, don't use them unless you know what you are doing :)
+
+=item (2)
+
+You I<can> use the C<default> option without the C<lazy> option if 
+you like, as we showed in the second recipe.
+
+=back
+
 =head1 AUTHOR
 
 Stevan Little E<lt>stevan@iinteractive.comE<gt>
index c558dc4..cdff83a 100644 (file)
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 
-use Test::More tests => 25;
+use Test::More tests => 34;
 use Test::Exception;
 
 use Scalar::Util 'isweak';
@@ -18,6 +18,8 @@ BEGIN {
     use warnings;
     use Moose;
 
+    has 'node' => (is => 'rw', isa => 'Any');
+
     has 'parent' => (
                is        => 'rw',
                isa       => 'BinaryTree',      
@@ -47,9 +49,11 @@ BEGIN {
        };
 }
 
-my $root = BinaryTree->new();
+my $root = BinaryTree->new(node => 'root');
 isa_ok($root, 'BinaryTree');
 
+is($root->node, 'root', '... got the right node value');
+
 ok(!$root->has_left, '... no left node yet');
 ok(!$root->has_right, '... no right node yet');
 
@@ -71,11 +75,31 @@ ok(isweak($left->{parent}), '... parent is a weakened ref');
 ok(!$left->has_left, '... $left no left node yet');
 ok(!$left->has_right, '... $left no right node yet');
 
+is($left->node, undef, '... left has got no node value');
+
+lives_ok {
+    $left->node('left')
+} '... assign to lefts node';
+
+is($left->node, 'left', '... left now has a node value');
+
 # make a right node
 
+ok(!$root->has_right, '... still no right node yet');
+
+is($root->right->node, undef, '... right has got no node value');
+
+ok($root->has_right, '... now we have a right node');
+
 my $right = $root->right;
 isa_ok($right, 'BinaryTree');
 
+lives_ok {
+    $right->node('right')
+} '... assign to rights node';
+
+is($right->node, 'right', '... left now has a node value');
+
 is($root->right, $right, '... got the same node (and it is $right)');
 ok($root->has_right, '... we have a right node now');