Skip to content
Browse files

en version

  • Loading branch information...
1 parent 5b6a637 commit 44e238bf68c90350da4cc04eb691c1b171962377 @zag committed Oct 29, 2011
Showing with 694 additions and 0 deletions.
  1. +694 −0 src/classes-and-objects.pod
View
694 src/classes-and-objects.pod
@@ -0,0 +1,694 @@
+=head0 Classes and Objects
+
+Z<sec:classes>
+
+TODO: start with a much simpler bare-bones example!
+
+The following program shows how a dependency handler might look in Perl 6.
+It showcases custom constructors, private and public attributes, methods
+and various aspects of signatures. It's not very much code, and yet the
+result is interesting and, at times, useful.
+
+=begin programlisting
+
+ class Task {
+ has &!callback;
+ has Task @!dependencies;
+ has Bool $.done;
+
+ method new(&callback, Task *@dependencies) {
+ return self.bless(*, :&callback, :@dependencies);
+ }
+
+ method add-dependency(Task $dependency) {
+ push @!dependencies, $dependency;
+ }
+
+ method perform() {
+ unless $!done {
+ .perform() for @!dependencies;
+ &!callback();
+ $!done = True;
+ }
+ }
+ }
+
+ my $eat =
+ Task.new({ say 'eating dinner. NOM!' },
+ Task.new({ say 'making dinner' },
+ Task.new({ say 'buying food' },
+ Task.new({ say 'making some money' }),
+ Task.new({ say 'going to the store' })
+ ),
+ Task.new({ say 'cleaning kitchen' })
+ )
+ );
+
+ $eat.perform();
+
+=end programlisting
+
+=head1 Starting with class
+
+X<class>
+X<classes>
+
+X<state>
+X<has>
+X<classes, has>
+X<behavior>
+X<classes, behavior>
+
+Perl 6, like many other languages, uses the C<class> keyword to introduce a new
+class. The block that follows may contain arbitrary code, just as with
+any other block, but classes commonly contain state and behavior declarations.
+The example code includes attributes (state), introduced through the C<has>
+keyword, and behaviors introduced through the C<method> keyword.
+
+X<type object>
+X<defined>
+X<.defined>
+
+Declaring a class creates a I<type object> which, by default, is
+installed into the current package (just like a variable declared with
+C<our> scope). This type object is an "empty instance" of the class.
+You've already seen these in previous chapters. For example, types such
+as C<Int> and C<Str> refer to the type object of one of the Perl 6 built-
+in classes. The example above uses the class name C<Task> so that other code
+can refer to it later, such as to create class instances by calling the
+C<new> method.
+
+Type objects are I<undefined>, in the sense that they return C<False> if you
+call the C<.defined> method on them. You can use this method to find out if a
+given object is a type object or not:
+
+=begin programlisting
+
+ my $obj = Int;
+ if $obj.defined {
+ say "Ordinary, defined object";
+ } else {
+ say "Type object";
+ }
+
+=end programlisting
+
+=head1 I can has state?
+
+X<attributes>
+X<classes, attributes>
+
+X<encapsulation>
+X<classes, encapsulation>
+
+The first three lines inside the class block all declare attributes (called
+I<fields> or I<instance storage> in other languages). These are storage
+locations that every instance of a class will obtain. Just as a C<my> variable can
+not be accessed from the outside of its declared scope, attributes are not
+accessible outside of the class. This I<encapsulation> is one of the key
+principles of object oriented design.
+
+The first declaration specifies instance storage for a callback -- a bit of
+code to invoke in order to perform the task that an object represents:
+
+=begin programlisting
+
+ has &!callback;
+
+=end programlisting
+
+X<sigils, &>
+X<twigils>
+X<twigils, !>
+
+The C<&> sigil indicates that this attribute represents something invocable.
+The C<!> character is a I<twigil>, or secondary sigil. A twigil forms part of
+the name of the variable. In this case, the C<!> twigil emphasizes that this
+attribute is private to the class.
+
+The second declaration also uses the private twigil:
+
+=begin programlisting
+
+ has Task @!dependencies;
+
+=end programlisting
+
+=for author
+
+The "subclass" language is slightly wrong, but I don't know if we want to get
+into allomorphism here. Is there a rephrasing that's more correct?
+
+=end for
+
+However, this attribute represents an array of items, so it requires the C<@>
+sigil. These items each specify a task that must be completed before the
+present one can complete. Furthermore, the type declaration on this attribute
+indicates that the array may only hold instances of the C<Task> class (or some
+subclass of it).
+
+The third attribute represents the state of completion of a task:
+
+=begin programlisting
+
+ has Bool $.done;
+
+=end programlisting
+
+X<twigils, .>
+X<twigils, accessors>
+X<accessor methods>
+X<classes, accessors>
+
+This scalar attribute (with the C<$> sigil) has a type of C<Bool>. Instead of
+the C<!> twigil, this twigil is C<.>. While Perl 6 does enforce encapsulation
+on attributes, it also saves you from writing accessor methods. Replacing the
+C<!> with a C<.> both declares the attribute C<$!done> and an accessor method
+named C<done>. It's as if you had written:
+
+=begin programlisting
+
+ has Bool $!done;
+ method done() { return $!done }
+
+=end programlisting
+
+Note that this is not like declaring a public attribute, as some languages
+allow; you really get I<both> a private storage location and a method, without
+having to write the method by hand. You are free instead to write your own
+accessor method, if at some future point you need to do something more complex
+than return the value.
+
+Note that using the C<.> twigil has created a method that will provide with
+readonly access to the attribute. If instead the users of this object should be
+able to reset a task's completion state (perhaps to perform it again), you can
+change the attribute declaration:
+
+=begin programlisting
+
+ has Bool $.done is rw;
+
+=end programlisting
+
+X<traits, is rw>
+
+The C<is rw> trait causes the generated accessor method to return something
+external code can modify to change the value of the attribute.
+
+=head1 Methods
+
+X<methods>
+X<classes, methods>
+
+While attributes give objects state, methods give objects behaviors. Ignore
+the C<new> method temporarily; it's a special type of method. Consider the
+second method, C<add-dependency>, which adds a new task to this task's
+dependency list.
+
+=begin programlisting
+
+ method add-dependency(Task $dependency) {
+ push @!dependencies, $dependency;
+ }
+
+=end programlisting
+
+X<invocant>
+
+In many ways, this looks a lot like a C<sub> declaration. However, there are
+two important differences. First, declaring this routine as a method adds it to
+the list of methods for the current class. Thus any instance of the C<Task>
+class can call this method with the C<.> method call operator. Second, a
+method places its invocant into the special variable C<self>.
+
+The method itself takes the passed parameter--which must be an instance of the
+C<Task> class--and C<push>es it onto the invocant's C<@!dependencies>
+attribute.
+
+The second method contains the main logic of the dependency handler:
+
+=begin programlisting
+
+ method perform() {
+ unless $!done {
+ .perform() for @!dependencies;
+ &!callback();
+ $!done = True;
+ }
+ }
+
+=end programlisting
+
+It takes no parameters, working instead with the object's attributes. First, it
+checks if the task has already completed by checking the C<$!done> attribute.
+If so, there's nothing to do.
+
+X<operators, .>
+
+Otherwise, the method performs all of the task's dependencies, using the C<for>
+construct to iterate over all of the items in the C<@!dependencies> attribute.
+This iteration places each item--each a C<Task> object--into the topic
+variable, C<$_>. Using the C<.> method call operator without specifying an
+explicit invocant uses the current topic as the invocant. Thus the iteration
+construct calls the C<.perform()> method on every C<Task> object in the
+C<@!dependencies> attribute of the current invocant.
+
+After all of the dependencies have completed, it's time to perform the current
+C<Task>'s task by invoking the C<&!callback> attribute directly; this is the
+purpose of the parentheses. Finally, the method sets the C<$!done> attribute
+to C<True>, so that subsequent invocations of C<perform> on this object (if this
+C<Task> is a dependency of another C<Task>, for example) will not repeat the
+task.
+
+=head1 Constructors
+
+X<constructors>
+
+Perl 6 is rather more liberal than many languages in the area of constructors.
+A constructor is anything that returns an instance of the class. Furthermore,
+constructors are ordinary methods. You inherit a default constructor named
+C<new> from the base class C<Object>, but you are free to override C<new>, as
+this example does:
+
+=begin programlisting
+
+ method new(&callback, Task *@dependencies) {
+ return self.bless(*, :&callback, :@dependencies);
+ }
+
+=end programlisting
+
+X<objects, bless>
+X<bless>
+
+=for author
+
+What's the C<*> in the C<bless> call?
+
+=end for
+
+The biggest difference between constructors in Perl 6 and constructors in
+languages such as C# and Java is that rather than setting up state on a somehow
+already magically created object, Perl 6 constructors actually create the
+object themselves. This easiest way to do this is by calling the C<bless>
+method, also inherited from C<Object>. The C<bless> method expects a positional
+parameter--the so-called "candidate"--and a set of named parameters providing
+the initial values for each attribute.
+
+The example's constructor turns positional arguments into named arguments, so
+that the class can provide a nice constructor for its users. The first
+parameter is the callback (the thing to do to execute the task). The rest of
+the parameters are dependent C<Task> instances. The constructor captures these
+into the C<@dependencies> slurpy array and passes them as named parameters to
+C<bless> (note that C<:&callback> uses the name of the variable--minus the
+sigil--as the name of the parameter).
+
+=head1 Consuming our class
+
+After creating a class, you can create instances of the class. Declaring a
+custom constructor provides a simple way of declaring tasks along with their
+dependencies. To create a single task with no dependencies, write:
+
+=begin programlisting
+
+ my $eat = Task.new({ say 'eating dinner. NOM!' });
+
+=end programlisting
+
+An earlier section explained that declaring the class C<Task> installed a type
+object in the namespace. This type object is a kind of "empty instance" of
+the class, specifically an instance without any state. You can call methods
+on that instance, as long as they do not try to access any state; C<new> is an
+example, as it creates a new object rather than modifying or accessing an
+existing object.
+
+Unfortunately, dinner never magically happens. It has dependent tasks:
+
+=begin programlisting
+
+ my $eat =
+ Task.new({ say 'eating dinner. NOM!' },
+ Task.new({ say 'making dinner' },
+ Task.new({ say 'buying food' },
+ Task.new({ say 'making some money' }),
+ Task.new({ say 'going to the store' })
+ ),
+ Task.new({ say 'cleaning kitchen' })
+ )
+ );
+
+=end programlisting
+
+Notice how the custom constructor and sensible use of whitespace allows a
+layout which makes task dependencies clear.
+
+Finally, the C<perform> method call recursively calls the C<perform> method on
+the various other dependencies in order, giving the output:
+
+ making some money
+ going to the store
+ buying food
+ cleaning kitchen
+ making dinner
+ eating dinner. NOM!
+
+=head1 Inheritance
+
+Object Oriented Programming provides the concept of inheritance as one of the
+mechanisms to allow for code reuse. Perl 6 supports the ability for one class
+to inherit from one or more classes. When a class inherits from another class
+that informs the method dispatcher to follow the inheritance chain to look
+for a method to dispatch. This happens both for standard methods defined via
+the method keyword and for methods generated through other means such as
+attribute accessors.
+
+TODO: the example here is rather bad, and needs to be replaced (or much
+improved). See L<https://github.com/perl6/book/issues/58> for discussion.
+
+=begin programlisting
+
+ class Employee {
+ has $.salary;
+
+ method pay() {
+ say "Here is \$$.salary";
+ }
+
+ }
+
+ class Programmer is Employee {
+ has @.known_languages is rw;
+ has $.favorite_editor;
+
+ method code_to_solve( $problem ) {
+ say "Solving $problem using $.favorite_editor in "
+ ~ $.known_languages[0] ~ '.';
+ }
+ }
+
+=end programlisting
+
+Now any object of type Programmer can make use of the methods and accessors
+defined in the Employee class as though they were from the Programmer class.
+
+=begin programlisting
+
+ my $programmer = Programmer.new(
+ salary => 100_000,
+ known_languages => <Perl5 Perl6 Erlang C++>,
+ favorite_edtor => 'vim'
+ );
+
+ $programmer.code_to_solve('halting problem');
+ $programmer.pay();
+
+=begin programlisting
+
+=head2 Overriding Inherited Methods
+
+Of course, classes can override methods and attributes defined on ancestoral
+classes by defining their own. The example below demonstrates the Baker class
+overriding the Cook's cook method.
+
+=begin programlisting
+
+ class Cook is Employee {
+ has @.utensils is rw;
+ has @.cookbooks is rw;
+
+ method cook( $food ) {
+ say "Cooking $food";
+ }
+
+ method clean_utensils {
+ say "Cleaning $_" for @.utensils;
+ }
+ }
+
+ class Baker is Cook {
+ method cook( $confection ) {
+ say "Baking a tasty $confection";
+ }
+ }
+
+ my $cook = Cook.new(
+ utensils => (<spoon ladel knife pan>),
+ cookbooks => ('The Joy of Cooking'),
+ salary => 40000);
+
+ $cook.cook( 'pizza' ); # Cooking pizza
+
+ my $baker = Baker.new(
+ utensils => ('self cleaning oven'),
+ cookbooks => ("The Baker's Apprentice"),
+ salary => 50000);
+
+ $baker.cook('brioche'); # Baking a tasty brioche
+
+=end programlisting
+
+Because the dispatcher will see the cook method on Baker before it moves up to
+the parent class the Baker's cook method will be called.
+
+=head2 Multiple Inheritance
+
+As mentioned before, a class can inherit from multiple classes. When a class
+inherits from multiple classes the dispatcher knows to look at both classes when
+looking up a method to search for. As a side note, Perl 6 uses the C3
+algorithm to linearize the multiple inheritance hierarchies, which is a
+significant improvement over Perl 5's approach to handling multiple inheritance.
+
+=begin programlisting
+
+ class GeekCook is Programmer is Cook {
+ method new( *%params ) {
+ %params<cookbooks> //= []; # remove once rakudo fully supports autovivification
+ push( %params<cookbooks>, "Cooking for Geeks" );
+ return self.bless(*, |%params);
+ }
+ }
+
+ my $geek = GeekCook.new(
+ books => ('Learning Perl 6'),
+ utensils => ('blingless pot', 'knife', 'calibrated oven'),
+ favorite_editor => 'MacVim',
+ known_languages => <Perl6>
+ );
+
+ $geek.cook('pizza');
+ $geek.code_to_solve('P =? NP');
+
+=end programlisting
+
+Now all the methods made available by both the Programmer class and the Cook
+class are available from the GeekCook class.
+
+While multiple inheritance is a useful concept to know and on occasion use, it
+is important to understand that there are more useful OOP concepts. When
+reaching for multiple inheritance it is good practice to consider whether the
+design wouldn't be better realized by using roles. For more information on roles
+check out the Roles chapter.
+
+=head1 Introspection
+
+Introspection is the process of gathering information about some objects in
+your program, not by reading the source code, but by querying the object (or a
+controlling object) for some properties, like its type.
+
+Given an object C<$p>, and the class definitions from the previous sections,
+we can ask it a few questions:
+
+=begin programlisting
+
+ if $o ~~ Employee { say "It's an employee" };
+ if $o ~~ GeekCook { say "It's a geeky cook" };
+ say $o.WHAT;
+ say $o.perl;
+ say $o.^methods(:local).join(', ');
+
+=end programlisting
+
+The output can look like this:
+
+=begin screen
+
+ It's an employee
+ Programmer()
+ Programmer.new(known_languages => ["Perl", "Python", "Pascal"], favorite_editor => "gvim", salary => "too small")
+ code_to_solve, known_languages, favorite_editor
+
+=end screen
+
+The first two tests each smart-match against a class name. If the object is
+of that class, or of an inheriting class, it returns true. So the object in
+question is of class C<Employee> or one that inherits from it, but not
+C<GeekCook>.
+
+The C<.WHAT> method returns the type object associated with the object C<$o>,
+which tells the exact type of C<$o>: in this case C<Programmer>.
+
+C<$o.perl> returns a string that can be executed as Perl code, and reproduces
+the original object C<$o>. While this does not work perfectly in all
+casesN<for example closures cannot easily be reproduced this way; if you don't
+know what a closure is don't worry. Also current implementations have problems
+with dumping cyclic data structures this way, but they are expected to be
+handlded correctly by C<.perl> at some point.>, it
+is very useful for debugging simple objects.
+
+Finally C<$o.^methods(:local)> produces a list of methods that can be called
+on C<$o>. The C<:local> named argument limits the returned methods to those
+defined in the C<Employee> class, and excludes the inherited methods.
+
+The syntax of calling method with C<.^> instead of a single dot means that it
+is actually a method call on the I<meta class>, which is a class managing the
+properties of the C<Employee> class - or any other class you are interested
+in. This meta class enables other ways of introspection too:
+
+=begin programlisting
+
+ say $o.^attributes.join(', ');
+ say $o.^parents.join(', ');
+
+=end programlisting
+
+Introspection is very useful for debugging, and for learning the language
+and new libraries. When a function or method
+returns an object you don't know about, finding its type with C<.WHAT>, a
+construction recipe for it with C<.perl> and so on you'll get a good idea what
+this return value is. With C<.^methods> you can learn what you can do with it.
+
+But there are other applications too: a routine that serializes objects to a
+bunch of bytes needs to know the attributes of that object, which it can find
+out via introspection.
+
+=head1 Exercises
+
+B<1.> The method C<add-dependency> in C<Task> permits the creation of I<cycles>
+in the dependency graph. That is, if you follow dependencies, you can
+eventually return to the original C<Task>. Show how to create a graph with
+cycles and explain why the C<perform> method of a C<Task> whose dependencies
+contain a cycle would never terminate successfully.
+
+B<Answer:> You can create two tasks, and then "short-circuit" them with
+C<add-dependency>:
+
+=begin programlisting
+
+ my $a = Task.new({ say 'A' });
+ my $b = Task.new({ say 'B' }, $a);
+ $a.add-dependency($b);
+
+=end programlisting
+
+The C<perform> method will never terminate because the first thing the method
+does is to call all the C<perform> methods of its dependencies. Because C<$a>
+and C<$b> are dependencies of each other, none of them would ever get around to
+calling their callbacks. The program will exhaust memory before it ever prints
+C<'A'> or C<'B'>.
+
+B<2.> Is there a way to detect the presence of a cycle during the course of a
+C<perform> call? Is there a way to prevent cycles from ever forming through
+C<add-dependency>?
+
+B<Answer:> To detect the presence of a cycle during a C<perform> call, keep
+track of which C<Task>s have started; prevent a C<Task> from starting twice
+before finishing:
+
+=for author
+
+It's time to introduce the C<augment> syntax.
+
+Also, C<!$!done> is unnecessarily hard to read if you see ! as a quasi-quoting
+symbol. It's a $ sandwich.
+
+=end for
+
+=begin programlisting
+
+ augment class Task {
+ has Bool $!started = False;
+
+ method perform() {
+ if $!started++ && !$!done {
+ die "Cycle detected, aborting";
+ }
+
+ unless $!done {
+ .perform() for @!dependencies;
+ &!callback();
+ $!done = True;
+ }
+ }
+ }
+
+=end programlisting
+
+Another approach is to stop cycles from forming during C<add-dependency> by
+checking whether there's already a dependency running in the other direction.
+(This is the only situation in which a cycle can occur.) This requires the
+addition of a helper method C<depends-on>, which checks whether a task depends
+on another one, either directly or transitively. Note the use of C<»> and
+C<[||]> to write succinctly what would otherwise have involved looping over all
+the dependencies of the C<Task>:
+
+=begin programlisting
+
+ augment class Task {
+ method depends-on(Task $some-task) {
+ $some-task === any(@!dependencies)
+ [||] @!dependencies».depends-on($some-task)
+ }
+
+ method add-dependency(Task $dependency) {
+ if $dependency.depends-on(self) {
+ warn 'Cannot add that task, since it would introduce a cycle.';
+ return;
+ }
+ push @!dependencies, $dependency;
+ }
+ }
+
+=end programlisting
+
+B<3.> How could C<Task> objects execute their dependencies in parallel? (Think
+especially about how to avoid collisions in "diamond dependencies", where a
+C<Task> has two different dependencies which in turn have the same dependency.)
+
+B<Answer:> Enabling parallelism is easy; change the line C<.perform() for
+@!dependencies;> into C<@!dependencies».perform()>. However, there may be race
+conditions in the case of diamond dependencies, wherein C<Task>s C<A> starts
+C<B> and C<C> in parallel, and both start a copy of C<D>, making C<D> run
+twice. The solution to this is the same as with the cycle-detection in Question
+2: introducing an attribute C<$!started>. Note that it's impolite to die if a
+C<Task> has started but not yet finished, because this time it might be due to
+parallelism rather than cycles:
+
+=begin programlisting
+
+ augment class Task {
+ has Bool $!started = False;
+
+ method perform() {
+ unless $!started++ {
+ @!dependencies».perform();
+ &!callback();
+ $!done = True;
+ }
+ }
+ }
+
+=end programlisting
+
+=for authors
+
+It occurs to me that the above solution might not be 100% thread-safe. Is
+there a good way to guarantee, in Perl 6, that the attribute C<$!started>
+gets checked-and-set atomically?
+
+=end
+
+=for authors
+
+TODO: Optiionally talk about multi methods
+
+=end authors

0 comments on commit 44e238b

Please sign in to comment.
Something went wrong with that request. Please try again.