Redid conversion to Test::Fatal
[gitmo/Moose.git] / lib / Moose / Cookbook / Basics / Recipe3.pod
index 9782548..84e7da5 100644 (file)
@@ -9,196 +9,186 @@ Moose::Cookbook::Basics::Recipe3 - A lazy B<BinaryTree> example
 
   package BinaryTree;
   use Moose;
-  
-  has 'node' => (is => 'rw', isa => 'Any');
-  
+
+  has 'node' => ( is => 'rw', isa => 'Any' );
+
   has 'parent' => (
       is        => 'rw',
-      isa       => 'BinaryTree',       
+      isa       => 'BinaryTree',
       predicate => 'has_parent',
       weak_ref  => 1,
   );
-  
+
   has 'left' => (
-      is        => 'rw',       
-      isa       => 'BinaryTree',               
-      predicate => 'has_left',  
+      is        => 'rw',
+      isa       => 'BinaryTree',
+      predicate => 'has_left',
       lazy      => 1,
-      default   => sub { BinaryTree->new(parent => $_[0]) },       
+      default   => sub { BinaryTree->new( parent => $_[0] ) },
+      trigger   => \&_set_parent_for_child
   );
-  
+
   has 'right' => (
-      is        => 'rw',       
-      isa       => 'BinaryTree',               
-      predicate => 'has_right',   
-      lazy      => 1,       
-      default   => sub { BinaryTree->new(parent => $_[0]) },       
+      is        => 'rw',
+      isa       => 'BinaryTree',
+      predicate => 'has_right',
+      lazy      => 1,
+      default   => sub { BinaryTree->new( parent => $_[0] ) },
+      trigger   => \&_set_parent_for_child
   );
-  
-  before 'right', 'left' => sub {
-      my ($self, $tree) = @_;
-      $tree->parent($self) if defined $tree;   
-  };
+
+  sub _set_parent_for_child {
+      my ( $self, $child ) = @_;
+
+      confess "You cannot insert a tree which already has a parent"
+          if $child->has_parent;
+
+      $child->parent($self);
+  }
 
 =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. In particular, we introduce
+a number of new attribute options, including C<predicate>, C<lazy>,
+and C<trigger>.
+
+The example class is a classic binary tree. Each node in the tree is
+itself an instance of C<BinaryTree>. It has a C<node>, which holds
+some arbitrary value. It has C<right> and C<left> attributes, which
+refer to its child trees, and a C<parent>.
 
-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 arbitrary 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. 
+Let's take a look at the C<node> attribute:
 
-Now, let's start with the code. Our first attribute is the C<node> 
-slot, defined as such:
+  has 'node' => ( is => 'rw', isa => 'Any' );
 
-  has 'node' => (is => 'rw', isa => 'Any');
+Moose generates a read-write accessor for this attribute. The type
+constraint is C<Any>, which literally means it can contain anything.
 
-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<Any>. C<Any> is the "root" of the
-L<Moose::Util::TypeConstraints> type hierarchy. It means exactly what it says:
-I<any> value passes the constraint. Now, you could just as easily have left out
-the C<isa>, leaving the C<node> 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<node> attribute can be of any type, and that the class is a polymorphic
-container.
+We could have left out the C<isa> option, but in this case, we are
+including it for the benefit of other programmers, not the computer.
 
-Next, let's move on to the C<parent> slot:
+Next, let's move on to the C<parent> attribute:
 
   has 'parent' => (
       is        => 'rw',
-      isa       => 'BinaryTree',       
+      isa       => 'BinaryTree',
       predicate => 'has_parent',
       weak_ref  => 1,
   );
 
-As you already know, this code tells you that C<parent> gets a read/write
-accessor and is constrained 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.
+Again, we have a read-write accessor. This time, the C<isa> option
+says that this attribute must always be an instance of
+C<BinaryTree>. 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<predicate> option. 
-This option creates a method which can be used to check whether 
-a given slot (in this case C<parent>) has been initialized. In 
-this case it will create a method called C<has_parent>. Quite simple, 
-and quite handy too.
+The C<predicate> 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<has_parent>.
 
-This brings us to our last attribute option, also a new one. Since C<parent> is
-a circular reference (the tree in C<parent> should already have a reference to
-this one, in its C<left> or C<right> node), 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
-then alters the accessor function to weaken the reference to any value stored in
-the C<parent> slot (1).
+This brings us to our last attribute option, C<weak_ref>. Since
+C<parent> is a circular reference (the tree in C<parent> should
+already have a reference to this one, in its C<left> or C<right>
+attribute), we want to make sure that we weaken the reference to avoid
+memory leaks. If C<weak_ref> is true, it alters the accessor function
+so that the reference is weakened when it is set.
 
-Now, onto the C<left> and C<right> attributes. They are essentially identical,
-save for different names, so I will just describe one here:
+Finally, we have the the C<left> and C<right> attributes. They are
+essentially identical except for their names, so we'll just look at
+C<left>:
 
   has 'left' => (
-      is        => 'rw',       
-      isa       => 'BinaryTree',               
-      predicate => 'has_left',  
+      is        => 'rw',
+      isa       => 'BinaryTree',
+      predicate => 'has_left',
       lazy      => 1,
-      default   => sub { BinaryTree->new(parent => $_[0]) },       
+      default   => sub { BinaryTree->new( parent => $_[0] ) },
+      trigger   => \&_set_parent_for_child
   );
 
-You already know what the C<is>, C<isa> and C<predicate> options do, but now we
-have two 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.
-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) you would need to 
-wrap it in a CODE reference, so this:
-
-  has 'foo' => (is => 'rw', default => []);
-
-is actually illegal in Moose. Instead, what you really want is this:
-
-  has 'foo' => (is => 'rw', default => sub { [] });
-
-This ensures that each instance of this class will get its own ARRAY ref in the
-C<foo> slot. 
-
-One other feature of the CODE ref version of the C<default> 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<BinaryTree> instance for the
-C<left> (or C<right>) slot. Here we set up the correct relationship by passing
-the current instance as the C<parent> argument 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 absolutely the I<latest> 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<read> the slot I<before>
-storing a value in it, 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>
-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<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 (type
-constraints, lazy initialization, and so on). Instead, we use 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 its
-usage can be found in F<t/000_recipes/003_recipe.t>.
+There are three new options here, C<lazy>, C<default>, and
+C<trigger>. The C<lazy> and C<default> options options are linked.  In
+fact, you cannot have a C<lazy> attribute unless it has a C<default>
+(or a C<builder>, 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<BankAccount>'s C<balance> attribute had a
+default value of C<0>. Given a non-reference, Perl copies the
+I<value>. However, given a reference, it does not do a deep clone,
+instead simply copying the reference. If you just specified a simple
+reference for a default, Perl would create it once and it would be
+shared by all objects with that attribute.
+
+As a workaround, we use an anonymous subroutine to generate a new
+reference every time the default is called.
+
+  has 'foo' => ( is => 'rw', default => sub { [] } );
+
+In fact, using a non-subroutine reference as a default is illegal in Moose.
+
+  # will fail
+  has 'foo' => ( is => 'rw', default => [] );
+
+This will blow up, so don't do it.
+
+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<BinaryTree> object in our default,
+with the current tree as the parent.
+
+Normally, when an object is instantiated, any defaults are evaluated
+immediately. With our C<BinaryTree> class, this would be a big
+problem! We'd create the first object, which would immediately try to
+populate its C<left> and C<right> attributes, which would create a new
+C<BinaryTree>, which would populate I<its> C<left> and C<right>
+slots. Kaboom!
+
+By making our C<left> and C<right> attributes C<lazy>, 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<right> and C<left> accessors are not quite correct. When one of
+these is set, we want to make sure that we update the parent of the
+C<left> or C<right> attribute's tree.
+
+We could write our own accessors, but then why use Moose at all?
+Instead, we use a C<trigger>. A C<trigger> accepts a subroutine
+reference, which will be called as a method whenever the attribute is
+set. This can happen both during object construction or later by
+passing a new object to the attribute's accessor method. However, it
+is not called when a value is provided by a C<default> or C<builder>.
+
+  sub _set_parent_for_child {
+      my ( $self, $child ) = @_;
+
+      confess "You cannot insert a tree which already has a parent"
+          if $child->has_parent;
+
+      $child->parent($self);
+  }
+
+This trigger does two things. First, it ensures that the new child
+node does not already have a parent. This is done for the sake of
+simplifying the example. If we wanted to be more clever, we would
+remove the child from its old parent tree and add it to the new one.
+
+If the child has no parent, we will add it to the current tree, and we
+ensure that is has the correct value for its C<parent> attribute.
+
+As with all the other recipes, B<BinaryTree> can be used just like any
+other Perl 5 class. A more detailed example of its usage can be found
+in F<t/000_recipes/moose_cookbook_basics_recipe3.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.
+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,35 +196,154 @@ 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<can> use the C<default> option without the C<lazy> option if 
-you like, as we showed in the second recipe.
+You I<can> use the C<default> option without the C<lazy> option if you
+like, as we showed in the second recipe.
 
-And actually, you can use C<builder> instead of C<default>. See
-L<Moose::Cookbook::Basics::Recipe9> for details.
+Also, you can use C<builder> instead of C<default>. See
+L<Moose::Cookbook::Basics::Recipe8> for details.
 
 =back
 
-=head1 AUTHOR
+=head1 AUTHORS
 
 Stevan Little E<lt>stevan@iinteractive.comE<gt>
 
+Dave Rolsky E<lt>autarch@urth.orgE<gt>
+
 =head1 COPYRIGHT AND LICENSE
 
-Copyright 2006-2008 by Infinity Interactive, Inc.
+Copyright 2006-2010 by Infinity Interactive, Inc.
 
 L<http://www.iinteractive.com>
 
 This library is free software; you can redistribute it and/or modify
 it under the same terms as Perl itself.
 
+=begin testing
+
+use Scalar::Util 'isweak';
+
+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');
+
+ok(!$root->has_parent, '... no parent for root node');
+
+# make a left node
+
+my $left = $root->left;
+isa_ok($left, 'BinaryTree');
+
+is($root->left, $left, '... got the same node (and it is $left)');
+ok($root->has_left, '... we have a left node now');
+
+ok($left->has_parent, '... lefts has a parent');
+is($left->parent, $root, '... lefts parent is the root');
+
+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');
+
+is(
+    exception {
+        $left->node('left');
+    },
+    undef,
+    '... 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');
+
+is(
+    exception {
+        $right->node('right');
+    },
+    undef,
+    '... 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');
+
+ok($right->has_parent, '... rights has a parent');
+is($right->parent, $root, '... rights parent is the root');
+
+ok(isweak($right->{parent}), '... parent is a weakened ref');
+
+# make a left node of the left node
+
+my $left_left = $left->left;
+isa_ok($left_left, 'BinaryTree');
+
+ok($left_left->has_parent, '... left does have a parent');
+
+is($left_left->parent, $left, '... got a parent node (and it is $left)');
+ok($left->has_left, '... we have a left node now');
+is($left->left, $left_left, '... got a left node (and it is $left_left)');
+
+ok(isweak($left_left->{parent}), '... parent is a weakened ref');
+
+# make a right node of the left node
+
+my $left_right = BinaryTree->new;
+isa_ok($left_right, 'BinaryTree');
+
+is(
+    exception {
+        $left->right($left_right);
+    },
+    undef,
+    '... assign to rights node'
+);
+
+ok($left_right->has_parent, '... left does have a parent');
+
+is($left_right->parent, $left, '... got a parent node (and it is $left)');
+ok($left->has_right, '... we have a left node now');
+is($left->right, $left_right, '... got a left node (and it is $left_left)');
+
+ok(isweak($left_right->{parent}), '... parent is a weakened ref');
+
+# and check the error
+
+isnt(
+    exception {
+        $left_right->right($left_left);
+    },
+    undef,
+    '... cannot assign a node which already has a parent'
+);
+
+=end testing
+
 =cut