A block... blocks

TL;DR

Unsuprisingly, a BLOCK in Perl blocks a lot of things, including a package statement.

Back then when I was working at cglib-perl, a small collection of algorithms implemented in Perl to ease my life with the puzzles in CodinGame, I came up with an implementation of the Dijkstra’s algorithm, available in Dijkstra.pm.

The high level structure of the module is that there is a function dijkstra, which returns an object in package/class Dijkstra that is possible to query for getting the output information (distance and/or path between the source and a target node):

package Dijkstra;
...;
sub dijkstra {
   ...;
   return bless {...}, 'Dijkstra';
} ## end sub dijkstra

package Dijkstra; # repetita juvant... especially with cut-and-paste
use strict;

sub path_to     { ... }
sub distance_to { ... }
1;

I didn’t like this arrangement too much, tough, because one of the goals of this library is to be copy-and-paste friendly and that package declaration might be easily overlooked.

This led me to code a mostly equivalent implementation in DijkstraFunction.pm. The difference is that the function returns a hash with two keys pointing to the respective sub references to get the distance_to and the path_to a target node:

package DijkstraFunction;
...
sub dijkstra {
   ...;
   return {
      path_to     => sub { ... },
      distance_to => sub { ... },
   };
} ## end sub dijkstra
1;

There we are: I can easily grab sub dijkstra { ... } only and be done with it. Very, very copy-and-paste friendly!

The same design was also adopted by the implementation of other algorithms, e.g. in FloydWarshall.pm:

sub floyd_warshall {
   ...;
   return {
      has_path => sub { ... },
      distance => sub { ... },
      path     => sub { ... },
   };
}

It works, but still tastes a lot like a poor’s man version of an object-orientedish thing that might be done better.

One first observation that present me has for past me is that the package declaration in the middle of Dijkstra.pm is not strictly necessary, because the following would work as well:

package Dijkstra;
...;
sub dijkstra {
   ...;
   return bless {...}, 'Dijkstra';
} ## end sub dijkstra
sub Dijkstra::path_to     { ... }
sub Dijkstra::distance_to { ... }
1;

This is copy-and-paste friendly-ish because we can copy from sub dijkstra up to sub Dijkstra::distance_to; still I’m not fully happy because it might be easy to eventually disconnect sub dijkstra from the other two as the code evolves, with the potential to lose either one.

Recent me discovered a simple truth that’s been shining under the sun for a long, long time: package declarations are confined to the enclosing BLOCK. In other terms, a BLOCK blocks a package declaration, avoiding it to spill out.

This leads to what should have been the solution in the first place:

package Dijkstra;
...;
{
   sub dijkstra {
      ...;
      return bless {...}, 'Dijkstra';
   } ## end sub dijkstra

   package Dijkstra;
   sub path_to     { ... }
   sub distance_to { ... }
}
1;

This has multiple advantages:

  • it restricts the range (well, scope!) of the package declaration inside the BLOCK (not the one outside) to the BLOCK itself;
  • it does not force sub dijkstra to be inside package Dijkstra (it is in the file, but copy-and-paste can leave that out);
  • it keeps the subs close to one another.

This make it is super copy-and-paste friendly: let’s just get the whole block and we’re done.

If only past me had known this… he would have coded the new Dijkstra.pm 😄


Comments? Octodon, , GitHub, Reddit, or drop me a line!