Day 15 – An Object Lesson for the Elven

This post is a continuation of Day 5 – The Elves go back to Grammar School, you may recall that our elfin friends had worked out how to parse all the addresses from the many, many children who had emailed in their wish lists.

edited by L. H. Jones, Public domain, via Wikimedia Commons

Now, as they sing along to “Christmas is Coming”, they realise that their AddressUSA::Grammar parser only covers mainland addresses in the USA. But what about Europe? What about the rest of the world? Oh my…

Could they use Object Orientation of the Raku Programming Language to handle multi-country names and addresses?

Peeking at the Answer

As is traditional for elves, we will start this post with the result:

use Data::Dump::Tree;
use Contact;
my $text;
$text = q:to/END/;
John Doe,
123, Main St.,
Springfield,
IL 62704
END
ddt Contact.new(:$text, country => 'USA');
$text = q:to/END/;
Dr. Jane Doe,
Sleepy Cottage,
123, Badgemore Lane,
Henley-on-Thames,
Oxon,
RG9 2XX
END
ddt Contact.new(:$text, country => 'UK');

This parses each address according to the Grammar in part I and then loads our Raku Contact objects, like this:

.Contact @0
├ $.text =
│ Dr. Jane Doe,
│ Sleepy Cottage,
│ 123, Badgemore Lane,
│ Henley-on-Thames,
│ Oxon,
│ RG9 2XX
│ .Str
├ $.country = UK.Str
├ $.name = Dr. Jane Doe.Str
├ $.address = .Contact::Address::UK @1
│ ├ $.house = Sleepy Cottage.Str
│ ├ $.street = 123, Badgemore Lane.Str
│ ├ $.town = Henley-on-Thames.Str
│ ├ $.county = Oxon.Str
│ ├ $.postcode = RG9 2XX.Str
│ └ $.country = UK.Str

Christmas is saved, now Santa has the structured address info to load into his SatNav … we leave the other geos as an exercise for the elves.

Contact

Here’s the top level Contact.rakumod code:

use Contact::Address;
role Contact {
has Str $.text is required;
has Str $.country is required
where * eq <USA UK>.any;
has Str $.name;
has Address $.address;
submethod TWEAK {
my @lines = $!text.lines;
$!name = @lines.shift;
$!address = AddressFactory[$!country].new.parse:
@lines.join("\n");
}
method Str { ... }
}

Key takeaways here are:

  • we use the built in TWEAK method to adjust our attributes immediately after the object is constructed … in this case parcelling out name and address construction
  • we choose to use the relaxed style of raku OO with public attributes so that (eg.) you can go say $contact.address.street if that’s what you want

Address

Now here is the Contact::Address code:

role Contact::Address is export {
method parse(Str $) {...}
method Str {...}
}
role Contact::AddressFactory[Str $country='USA'] is export {
method new {
Contact::Address::{$country}.new
}
}
class Contact::Address::USA does Contact::Address {
has Str $.street;
has Str $.city;
has Str $.state;
has Str $.zip;
has Str $.country = 'USA';
method parse($address is rw) {
#load lib/Contact/Address/USA/Parse.rakumod
use Contact::Address::USA::Parse;
my %a = Contact::Address::USA::Parse.new: $address;
self.new: |%a
}
method Str { ... }
}
class Contact::Address::UK does Contact::Address {
has Str $.house;
has Str $.street;
has Str $.town;
has Str $.county;
has Str $.postcode;
has Str $.country = 'UK';
method parse($address is rw) {
#load lib/Contact/Address/UK/Parse.rakumod
use Contact::Address::UK::Parse;
my %a = Contact::Address::UK::Parse.new: $address;
self.new: |%a
}
method Str { ... }
}

You might recognise these classes from the previous post … now we have refactored the classes into a single common Address module and this gives the coding flexibility to keep all classes & methods separate, or to evolve them to move common code into composed roles.

Highlights are:

  • Girl, that’s really clear! It shows how raku objects can be used to contain real-world data, keeping the “labels” (house, street, city, etc) as has attributes.
  • It shows the application of an API definition role that stubs required methods with the { … } syntax (these methods are then mandatory for any class that does the role
  • It shows the application of a parameterized role – in this case the $country parameter can be specified via a Factory class pattern (with suitable default)
  • This allows for USA and UK variants (and, in future, others) to be checked with a where clause and then to be instanced as consumer of the Contact::Address role
  • Each branch of the factory will create a country-specific instance such as class Contact::Address::UK, class Contact::Address::USA, and more can be added
  • Each of these child objects has a .parse method as required by the API and that, in turn, loads the implementation with (eg.) use Contact::Address::UK::Parse, which loads the class Contact::Address::UK::Parse child to perform the Grammar and Actions specific to that country

This code is intended to make it’s way into a new raku Contact module … that’s work in progress for now, but you are welcome to view / raise issues / make PRs if you would like to contribute…

https://github.com/librasteve/raku-Contact

There are some subtleties in here…. for one, I used an intermediate Hash variable %a to carry the attribute over from the parser to the object:

my %a = Contact::Address::UK::Parse.new: $address;
self.new: |%a

The following line would have been more compact, but I judge it to be less readable code:

self.new(Contact::Address::UK::Parse.new: $address).flat;

Tree

And since no Christmas is complete without a tree, this is how it all looks in the Raku Contact module lib:

raku-Contact/lib > tree
.
├── Contact
│   ├── Address
│   │   ├── GrammarBase.rakumod
│   │   ├── UK
│   │   │   └── Parse.rakumod
│   │   └── USA
│   │   └── Parse.rakumod
│   └── Address.rakumod
└── Contact.rakumod

5 directories, 5 files

This gives us a clear class & role hierarchy with extensibility such as more attributes of Contact (email, phone anyone?) and international coverage (FR, GE and beyond).

It keeps the Grammar and Action classes of the country-specific parsers together since they have an intimate context. And, since they are good citizens in the Raku OO model, they sit naturally in the tree.

Fröhliche Weihnachten!

…said Père Noël. Dammit, said the naughty elf, we’ve hardly started on the anglophone addresses and now we need to cope with all these accents and hieroglyphs (not to mention pictographic addresses).

Stay cool said Rudi, for he knew a thing or two about Raku’s super power regexes and grammars with Unicode support built right in.

And off he went to see if his Goose was cooked.

<Merry Christmas>.all

~librasteve

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.