Fun with overload

Spoiler alert: If you are participating in the DFW.pm February thought exercise, this post is about my solution :)

DFW.pm challenged its members with the following exercise:

Pivot a multi-row/multi-column table, 4X4 in size for example, containing name-value pairs. Code should account for larger table sizes with any number of name-value pairs. Numbering the pairs is optional but encouraged for readability. An example would be as follows:

THE SOURCE TABLE
1. name | tommy | 5. pet   | fish    
2. lang | Perl  | 6. kids  | four   
3. eyes | blue  | 7. food  | pizza 
4. lbs  | n/a   | 8. hello | world 

PIVOTED RESULT TABLE
1. name | tommy | 2. lang  | perl   
3. eyes | blue  | 4. lbs   | n/a    
5. pet  | fish  | 6. kids  | four   
7. food | pizza | 8. hello | world

If you happened to read my previous post, you'll know that I'm not one to seek the most terse solution to a problem. In fact, for this exercise I was most interested in specifications left unspoken:

  1. What happens when the data set is larger than the dimensions of the table?
  2. What if the key value pairs were objects instead?

It turns out (as usual) there are a plethora of modules dating back to the relatively far past to handle tabular data. There are an equally impressive number of modules to handle pagination. While I could (maybe should) have used any number of these solutions in conjunction with one another to write a script to solve the challenge, I had a very specific contract in mind for the way objects would be treated when the table was rendered; And thus, I decided to add yet another data-table-paginator-whatsit to an already crowded namespace.

The result of this effort is Data::PaginatedTable, and here is the script that uses this new fangled module to solve the DFW.pm exercise.

#!/usr/bin/env perl
use 5.18.2;
use Modern::Perl;
use Data::PaginatedTable;
use Moops;

class Tabifier {
    use overload '""' => 'tabify';
    has 'key'   => ( is => 'ro', isa => Str, required => true );
    has 'value' => ( is => 'ro', isa => Str, required => true );

    method tabify {
        "\t" . $self->key . "\t" . $self->value;
    }
}

my $key_value_pairs = [
    [qw( name tommy )], [qw( lang Perl )],
    [qw( eyes blue )],  [qw( lbs  n/a )],
    [qw( pet  fish )],  [qw( kids four )],
    [qw( food pizza )], [qw( hello world )],
];

my @data =
  map { Tabifier->new( key => $_->[0], value => $_->[1] ) } @$key_value_pairs;

my $pt = Data::PaginatedTable->new(
    {
        data           => \@data,
        columns        => 2,
        rows           => 4,
        string_mode    => 'preformatted',
        fill_direction => 'vertical'
    }
);

say $pt;

#        name    tommy   pet     fish
#        lang    Perl    kids    four
#        eyes    blue    food    pizza
#        lbs     n/a     hello   world
#

$pt->fill_direction('horizontal');
say $pt;

#        name    tommy   lang    Perl
#        eyes    blue    lbs     n/a
#        pet     fish    kids    four
#        food    pizza   hello   world

I used overload to have the string context of a Data::PaginatedTable instance return the current page of the table, with each of the table's elements also stringified. As the script demonstrates above, table content objects may also employ string overload to dispatch a rendering method that is appropriate to themselves. This pattern can be applied to nested objects (turtles all the way down!), thereby making each object in the hierarchy responsible for its own display logic and creating a nice separation of concerns.

Using overload let's you extend Perl DWIMmery convenience to your own classes. If you've never seen what all it can do for you, have a look!

Leave a comment

About Camspi

user-pic Yet another Perl enthusiast! <3