Author: Richard A. O'Keefe , Per Gustafsson Status: Draft Type: Standards Track Created: 10-Aug-2007 Erlang-Version: R12B-0 Post-History: **** EEP 5: More Versatile Encapsulation with `export_to` ---- Abstract ======== 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. Specification ============= This is the syntax for the `-export_to` directive: -export_to([m,...], [f/a,...]). where ``[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 world using `-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. The existing `export_all` flag should also be compatible with explicit exports. Motivation ========== 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. The `-export_to` directive lets the programmer express such restrictions, which the runtime system then enforces, so that readers can trust them. The `-export_to` directive is __not__ meant to replace the `-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". Rationale ========= There are some choices in designing the `-export_to` syntax, 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, function/arity pair. One reason to use the suggested syntax is that it reads pretty easily as: export to module `m` this list of functions `[f/a]` 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 file _foo_`_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. Backwards Compatibility ======================= Adding an `-export_to` directive should be totally backwards compatible, because since it is not a legal attribute, it currently causes a syntax error. Implementation ============== 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 `-export_to` functions. * The performance of other calls should not be affected by the introduction of `-export_to` calls. * 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. [EmacsVar]: <> "Local Variables:" [EmacsVar]: <> "mode: indented-text" [EmacsVar]: <> "indent-tabs-mode: nil" [EmacsVar]: <> "sentence-end-double-space: t" [EmacsVar]: <> "fill-column: 70" [EmacsVar]: <> "coding: utf-8" [EmacsVar]: <> "End:"