At 8:41 AM -0800 1/7/08, Paul Hodges wrote:
A small tangent that might be relevant -- what's the current convention
for, say, putting several related "packages" in the same file?

I do that frequently in my Perl modules, and so do modules like DBI. I believe that it is a good idea to do some package grouping into common files (though not necessarily having all packages in one file).

Moreover, I belive that the common practice of unconditionally having a separate file for each package is very much a design smell and a bad idea.

My suggested convention, which applies to both Perl 6 and Perl 5, is to group together in one file any packages that conceptually provide a single API which is used as a whole, and separate into different files any packages that conceptually provide separate APIs and/or separate functionality that is always used from optional extensions. Optional alternately meaning highly likely to be substituted by users for some other packages, is good to keep separate.

For example, with DBI, you have one main file (DBI.pm) that declares a package for creating a DBMS connection object, and a package for the DBMS handle role, and a package for the statement handle role. Then for each DBI driver, you have 1 file that contains multiple packages, 1 each for implementing a DBMS handle and statement handle, et al. Various utilities used by DBI or DBD modules, such as SQL::Statement, are a separate file again, as while DBI may use it internally, it isn't part of the API of DBI.

For another example, if you had your own implementation of some collection data type, eg a graph structure where you would have one package representing a node object and another one representing the whole graph, and users would interact with both directly, then those 2 packages should be stored in the same file.

In fact, this also illustrates circular dependencies between objects (a graph is made of nodes, a node is useless outside a graph), and in cases like these, definitely they should be together.

One rule of thumb is to look at your ideal documentation strategry. If you would naturally explain a component of your project such that multiple object types are conceptually joined at the hip, such that you need to understand all of the objects in order to understand one of them (see the graph example), then it makes sense to put the parts of the component in one file, and you can then document the 2 of them collectively rather than forcing separate documentation for each one that has to constantly refer back and forth to each other.

My Muldis DB project (with separate Perl 6 and Perl 5 versions) version 0.6.0 has 13 packages grouped into 3 files:

1. Interface.pm (no deps) has Muldis::DB::Interface, ::Machine, ::Process, ::Var, ::FuncBinding, ::ProcBinding

2. Validator.pm (dep on Intf) has Muldis::DB::Validator

3. Example.pm (dep on Intf) has Muldis::DB::Engine::Example, ::Machine, ::Process, ::Var, ::FuncBinding, ::ProcBinding

Version 0.7.0 (unreleased) has at least 13 packages grouped into this 1 additional file:

1. Value.pm (no deps, used by Example) has Muldis::DB::Engine::Example::Value, ::Universal, ::Scalar, ::Bool, ::Int, ::Rat, ::Blob, ::Text, ::QuasiTuple, ::Tuple, ::QuasiRelation, ::Relation, ::Cat_Order.

2. Further expected files would be like Operator.pm, Storage.pm, Runtime.pm, Compiler.pm; these would depend on Value.pm and be used by Example.pm, and each could potentially have multiple packages, though just 1 each is also possible.

3. Value|Operator.pm (optional user-caused 'require' at runtime) just has the Core data types and operators; for language extensions, they would probably add extra files per extension, such as ::Value::Temporal.pm and ::Operator::Temporal.pm et al.

I think that this approach strikes a good balance serving ease of use, ease of maintenance, and resource efficiency.

In p5, I might write a great Foo.pm that loads Foo::Loader.pm and
Foo::Parser.pm and Foo::Object.pm; I'd usually drop them into seperate
files and have one load the rest, so I could just use Foo; and get on
with my code, but that does add some maintenance that could be skipped
if loading the one file automatically grabbed all the relevant parts.
Plusses and minuses there.... If the Foo:Widget and Foo:Gadget are only
of use with the something in Foo proper, maybe it would be reasonable
to toss them into the same file, though.
<snip>

As for your example, I would probably keep object/loader/parser as 3 files, especially if the loader/parser is likely to be subsituted by users for alternatives that use the same object, or there are situations where object would be used but the others wouldn't. Or combine if they are very simple. But really, you know better to make this call yourself, having more information on your circumstances.

-- Darren Duncan

P.S. Does anyone think that the main part of this email may provide a starting point for a general best practices tutorial item or Perl.com article?

Reply via email to