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?