From df62c329967e69d3af8a31402082e34a5e55ed4d Mon Sep 17 00:00:00 2001 From: Moritz Lenz Date: Sun, 16 Sep 2012 14:49:08 +0200 Subject: [PATCH] add classtut. Mostly copied from the "Using Perl 6" book --- lib/classtut.pod | 480 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 480 insertions(+) create mode 100644 lib/classtut.pod diff --git a/lib/classtut.pod b/lib/classtut.pod new file mode 100644 index 000000000..8db08056f --- /dev/null +++ b/lib/classtut.pod @@ -0,0 +1,480 @@ +=begin pod + +=TITLE Classes and Objects + +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. + + 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(); + +=head1 Starting with class + +X +X + +X +X +X +X +X + +Perl 6, like many other languages, uses the C 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 +keyword, and behaviors introduced through the C keyword. + +X +X +X<.defined> + +Declaring a class creates a I which, by default, is +installed into the current package (just like a variable declared with +C 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 and C refer to the type object of one of the Perl 6 built- +in classes. The example above uses the class name C so that other code +can refer to it later, such as to create class instances by calling the +C method. + +Type objects are I, in the sense that they return C 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: + + my $obj = Int; + if $obj.defined { + say "Ordinary, defined object"; + } else { + say "Type object"; + } + +=head1 I can haz state? + +X +X + +X +X + +The first three lines inside the class block all declare attributes (called +I or I in other languages). These are storage +locations that every instance of a class will obtain. Just as a C variable can +not be accessed from the outside of its declared scope, attributes are not +accessible outside of the class. This I 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: + + has &!callback; + +X +X +X + +The C<&> sigil indicates that this attribute represents something invocable. +The C character is a I, 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: + + has Task @!dependencies; + +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 class (or some +subclass of it). + +The third attribute represents the state of completion of a task: + + has Bool $.done; + +X +X +X +X + +This scalar attribute (with the C<$> sigil) has a type of C. 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. It's as if you had written: + + has Bool $!done; + method done() { return $!done } + +Note that this is not like declaring a public attribute, as some languages +allow; you really get I 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: + + has Bool $.done is rw; + +X + +The C trait causes the generated accessor method to return something +external code can modify to change the value of the attribute. + +=head1 Methods + +X +X + +While attributes give objects state, methods give objects behaviors. Ignore +the C method temporarily; it's a special type of method. Consider the +second method, C, which adds a new task to this task's +dependency list. + + method add-dependency(Task $dependency) { + push @!dependencies, $dependency; + } + +X + +In many ways, this looks a lot like a C 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 +class can call this method with the C<.> method call operator. Second, a +method places its invocant into the special variable C. + +The method itself takes the passed parameter--which must be an instance of the +C class--and Ces it onto the invocant's C<@!dependencies> +attribute. + +The second method contains the main logic of the dependency handler: + + method perform() { + unless $!done { + .perform() for @!dependencies; + &!callback(); + $!done = True; + } + } + +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 + +Otherwise, the method performs all of the task's dependencies, using the C +construct to iterate over all of the items in the C<@!dependencies> attribute. +This iteration places each item--each a C 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 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'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, so that subsequent invocations of C on this object (if this +C is a dependency of another C, for example) will not repeat the +task. + +=head1 Constructors + +X + +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 from the base class C, but you are free to override C, as +this example does: + + method new(&callback, Task *@dependencies) { + return self.bless(*, :&callback, :@dependencies); + } + +X +X + +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 L +method, also inherited from L. The C method expects a positional +parameter--the so-called I--and a set of named parameters providing +the initial values for each attribute. The C<*> as a candidate tells C +to create a new object and initialize it. + +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 instances. The constructor captures these +into the C<@dependencies> slurpy array and passes them as named parameters to +C (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: + + my $eat = Task.new({ say 'eating dinner. NOM!' }); + +An earlier section explained that declaring the class C 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 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: + + 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' }) + ) + ); + +Notice how the custom constructor and sensible use of whitespace allows a +layout which makes task dependencies clear. + +Finally, the C method call recursively calls the C 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 for discussion. + + 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] ~ '.'; + } + } + +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. + + my $programmer = Programmer.new( + salary => 100_000, + known_languages => , + favorite_edtor => 'vim' + ); + + $programmer.code_to_solve('halting problem'); + $programmer.pay(); + +=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 C class +overriding the C's C method. + + 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 => (), + 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 + +Because the dispatcher will see the C method on C before it moves up to +the parent class the C's C 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. + + class GeekCook is Programmer is Cook { + method new( *%params ) { + %params //= []; # remove once rakudo fully supports autovivification + push( %params, "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 => + ); + + $geek.cook('pizza'); + $geek.code_to_solve('P =? NP'); + +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 of 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, which are generally safer +because they force the class author to explicitly resolve conflicting method +names. For more information on roles see A. + +=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: + + 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(', '); + +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 or one that inherits from it, but not +C. + +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. + +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 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 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, which is a class managing the +properties of the C class - or any other class you are interested +in. This meta class enables other ways of introspection too: + + say $o.^attributes.join(', '); + say $o.^parents.join(', '); + +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. + +=end pod