/
classtut.pod
489 lines (377 loc) · 17.4 KB
/
classtut.pod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
=begin pod
=TITLE Classes and Objects
=DESCRIPTION A tutorial for creating and using classes in Perl 6
=comment More descriptive title?
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 code
class Task {
has &.callback;
has Task @.dependencies;
has Bool $.done;
method new(&callback, *@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 code
=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:
my $obj = Int;
if $obj.defined {
say "Ordinary, defined object";
} else {
say "Type object";
}
=head1 I can haz 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:
has &!callback;
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:
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<Task> class (or some
subclass of it).
The third attribute represents the state of completion of a task:
has Bool $.done;
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:
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<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:
has Bool $.done is rw;
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.
method add-dependency(Task $dependency) {
push @!dependencies, $dependency;
}
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:
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<|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:
method new(&callback, Task *@dependencies) {
return self.bless(:&callback, :@dependencies);
}
X<|objects, bless>
X<|bless>
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<bless>
method, also inherited from L<Mu>. The C<bless> method expects 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:
my $eat = Task.new({ say 'eating dinner. NOM!' });
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:
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<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 code
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 code
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 code
my $programmer = Programmer.new(
salary => 100_000,
known_languages => <Perl5 Perl6 Erlang C++>,
favorite_editor => 'vim'
);
$programmer.code_to_solve('halting problem');
$programmer.pay();
=end code
=head2 Overriding Inherited Methods
Of course, classes can override methods and attributes defined by parent
classes by defining their own. The example below demonstrates the C<Baker> class
overriding the C<Cook>'s C<cook> method.
=begin code
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 ladle 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 code
Because the dispatcher will see the C<cook> method on C<Baker> before it moves up to
the parent class the C<Baker>'s C<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 code
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 => ('stainless steel pot', 'knife', 'calibrated oven'),
favorite_editor => 'MacVim',
known_languages => <Perl6>
);
$geek.cook('pizza');
$geek.code_to_solve('P =? NP');
=end code
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<sec:roles>.
=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:
=for output
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
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
handled 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:
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