/
classtut.pod6
933 lines (730 loc) · 33.9 KB
/
classtut.pod6
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
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
=begin pod :kind("Language") :subkind("Language") :category("tutorial")
=TITLE Classes and objects
=SUBTITLE A tutorial about creating and using classes in Raku
X<|Tutorial,OOP>
Raku provides a rich built-in syntax for defining and using classes. It makes writing classes
expressive and short for most cases, but also provides mechanisms to cover
the rare corner cases.
=head1 A quick overview
Let's start with an example to give an overview:
=begin code
class Rectangle {
has Int $.length = 1;
has Int $.width = 1;
method area(--> Int) {
return $!length * $!width;
}
}
my $r1 = Rectangle.new(length => 2, width => 3);
say $r1.area(); # OUTPUT: «6»
=end code
We define a new I<Rectangle> class using the L<class|/language/objects#Classes> keyword. It has two L<attributes|Attributes>, C<$!length> and C<$!width> introduced with the L<has> keyword. Both default to C<1>. Read only accessor methods are automatically generated. (Note the C<.> instead of C<!> in the declaration, which triggers the generation. Mnemonic: C<!> resembles a closed door, C<.> an open one.)
The L<method|/language/objects#Methods> named C<area> will return the area of the rectangle.
It is rarely necessary to explicitly write a constructor. An automatically inherited default constructor called L<new|/language/objects#Object_construction> will automatically initialize attributes from named parameters passed to the constructor.
=head1 The Task example
As a more elaborate example the following piece of code implements a dependency
handler. It showcases custom constructors, private and public attributes,
methods, and various aspects of signatures. It's not a lot of code, and yet the
result is interesting and useful. It will be used as an example throughout the
following sections.
=begin code
class Task {
has &!callback is built;
has Task @!dependencies is built;
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
X<|Tutorial,class;Tutorial,state>
=head1 X<Class|Tutorial,class;Tutorial,state>
Raku, like many other languages, uses the C<class> keyword to define a
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.
=head1 X<Attributes|Tutorial,attributes;Tutorial,encapsulation;Tutorial,has>
In the C<Task> class, the first three lines inside the block all
declare attributes (called I<fields> or I<instance storage> in other
languages) using the C<has> declarator. Just as a C<my> variable cannot be
accessed from outside its declared scope, attributes are never directly
accessible from outside of the class (this is in contrast to many other
languages). This I<encapsulation> is one of the key principles of object
oriented design.
=head2 X<Twigil C<$!>|Tutorial,twigils;Tutorial,!;Tutorial,&>
The first declaration specifies instance storage for a callback (i.e.
a bit of code to invoke in order to perform the task that an object
represents):
=for code
has &!callback is built;
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 attribute is I<encapsulated>.
Private attributes will not be set by the default constructor by default, which is
why we add the C<is built> trait to allow just that. Mnemonic: C<!> looks like a closed door.
The second declaration also uses the private twigil:
=for code :preamble<class Task {}>
has Task @!dependencies is built;
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 is completed. 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).
X<|Tutorial,.>
X<|Tutorial,accessors>
X<|Tutorial,accessor methods>
=head2 Twigil C<$.>
The third attribute represents the state of completion of a task:
=for code
has Bool $.done;
This scalar attribute (with the C<$> sigil) has a type of C<Bool>. Instead
of the C<!> twigil, the C<.> twigil is used. While Raku does enforce
encapsulation on attributes, it also saves you from writing accessor
methods. Replacing the C<!> with a C<.> both declares a private attribute
and an accessor method named after the attribute. In this case,
both the attribute C<$!done> and the accessor method C<done> are declared.
It's as if you had written:
=begin code
has Bool $!done;
method done() { return $!done }
=end code
Note that this is not like declaring a public attribute, as some languages
allow; you really get I<both> a private attribute 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 returning the value.
=head2 X<C<is rw> trait|Tutorial,is rw>
Note that using the C<.> twigil has created a method that will provide
read-only 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:
=for code
has Bool $.done is rw;
The C<is rw> trait causes the generated accessor method to return a container
so external code can modify the value of the attribute.
=head2 C<is built> trait
=for code
has &!callback is built;
X<|Types,traits;Types,is built>
By default private attributes are not automatically set by the default constructor. (They are private after
all.) In the above example we want to allow the user to provide the initial value but keep the attribute
otherwise private. The C<is built> trait allows to do just that.
One can also use it to do the opposite for public attributes, i.e. prevent them to be automatically
initialized with a user provided value, but still generate the accessor method:
=for code
has $.done is built(False);
Above declaration makes sure one can't construct finished tasks, but still allow users to look if a task is done.
The C<is built> trait was introduced in Rakudo version 2020.01.
=head2 C<is required> trait
X<|Types,traits;Types,is required>
Providing a value for an attribute during initialization is optional by default. Which in the task example
makes sense for all three, the C<&!callback>, the C<@!dependencies> and the C<$.done> attribute. But lets say
we want to add another attribute, C<$.name>, that holds a tasks name and we want to force the user to
provide a value on initialization. We can do that as follows:
=for code
has $.name is required;
=head2 Default values
You can also supply default values to attributes (which works equally for
those with and without accessors):
=for code
has Bool $.done = False;
The assignment is carried out at object build time. The right-hand side is
evaluated at that time, and can even reference earlier attributes:
=begin code :preamble<class Task {}>
has Task @!dependencies;
has $.ready = not @!dependencies;
=end code
Writable attributes are accessible through writable containers:
=begin code
class a-class {
has $.an-attribute is rw;
}
say (a-class.new.an-attribute = "hey"); # OUTPUT: «hey»
=end code
This attribute can also be accessed using the C<.an-attribute> or
C<.an-attribute()> syntax. See also
L<the C<is rw> trait on classes|/language/typesystem#trait_is_rw> for examples
on how this works on the whole class.
X<|Tutorial,class variables>
=head1 Class variables
A class declaration can also include I<class variables>, declared with C<my> or C<our>, which are variables
whose value is shared by all instances, and can be used for things like
counting the number of instantiations or any other shared state. So I<class variables> act similarly to
I<static> variables known from other programming languages.
They look the same as normal (non class) lexical variables (and in fact they are the same):
=begin code
class Str-with-ID is Str {
my $counter = 0;
our $our-counter = 0;
has Str $.string;
has Int $.ID is built(False);
submethod TWEAK() {
$counter++;
$our-counter++;
$!ID = $counter;
}
}
class Str-with-ID-and-tag is Str-with-ID {
has Str $.tag;
}
say Str-with-ID.new(string => 'First').ID; # OUTPUT: «1»
say Str-with-ID.new(string => 'Second').ID; # OUTPUT: «2»
say Str-with-ID-and-tag.new( string => 'Third', tag => 'Ordinal' ).ID; # OUTPUT: «3»
say $Str-with-ID::our-counter; # OUTPUT: «3»
=end code
I<Class variables> are shared by all subclasses, in this case
C<Str-with-ID-and-tag>. Additionally, when the package scope C<our> declarator
is used, the variable is visible via their fully qualified name (FQN), while
lexically scoped C<my> variables are "private". This is the exact behavior that
C<my> and C<our> also show in non class context.
X<|Types,static>
I<Class variables> act similarly to I<static> variables in many other programming
languages.
=begin code
class Singleton {
my Singleton $instance;
method new {!!!}
submethod instance {
$instance = Singleton.bless unless $instance;
$instance;
}
}
=end code
In this implementation of the I<Singleton> pattern a I<class variable> is used
to save the instance.
=begin code
class HaveStaticAttr {
my Int $.foo = 5;
}
=end code
Class attributes may also be declared with a secondary sigil – in a similar
manner to instance attributes – that will generate read-only accessors if the
attribute is to be public.
Default values behave as expected and are assigned only once.
=head1 Methods
X<|Tutorial,methods>
While attributes give objects state, methods give objects behaviors. Back to our C<Task> example. Let's
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 a
task's dependency list:
=begin code :skip-test<incomplete code>
method add-dependency(Task $dependency) {
push @!dependencies, $dependency;
}
=end code
X<|Tutorial,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 it 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 C<perform> method contains the main logic of the dependency handler:
=begin code :skip-test<incomplete code>
method perform() {
unless $!done {
.perform() for @!dependencies;
&!callback();
$!done = True;
}
}
=end code
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.
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.
=head2 X<Private methods|Tutorial,FQN;Tutorial,Private methods>
Just like attributes, methods can also be private. Private methods are declared
with a prefixed exclamation mark. They are called with C<self!> followed by the
method's name. In the following implementation of a C<MP3TagData> class to
extract L<ID3v1|https://en.wikipedia.org/wiki/ID3> metadata from an mp3 file,
methods C<parse-data>, C<can-read-format>, and C<trim-nulls> are private
methods while the remaining ones are public methods:
=begin code
class MP3TagData {
has $.filename where { .IO ~~ :e };
has Str $.title is built(False);
has Str $.artist is built(False);
has Str $.album is built(False);
has Str $.year is built(False);
has Str $.comment is built(False);
has Int $.genre is built(False);
has Int $.track is built(False);
has Str $.version is built(False);
has Str $.type is built(False) = 'ID3';
submethod TWEAK {
with $!filename.IO.open(:r, :bin) -> $fh {
$fh.seek(-128, SeekFromEnd);
my $tagdata = $fh.read(128);
self!parse-data: $tagdata;
$fh.close;
}
else {
warn "Failed to open file."
}
}
method !parse-data($data) {
if self!can-read-format($data) {
my $offset = $data.bytes - 128;
$!title = self!trim-nulls: $data.subbuf($offset + 3, 30);
$!artist = self!trim-nulls: $data.subbuf($offset + 33, 30);
$!album = self!trim-nulls: $data.subbuf($offset + 63, 30);
$!year = self!trim-nulls: $data.subbuf($offset + 93, 4);
my Int $track-flag = $data.subbuf($offset + 97 + 28, 1).Int;
$!track = $data.subbuf($offset + 97 + 29, 1).Int;
($!version, $!comment) = $track-flag == 0 && $!track != 0
?? ('1.1', self!trim-nulls: $data.subbuf($offset + 97, 28))
!! ('1.0', self!trim-nulls: $data.subbuf($offset + 97, 30));
$!genre = $data.subbuf($offset + 97 + 30, 1).Int;
}
}
method !can-read-format(Buf $data --> Bool) {
self!trim-nulls($data.subbuf(0..2)) eq 'TAG'
}
method !trim-nulls(Buf $data --> Str) {
$data.decode('utf-8').subst(/\x[0000]+/, '')
}
}
=end code
To call a private method of another class, the caller has to be trusted by the
callee. A trust relationship is declared with
L<C<trusts>|/language/typesystem#trait_trusts> and the class to be trusted must
already be declared. Calling a private method of another class requires an
instance of that class and the fully qualified name (FQN) of the method. A trust
relationship also allows access to private attributes.
=begin code
class B {...}
class C {
trusts B;
has $!hidden = 'invisible';
method !not-yours () { say 'hidden' }
method yours-to-use () {
say $!hidden;
self!not-yours();
}
}
class B {
method i-am-trusted () {
my C $c.=new;
$c!C::not-yours();
}
}
C.new.yours-to-use(); # the context of this call is GLOBAL, and not trusted by C
B.new.i-am-trusted();
=end code
Trust relationships are not subject to inheritance. To trust the global
namespace, the pseudo package C<GLOBAL> can be used.
=head1 X<Construction|Tutorial,Constructor>
The object construction mechanisms described up to now suffice for most use cases. But if one actually needs
to tweak object construction more than said mechanisms allow, it's good to understand how object construction
works in more detail.
Raku 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<Mu>, but you are
free to override C<new>, as the Task example does:
=begin code
method new(&callback, *@dependencies) {
return self.bless(:&callback, :@dependencies);
}
=end code
X<|Tutorial,bless>
=head2 bless
The biggest difference between constructors in Raku and constructors in
languages such as C# and Java is that rather than setting up state on a
somehow already magically created object, Raku constructors create the
object themselves. They do this by calling the
L<bless|/routine/bless> method, also inherited from L<Mu|/type/Mu>.
The C<bless> method expects a set of named parameters to provide the initial
values for each attribute.
The example's constructor turns positional arguments into named arguments,
so that the class can provide a nicer constructor for its users. The first
parameter is the callback (the thing which will 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).
One should refrain from putting logic other than reformulating the parameters in the constructor, because
constructor methods are not recursively called for parent classes. This is different from e.g. Java.
Declaring C<new> as a C<method> and not as a C<multi method> prevents access to the default constructor.
+So if you intend to keep the default constructor available, use C<multi method new>.
=head2 X<C<TWEAK>|Tutorial,TWEAK>
After C<bless> has initialized the classes attributes from the passed values, it will in turn call C<TWEAK>
for each class in the inheritance hierarchy.
C<TWEAK> gets passed all the arguments passed to bless.
This is where custom initialization logic should go.
Remember to always make C<TWEAK> a L<submethod|Submethod> and not a normal C<method>. If in a class hierarchy a class contains a C<TWEAK> method (declared as a C<method> instead of a C<submethod>) that method is inherited to its subclass and will thus be called twice during construction of the subclass!
=head2 X<C<BUILD>|Tutorial,BUILD>
It is possible to disable the automatic attribute initialization and perform the initialization of
attributes oneself. To do so one needs to write a custom C<BUILD> submethod. There are several edge cases one
needs to be aware of and take into account though. This is detailed in the
L<Object Construction Reference|/language/objects#Object_construction>. Because of the difficulty of using
C<BUILD>, it is recommended to only make use of it when none of the other approaches described above suffices.
X<|Tutorial,submethod DESTROY>
=head1 X<Destruction|Tutorial,DESTROY>
Raku is a garbage collecting language. This means that one usually doesn't need to care about cleaning up objects,
because Raku does so automatically. Raku does not give any guarantees as to when it will
clean up a given object though. It usually does a cleanup run only if the runtime needs the memory, so we
can't rely on when it's going to happen.
To run custom code when an object is cleaned up one can use the C<DESTROY> submethod. It can for example be used to close handles or supplies or delete temporary files that are no longer
going to be used. As garbage collection can happen at arbitrary points during the runtime of our program, even in the middle of some totally unrelated piece of code in a different thread, we
must make sure to not assume any context in our C<DESTROY> submethod.
=begin code
my $in_destructor = 0;
class Foo {
submethod DESTROY { $in_destructor++ }
}
my $foo;
for 1 .. 6000 {
$foo = Foo.new();
}
say "DESTROY called $in_destructor times";
=end code
This might print something like C<DESTROY called 5701 times> and possibly only kicks
in after we have stomped over former instances of C<Foo> a few thousand times. We also can't
rely, on the order of destruction.
Same as C<TWEAK>: Make sure to always declare C<DESTROY> as a C<submethod>.
=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:
=for code :preamble<class Task {}>
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:
=begin code :preamble<class Task {}>
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 code
Notice how the custom constructor and the sensible use of whitespace 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:
=begin code :lang<output>
making some money
going to the store
buying food
cleaning kitchen
making dinner
eating dinner. NOM!
=end code
=head2 X<A word on types|Tutorial,type object;Tutorial,DEFINITE>
Declaring a class creates a new 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. For example, types such as
C<Int> and C<Str> refer to the type object of one of the Raku built-in
classes. One can call methods on these type objects. So there is nothing special
with calling the C<new> method on a type object.
You can use the C<.DEFINITE> method to find out if what you have is an instance
or a type object:
=begin code
say Int.DEFINITE; # OUTPUT: «False» (type object)
say 426.DEFINITE; # OUTPUT: «True» (instance)
class Foo {};
say Foo.DEFINITE; # OUTPUT: «False» (type object)
say Foo.new.DEFINITE; # OUTPUT: «True» (instance)
=end code
In function signatures one can use so called type "smileys" to only accept instances
or type objects:
=begin code
multi foo (Int:U) { "It's a type object!" }
multi foo (Int:D) { "It's an instance!" }
say foo Int; # OUTPUT: «It's a type object!»
say foo 42; # OUTPUT: «It's an instance!»
=end code
=head1 X<Inheritance|Tutorial,inheritance>
Object Oriented Programming provides the concept of inheritance as one of
the mechanisms for code reuse. Raku supports the ability for one
class to inherit from one or more classes. When a class inherits from
another class it 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 C<method> keyword and for methods generated through
other means, such as attribute accessors.
=begin code
class Employee {
has $.salary;
}
class Programmer is Employee {
has @.known_languages is rw;
has $.favorite_editor;
method code_to_solve( $problem ) {
return "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 :preamble<class Programmer {}>
my $programmer = Programmer.new(
salary => 100_000,
known_languages => <Raku Perl Erlang C++>,
favorite_editor => 'vim'
);
say $programmer.code_to_solve('halting problem'),
" will get \$ {$programmer.salary()}";
# OUTPUT: «Solving halting problem using vim in Raku will get $100000»
=end code
=head2 Overriding inherited methods
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 :preamble<class Employee {}>
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' ); # OUTPUT: «Cooking pizza»
say $cook.utensils.raku; # OUTPUT: «["spoon", "ladle", "knife", "pan"]»
say $cook.cookbooks.raku; # OUTPUT: «["The Joy of Cooking"]»
say $cook.salary; # OUTPUT: «40000»
my $baker = Baker.new(
utensils => 'self cleaning oven',
cookbooks => "The Baker's Apprentice",
salary => 50000
);
$baker.cook('brioche'); # OUTPUT: «Baking a tasty brioche»
say $baker.utensils.raku; # OUTPUT: «["self cleaning oven"]»
say $baker.cookbooks.raku; # OUTPUT: «["The Baker's Apprentice"]»
say $baker.salary; # OUTPUT: «50000»
=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.
To access methods in the inheritance chain, use
L<re-dispatch|/language/functions#Re-dispatching> or the
L<MOP|/type/Metamodel::ClassHOW#method_can>.
=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. Raku uses the
L<C3 algorithm|https://en.wikipedia.org/wiki/C3_linearization> to linearize
multiple inheritance hierarchies, which is better than depth-first search
for handling multiple inheritance.
=begin code :preamble<class Programmer {}; class Cook {}>
class GeekCook is Programmer is Cook {
method new( *%params ) {
push( %params<cookbooks>, "Cooking for Geeks" );
return self.bless(|%params);
}
}
my $geek = GeekCook.new(
books => 'Learning Raku',
utensils => ('stainless steel pot', 'knife', 'calibrated oven'),
favorite_editor => 'MacVim',
known_languages => <Raku>
);
$geek.cook('pizza');
$geek.code_to_solve('P =? NP');
=end code
Now all the methods made available to the Programmer and the Cook classes
are available from the GeekCook class.
While multiple inheritance is a useful concept to know and occasionally 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
L<Roles|/language/objects#Roles>.
=head2 The X<C<also>|Tutorial,also declarator> declarator
Classes to be inherited from can be listed in the class declaration body by
prefixing the C<is> trait with C<also>. This also works for the role
composition trait C<does>.
=begin code :preamble<class Programmer {}; class Cook {}>
class GeekCook {
also is Programmer;
also is Cook;
# ...
}
role A {};
role B {};
class C {
also does A;
also does B;
# ...
}
=end code
=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, such as its type.
Given an object C<$o> and the class definitions from the previous sections,
we can ask it a few questions:
=begin code :preamble<class Programmer {}; class Employee {}; class GeekCook {}>
my Programmer $o .= new;
if $o ~~ Employee { say "It's an employee" };
say $o ~~ GeekCook ?? "It's a geeky cook" !! "Not a geeky cook";
say $o.^name;
say $o.raku;
say $o.^methods(:local)».name.join(', ');
=end code
The output might look like this:
=begin code :lang<output>
It's an employee
Not a geeky cook
Programmer
Programmer.new(known_languages => ["Perl", "Python", "Pascal"],
favorite_editor => "gvim", salary => "too small")
code_to_solve, known_languages, favorite_editor
=end code
The first two tests each smartmatch against a class name. If the object is
of that class, or of an inheriting class, it returns C<True>. So the object in
question is of class C<Employee> or one that inherits from it, but not
C<GeekCook>.
The call C<$o.^name> tells us the type of C<$o>; in this case C<Programmer>.
C<$o.raku> returns a string that can be executed as Raku code, and
reproduces the original object C<$o>. While this does not work perfectly in
all cases, it is very useful for debugging simple objects.
N<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<.raku> at some point.>
The syntax of calling a method with C<.^> instead of a single dot means that
it is actually a method call on its I<metaclass>, which is a class managing
the properties of the C<Programmer> class – or any other class you are
interested in. This metaclass enables other ways of introspection too:
=for code :preamble<my $o>
say $o.^attributes.join(', ');
say $o.^parents.map({ $_.^name }).join(', ');
Finally C<$o.^name> calls the C<name> method on the metaobject, which
unsurprisingly returns the class name.
Given an object C<$mp3> and the C<MP3TagData> class definition from the section
L<Private methods|/language/classtut#Private_methods>, we can inquire about its
public methods with C<.^methods>:
=begin code :skip-test<compile time error>
my $mp3 = MP3TagData.new(filename => 'football-head.mp3');
say $mp3.^methods(:local);
# OUTPUT: (TWEAK filename title artist album year comment genre track version
# type Submethod+{is-hidden-from-backtrace}.new)
=end code
X<|Syntax,^methods>
C<$mp3.^methods(:local)> produces a list of L<Method|/type/Method>s that can be
called on C<$mp3>. The C<:local> named argument limits the returned methods to
those defined in the C<MP3TagData> class and excludes the inherited methods;
C<MP3TagData> inherits from no class, so providing C<:local> makes no difference.
X<|Language,find_method>
To check if a type object (or an instance object) implements a certain public
method, use the L<C<.^find-method>|/routine/find_method> metamethod, which
returns the method object if it exists. Otherwise, it returns L<C<Mu>|/type/Mu>.
=begin code :skip-test<compile time error>
say $mp3.^find_method('name'); # OUTPUT: «(Mu)»
say $mp3.^find_method('artist'); # OUTPUT: «artist»
=end code
X<|Language,private_methods>
X<|Language,find_private_methods>
Type objects can also be introspected for its private methods. However, public
and private methods don't use the same APIs, and thus different metamethods
must be used: L<C<.^private_methods>|/routine/private_methods> and
L<C<.^find_private_method>|/routine/find_private_method>.
=begin code :skip-test<compile time error>
say $mp3.^private_methods; # OUTPUT: «(parse-data can-read-format trim-nulls)»
say $mp3.^find_private_method('parse-data'); # OUTPUT: «parse-data»
say $mp3.^find_private_method('remove-nulls'); # OUTPUT: «(Mu)»
=end code
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, by finding its type with C<.^name>, seeing a construction recipe
for it with C<.raku>, and so on, you'll get a good idea of what its return
value is. With C<.^methods>, you can learn what you can do with the class.
But there are other applications too. For instance, 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.
=head2 X<Overriding default gist method|Reference,Overriding default gist method>
Some classes might need their own version of C<gist>, which overrides the terse
way they are printed when called to provide a default representation of the class.
For instance, L<exceptions|/language/exceptions#Uncaught_exceptions> might want
to write just the C<payload> and not the full object so that it is clearer what
to see what's happened. However, this isn't limited to exceptions; you can
do that with every class:
=begin code
class Cook {
has @.utensils is rw;
has @.cookbooks is rw;
method cook( $food ) {
return "Cooking $food";
}
method clean_utensils {
return "Cleaning $_" for @.utensils;
}
multi method gist(Cook:U:) { '⚗' ~ self.^name ~ '⚗' }
multi method gist(Cook:D:) {
'⚗ Cooks with ' ~ @.utensils.join( " ‣ ") ~ ' using '
~ @.cookbooks.map( "«" ~ * ~ "»").join( " and ") }
}
my $cook = Cook.new(
utensils => <spoon ladle knife pan>,
cookbooks => ['Cooking for geeks','The French Chef Cookbook']);
say Cook.gist; # OUTPUT: «⚗Cook⚗»
say $cook.gist; # OUTPUT: «⚗ Cooks with spoon ‣ ladle ‣ knife ‣ pan using «Cooking for geeks» and «The French Chef Cookbook»»
=end code
Usually you will want to define two methods, one for the class and another for
class instances; in this case, the class method uses the alembic symbol, and the
instance method, defined below it, aggregates the data we have on the cook to show
it in a narrative way.
=end pod