Prologue
Santa [1][2] was browsing the eTrade magazines on his iPad one morning and came across an article referenced in the latest O’Reilly Programming Newsletter about how ancient COBOL is the programming language still used for the bulk of the world’s business software.
He had been aware of that since his huge operations with millions of elves [3] had always been at the forefront of big business practice over the cenruries, and he was very proud of the cutting edge efficiencies in his maximally-automated toy factories.
He had been keeping a keen eye (filled with a twinkle π ) on Larry Wall’s new Raku language since its formal release on Christmas Day in 2015, and decided it was time to incorporate its use in his new five-year plan. (After all, he mused, it is supposed to be the “100 year language.”) He soon called a meeting of his IT staff leaders to get the ball rolling.
At the meeting he handed out copies of Dr. Juan Merelo’s new book, Raku Recipes, to inspire the coding cowboys in the crowd. “Now people, let’s start at the beginning and teach Raku as the initial programming language for IT students at the North Pole University. In the meantime, make sure all current IT coders have a copy of JJ’s book, and I expect them to start learning to use Raku in their spare time,” he said with a chuckle. “And have them all join the #raku
IRC channel,” he added.
A Class on Class
[Some weeks later, Santa was the guest instructor in a beginning Raku class. We listen in as he deals with class design. Santa is speaking…]
And Raku has an easy-to-use but powerful class construction syntax. For example, look at this simple example of a Circle class with the following characteristics:
- immutable after construction
- user enters either the radius or diameter during construction
- area is calculated during construction
- circumference is calculated during construction
- an error is thrown if neither radius nor diameter are entered
$ cat circle-default class Circle { has $.radius; has $.diam; has $.area = $!radius.defined ?? ( $!diam = $!radius * 2; pi * $!radius ** 2 ) !! $!diam.defined ?? ( $!radius = $!diam * 0.5; pi * $!radius ** 2 ) !! die "FATAL: neither radius nor diam are defined"; has $.circum = $!radius.defined ?? ( $!diam = $!radius * 2; pi * $!radius * 2 ) !! $!diam.defined ?? ( $!radius = $!diam * 0.5; pi * $!radius * 2 ) !! die "FATAL: neither radius nor diam are defined"; } say "== enter radius"; my $radius = 3; my $c = Circle.new: :$radius; say "radius: {$c.radius}"; say "diam: {$c.diam}"; say "area: {$c.area}"; say "circum: {$c.circum}"; say "== enter diam"; my $diam = 6; $c = Circle.new: :$diam; say "radius: {$c.radius}"; say "diam: {$c.diam}"; say "area: {$c.area}"; say "circum: {$c.circum}";
What do you notice about the construction? Complicated default generation handling?
What happens with more complicated geometric figures? It gets worse, right?
How can you handle them? Yes, there are submethods that can help: BUILD and TWEAK.
I’m not going to bore you with the gory details but you can read all about them in the fine “docs” to which I’ll refer you later.
Instead, I recommend jumping straight to using TWEAK. It was added soon after the Christmas release because it eases the burden of creating immutable, practical classes.
Take a look at a rewrite of the Circle class using TWEAK.
$ cat circle-tweak class Circle { has $.radius; has $.diam; has $.area; has $.circum; submethod TWEAK { # Here we have access to all attributes and their values entered # in the new method! if $!radius.defined { $!diam = $!radius * 2 } elsif $!diam.defined { $!radius = $!diam * 0.5 } else { die "FATAL: neither radius nor diam are defined" } $!area = pi * $!radius ** 2; $!circum = pi * $!radius * 2; } } say "== enter radius"; my $radius = 3; my $c = Circle.new: :$radius; say "radius: {$c.radius}"; say "diam: {$c.diam}"; say "area: {$c.area}"; say "circum: {$c.circum}"; say "== enter diam"; my $diam = 6; $c = Circle.new: :$diam; say "radius: {$c.radius}"; say "diam: {$c.diam}"; say "area: {$c.area}"; say "circum: {$c.circum}";
In those two short examples, a wc
comparison of the class definition code gives:
$ wc circle-default-class-only circle-tweak-class-only 14 90 541 circle-default-class-only 19 59 430 circle-tweak-class-only 33 149 971 total
The default class version does have fewer lines, but it has more words and characters than the TWEAK version. Not only is the TWEAK version a bit less wordy, I think it’s much easier to maintain and extend. Why optimize whan clarity is much more important? Remember the famous quote by Dr. Donald Knuth, the world-renowned computer scientist and mathemetician, “premature optimization is the root of all evil.”
Now let’s look at a practical case for class submethods. We are rewriting our page layout software for our publishing department. As you may know, we have now started writing PDF directly using David Warring’s excellent, but voluminous, Raku PDF modules, but we also do a lot of automated document production with PostScript. In both cases we use the convention of describing the location of page objects (text, images, etc.) as a 2D reference of x,y coordinates with the default origin at the lower-left corner of the page with the positive x and y axes pointing to the right and up, respectively.
For today’s class exercise, divide yourselves into two-elf teams and come up with a Raku class to describe rectangular areas on a page that will contain text or images. You all had geometry in high school, but perhaps a little review is in order.
A rectangle is a quadrilateral (a four-sided plane figure) with opposite sides parallel and adjacent sides at right angles to each other. Adjacent sides may be of different lengths. Note we will not consider rectangles with zero-length edges as valid.
A free-floating rectangle can be precisely defined by its width and length. A fixed rectangle on an orthogonal x,y plane must have one of its diagonals defined by either the coordinates of its two endpoints or one endpoint and the diagonal’s angle from one of the positive x-axis.
The requirements of our class are as follows:
- immutability after consruction via the default
new
method - defined by the lower-left corner and either (1) the upper-right corner or (2) its width and height
For our exercise observe the following constraints:
- the rectangle’s edges are always parallel to the x or y axes
- the rectangle’s edges have finite length
Your work should have at least the necessary attributes to define and position your class. You should also have code to show the creation of an instance of your class.
Note that as I designed my version of the Box class, I wrote a test for it at the same time. Then I refined each as I continued until I was satisfied with both. The test actually specifies my design, much the same as is done with the Raku language which is defined by its extensive test suite, referred to as roast. I will check your work with that test.
You may start and will have a few minutes to complete the assignment. Raise your hands when finishedβthe first group to finish gets a candy cane. π¬
…
Okay, group A show your class.
$ cat BoxA.rakumod class Box is export { ; ;;; ; ;;; ;;;;; ;;;;;;; ;;has$.h; ;;;;;;;;;;; ;;;has$.urx;; ;;;;;;;;;;;;;;; ;;;has$.ury;;;;;; ;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;has$.w;;; ;;;has$.llx;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;has$.lly;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;;;;;; method width { $!urx - $!llx } }
Ho, ho, ho! Quite the little ASCII artistes aren’t we? π π π€£ Β
Let’s see Python top that! π Now lets try it out…
$ cat testA.raku use lib '.'; use BoxA; my ($llx, $lly, $urx, $ury) = 0, 0, 2, 3; my $o = Box.new: :$llx, :$lly, :$urx, :$ury; say $o.width; $ raku testA.raku 2
Hm, I see at least one problem. You’ve added all the attributes, but the width method relies on attributes that may not have been initialized. What if the user had done this:
$ cat testA2.raku use lib '.'; use BoxA; my ($llx, $lly, $w, $h) = 0, 0, 2, 3; my $o = Box.new: :$llx, :$lly, :$w, :$h; say $o.width; $ raku testA2.raku Use of uninitialized value of type Any in numeric context in method width at BoxA.rakumod (BoxA) line 24 0
Boom! We got an invalid answer plus an error message! How can you change your code to handle the width and length properly? avoid an exception? Another group please take that code and modify it accordingly.
And remove the ASCII art or the reindeer π¦ may think it’s something good to eat, ho, ho, ho! π
Any one? Yes, group C, please show your solution.
$ cat BoxC.rakumod unit module BoxC; class Box is export { has$.llx; has$.lly; has$.urx; has$.ury; has$.w; has$.h; method width { if $!urx.defined { $!urx - $!llx } elsif $!w.defined { $!w } } }
And the same test:
$ cat testC.raku use lib '.'; use BoxC; my ($llx, $lly, $w, $h) = 0, 0, 2, 3; my $o = Box.new: :$llx, :$lly, :$w, :$h; say $o.width; $ raku testA2.raku 2
Okay, great. That solution will work, but why are we not taking advantage of Raku’s default methods to show the values of public attributes? We shouldn’t have to add our own width method, or any other method. Any ideas?
[Santa hears a faint chime from his pocket watch β± and checks its face…]
Okay, Christmas people, Santa is running behind schedule and I have to leave you soon. Besides, I don’t know much more than you do and you’ll have to dig into the “docs” about class construction in order to get the gory details on submethods BUILD and TWEAK and their differences.
Also seek help from more experienced Rakoons (the friendly community of Raku users) on IRC channel #raku
.
Well done, all! And I’m not going to leave you with an unfinished task.
I’m a pragmatic programmer and business man and “the bottom line in practical class construction is to “cut to the chase,” use the TWEAK submethod and “take care of business.”
Please see my final solution on my friend <@tbrowder>’s Github site here. [4] It’s my idea of a practical, robust, and immutable class with the aid of the TWEAK submethod. As they say on IRC, “YMMV” (your mileage may vary).
Now I’m handing out candy canes and sugar plums for everyone in the class, ho, ho, ho! π I do β€οΈ Raku!
Have a very Merry Chistmas π, and a Happy New Year π₯ π to you and your families! Raku on, upward and onward all (and you, too, Rudolf)! π
Santa’s Epilogue
Don’t forget the “reason for the season:” β
As I alway end these ruminants π¦, er, sorry, too many reindeer around, ruminations, in the words of Charles Dickens’ Tiny Tim, “may God bless Us , Every one!” [5]
3 thoughts on “Day 11: Santa Claus TWEAKs with a Class”