Author: Richard A. O'Keefe <ok(at)cs(dot)otago(dot)ac(dot)nz>, Per Gustafsson <pergu(at)it(dot)uu(dot)se> Status: Draft Type: Standards Track Created: 10-Aug-2007 Erlang-Version: R12B-0 Post-History:
This EEP describes a new directive called
export_to which allows a
module to specify exactly which other modules that can call a function
defined in the module. This provides a very fine grained primitive for
encapsulation. Allowing the programmer to control more directly how
his code should be used.
This is an idea originally proposed by Richard O'Keefe.
This is the syntax for the
[m,...] is a list of module names and ``[f/a,...]''
is a list of function name/arity pairs.
This means that the function
f/a can be called from the module
m. Whether the call is a static call, a dynamic call,
or an apply should not matter.
Except for the restriction to a specified set of modules,
these functions should act like functions exported to the
-export, i.e., calls to these functions
should always invoke the latest version of the function.
When a list has only one element, it may be abbreviated to just that element, so
-export_to(m, [f/a,...]). -export_to(m, f/a). -export_to([m,...], f/a).
are allowed abbreviations.
A function may be explicitly exported to any number of modules.
An Erlang compiler should allow a function to be both explicitly
exported to one or more modules with
-export_to and also
exported to the world with
-export, in order to ease the
transition, but should by default warn about this.
export_all flag should also be compatible with
Modules in Erlang have several roles. A module is the unit of compilation and code reloading. It is also the unit of encapsulation, because the only way to hide a function from being called by any other function is to make it a local function in a module. A module also defines a separate namespace for functions. This wealth of roles for the module makes it difficult to structure Erlang applications in a way that makes them well encapsulated while keeping modules small and focused.
Most applications written in Erlang consist of several smaller modules, and though the application has a public API which is a subset of the exported functions of the modules, exactly which functions are part of the public API can only be specified in comments or documentation since functions are either exported so that anyone can call them, or not exported meaning that they can only be called inside the module, but sometimes we want to have more control than this, e.g., this function should only be called from other modules in this application, or this function should only be called from the shell.
-export_to directive lets the programmer
express such restrictions, which the runtime system then enforces,
so that readers can trust them.
-export_to directive is not meant to replace
-export directive, but to be an alternative when the
programmer knows all intended collaborators.
This feature was originally inspired by the way the programming language Eiffel controls the visibility of a class's features to other classes, so Bertrand Meyer deserves the credit. This means, of course, that the idea is "proven technology".
There are some choices in designing the
for example should m be allowed to be a list of modules or
should we have an
export_to list where each entry is a module,
One reason to use the suggested syntax is that it reads pretty easily as:
export to module
m this list of functions
One way to think about this is that there is an "export" matrix where the rows are indexed by modules and the columns are indexed by local functions. Sometimes it is convenient to slice it by rows (this behaviour module may call these callbacks) and sometimes it is convenient to slice it by columns (this utility function may be called by all these modules in my application). The original design focus was on callbacks so that they could be exported to a behaviour without exporting them to the world. Since then it has become clear that exporting to an application is also a convenient thing. So let us allow programmers to write the matrix whatever way most clearly expresses their intentions.
Another issue is whether we should have some syntactic sugar for specifying common export patterns such as exporting a set of functions to all the modules in an application or exporting a function in order to make it possible to apply the function or to make it possible to update the code of the function.
In fact exporting to an application is already easy with this
proposal. When you develop an application foo, create a
_modules.hrl with the contents:
-define(_FOO_`_MODULES`, [ mod1 , mod2 ... ]).
Then within a file you may write
-include('_foo_`_modules.hrl'). -export_to(_FOO_`_MODULES`, [f/a,...]).
Further additions to this proposal may be worth discussing once we have some experience with this basic building block.
One issue which perhaps deserves comment is that calling a restricted-export function is just like any other remote call. These seems to be no reason to make it different. In particular, if module A initially exports a function to the world, and module B calls it, and then the author of module A decides to restrict it to B, B should not change.
-export_to directive should be totally backwards
compatible, because since it is not a legal attribute, it currently
causes a syntax error.
This feature has not been implemented yet, but here are some goals that we think the implementation should fulfill:
Ordinary static calls to an
-export_to function should cost the
same as calls to other
The performance of other calls should not be affected by the
The space cost should be O(1) per (m,f,a) matrix entry.
These can be archived by putting most of the machinery to handle this feature in the loader and only using dynamic checks for dynamic calls.