[erlang-questions] Defining static functions in abstract modules
Wed Apr 9 12:52:32 CEST 2008
Matthew Dempsky wrote:
> I have an abstract module zap. I have a client module quux.
> quux should not know about zap's internals, in particular the abstract
> module parameters. I can hide the abstract module parameters by
> writing a custom zap:new function to handle instantiating the zap
> However, because zap:new is not entirely trivial, I would like to
> define helper functions. These helper functions don't make sense
> outside of implementing zap, so I'd prefer to define them within the
> same file as zap:new.
> I could define a zap_static module for the static helper functions,
> but then I have to do extra work when changing the parameters to the
> helper functions.
One issue that should be considered, though, is that of code update.
Calls M:f(...) to abstract modules are, by design, using dynamic lookup
of the implementation module, just as for remote calls foo:bar(...).
This is so that you can fix bugs and load a new version of the module,
and all existing instances will use the updated code.
However, if you decide to extend, reduce, or reorder, the module parameters
of the abstract module, e.g. change -module(m, [X]) to -module(m, [X, Y]),
this will break the calling conventions of existing instances of m. For this
reason, I think that abstract modules should probably not be used as both
implementation and client interface simultaneously.
What one can do instead, is to let zap (a normal, static module, or possibly
an abstract module with extremely stable module parameters) be the client
interface, which in its turn instantiates the abstract module zap_impl.
(Note that the client never actually sees the name zap_impl.)
If you decide to rewrite zap_impl and make incompatible changes to the
module parameters, you could keep your existing instances working, by
naming the new implementation zap_impl_2 (or similar), and change the
interface in zap to use this module instead from now on. Changing zap
does not break anything, and all new instances will be using zap_impl_2.
(Of course, existing instances don't get the benefits of the new code.)
And you can repeat this procedure as many times as you like.
A middle ground that supports code upgrade is to use a single abstract
module both as interface and implementation, but make sure to include
an extra module parameter that can be used for future expansions, e.g.:
-module(m, [X, Y, ModOpts]). This might typically be a property list
(see the proplists module), which defaults to the empty list; or even
a dict or gb_tree for faster lookups. Then, you never need to physically
change the calling conventions, but if many things get added over the
years, it might get rather kludgy.
In any case, the ability to declare static functions should be there,
for when you need it.
More information about the erlang-questions