Skip to main content

Creating Perl5 Objects with Moxie

Having in the previous article prepared data types for car suits and card ranks, I can now combine them to provide a playing card class, using Stevan Little's Moxie module (version 0.04, so definitely early days.) The goal is to provide an object-oriented paradigm to the Perl 5 programming language which is more sophisticated, more powerful and less verbose than manually bless()-ing hashes. To achieve that goal it needs to be faster and light-weight compared to Moose. Currently, Moxie.pm and and MOP.pm are add-on modules, but eventually, when they are more complete, when the wrinkles have been ironed out, and when they have gained acceptance and a community of users, they might be merged into the Perl core.

One significant feature of Moxie is that it reduces boilerplate code. You don't have to specify warnigns or strict. As well, the features or the perl you are using are enabled, among them say, state, signatures, and post_deref.

A Simple Moxie Class

package Card { use Moxie; use Suits; use Ranks; extends 'Moxie::Object'; has _rank => sub { }; sub rank : ro(_rank); has _suit => ( default => sub { }); sub suit : ro(_suit); sub BUILDARGS : init_args( suit => _suit, rank => _rank ); # ... }
A package provides the namespace for a class. The crucial steps are to use the Moxie module; to declare we are a subclass of Moxie::Object, and potentially also of a some class we have defined; and to declare attributes and accessors. An attribute must specify a subroutine to provide a default value, in case it has not been initialized. In the example above I demonstrate both styles of doing this.

At the moment there is no mechanism to declare or to verify the type of an attribute, but I suspect that is inevitable at some point. It is not possible, at least currently, to have an attribute share a name with its accessor. The solution is to mark the attribute with a leading underscore, the conventional indicator for a private attribute. In fact the result of the code is a conventional blessed hash object, so it is possible to access the attribute directly: $card->{_suit}, but this is definitely not a best practice. If you're going to do that, why bother with Moxie in the first place?

The accessor can be read-only, write-only, or read-write. The underlying hash element remains readable and writable. Besides the accessor, it is possible to define a predicate, to detect whether the attribute has been assigned or not, and a clearer, to reset an attribute to undefined. As well, a method can be marked to delegate its operation to the underlying attribute, invoking a method of the same name.

The initalizer, BUILDARGS is shown with mandatory arguments. This is inevitable in this case since the attributes only have read-only accessors. An uninitialized object would remain so forever. But in cases where an attribute can be initialized in the constructor or can accept the default value, a '?' can be appended to the pair tag, to indicate the tag may or may not be present in the initializer list or pairs.

Completing the Class

my $FMT = "%2s%s"; sub format ( $self, $format = '' ) { my $old_format = $FMT; if ( $format eq '' && 0 == length ref $self ) { $format = $self; } $FMT = $format if $format; return $old_format; } sub to_string ($self) { return sprintf $FMT, $self->rank(), $self->suit(); } sub print ($self) { say $self->to_string(); } sub compare_by_rank ( $self, $card ) { return Ranks::compare( $self->rank(), $card->rank() ) # increasing rank || Suits::compare( $self->suit(), $card->suit() ) # increasing ruit } sub compare_by_suit ( $self, $card ) { return Suits::compare( $self->suit(), $card->suit() ) # increasing ruit || Ranks::compare( $self->rank(), $card->rank() ) # increasing rank } sub gen_deck { my @cards; for my $suit ( Suits::suits() ) { for my $rank ( Ranks::ranks() ) { push @cards, Card->new( suit => $suit, rank => $rank ); } } return \@cards; }
Moxie can't do all the work, you have to write some code yourself. But once you no longer have to deal with repetitive implementation details, it becomes easier to focus on the important aspects of your class. And smaller code is easier to understand.

In this case, I provide a method to express the card as a string, and a method to print that representation. The to_string() method will be important in other classes, as the basis for outputing a single card out of a set of cards. I'm using a class attribute $FMT, and I provide a setter/getter method, format(). This can be invoked as an object method, $card->format(), or as a class routine, Card::format(). If an argument is provided, that value is assigned to $FMT. In either case, the old value of the variable is returned to the user. While it might be possible to use a true attribute to specify the format for outputing a card, it would be irritating to have to asign each card of the deck with its format.

Two compare routines combine the routines provided in the Suits and Ranks modules. A hand should be displayed sorted. For games like Fish or Canasta which focus on collecting cards of a rank, you want cards sorted by rank and then by suit. Games like bridge sort cards by suit and then by rank. The actual selection of the appropriate compare routine will take in another class representing a collection of cards.

Finally, a routine is provide to generate a deck of cards. It is convenient to provide it at this level, so that knowledge of and access to Suits and Ranks are limited to this one class.

Comments

Popular posts from this blog

Perl5, Moxie and Enumurated Data Types

Moxie - a new object system for Perl5 Stevan Little created the Moose multiverse to upgrade the Perl 5 programming language's object-oriented system more in line with the wonderfull world of Perl 6. Unfortunately, it's grown into a bloated giant, which has inspired light-weight alternatives Moos, Moo, Mo, and others. Now he's trying to create a modern, efficient OO system that can become built into the language. I've seen a few of his presentations at YAPC (Yet Another Perl Conference, now known as TPC, The Perl Conference), among them ‎p5 mop final final v5 this is the last one i promise tar gz While the package provides some POD documentation about the main module, Moxie, it doesn't actually explain the enum package, Moxie::Enum. But delving into the tests directory reveals its secrets. Creating an Enum package Ranks { use Moxie::Enum; enum by_ARRAY => qw( unused 2 3 4 5 6 7 8 9 10 J Q K A ); enum by_HASH => { 2 => 2, 3 =...

Book review: 390+ Python Interview Questions and Answers

I downloaded a preview portion of 390+ Python MCQs from Anazon, thinking reading through it would help me advance my Python skills beyond what I have learned from Harvard’s online CS50P (Python) course. I’m an experienced program looking to add a new skill to my repertoire, and while the course covered many significant aspects of Python programming, there are many other details to perfect, such as best practices, developing packages, and so on. The book is written by Manish Dnyandeo Salunke, who claims 15 years experience in IT,  but it is not clear who published it. It is obvious no one edited it, or verified the correctness of the questions, answers and explanations. Amazon allowed me to download a sample of (I think) 57 questions. Roughly half of these were wrong, and some of the others struck me as irrelevant. The maximum allowed length for an identifier, apparently, is 79 characters. Anything over 20 characters should be considered unusual, so sufficient to say the limit is se...

BASH Matrix Multiplication

tl;dr Bash is not the language for math-intensive operations. REPS=$1; FILE_1=$2; FILE_2=$3 OUTFILENAME=$4; readonly COLS=`head -1 $FILE_1 | wc -w`; readonly ROWS=`cat $FILE_1 | wc -l`; # echo "rows is $ROWS; cols is $COLS" if [[ $ROWS != $COLS ]]; then echo "Expecting square matrices, " \ "but rows = $ROWS, cols = $COLS\n"; exit 1; fi # -------------------------------------------------- # SUBROUTINES # function outputMatrix() { local matrixname=$1; local matrix; local elem; echo "matrix is '$matrixname'."; eval matrix=\( \${${matrixname}[@]} \); local i=0; for elem in "${matrix[@]}"; do echo -n "$elem "; if (( ++i == $COLS )); then echo ''; i=0; fi done } function multiply() { declare -a product; local M=$1 N=$2; local i j k idx1 idx2 idx3; for ((i=0; i < $ROWS; i++ )); do for ((j=0; j<$COLS; j++)); do ...