02.11.2012
A directory can be turned into a Matlab package by prefixing it with a +. For Matlab to find the package, its parent directory must be in the Matlab path. A package, as a directory, can not be added into the Matlab path. The reason for this becomes clear in the next section.
The packages in Matlab are an implementation of modules. The main point of a package is to help prevent name-collisions between functions and classes of the same name in different libraries. This is accomplished by requiring that everything in a package has to, by default, be prefixed by the package name. For example, if the package is named package
, and it contains a function f
, then it has to be called as package.f()
. This is true even for functions in the same package.
Of course, package names may collide too. Using packages just pushes the actual underlying problem farther, the problem being to unambiguously specify the function or class to use given that everyone everywhere can write code with the same names. Collisions in package names is less probable than having collisions in function and class names. This is because the package name is usually chosen deliberately distinctively, and there is only one such name per each library (instead of thousands of functions and classes).
To save on typing, it is possible to bring names in a package into the scope. For example, to be able to call package.f()
simply as f()
, you can do
import package.f
If you want to bring all names in a package into the scope then you can do
import package.*
This is not away from the safety of packages; you are still being explicit on what names you want to use.
The rules for name precedence are that
For an example of these rules, assume the following code organization, where the test
directory has been added into the Matlab path.
test/+package-v1/f.m:
function f()
disp('package-v1.f');
test/+package-v2/f.m:
function f()
disp('package-v2.f');
test/f.m:
function f()
disp('non-package f');
Then this code,
f(2)
import package-v1.*;
f(2)
import package-v2.*;
f(2)
when run in the test
directory, will output this:
non-package f
package-v1.f
package-v1.f
It is useful to be able to use multiple versions of the same library at the same time. For example, this can be used to test the results of a new version against the results of an old version. It is also useful when the versions change in behaviour and/or interfaces. In these cases it is not possible for the user to simply plug in a new version of the library. Rather, the user may have different several libraries which use different, mutually incompatible, versions of your library. Thus, any serious Matlab library must support versioned packages.
Versioned packages are easier said than done. An ideal solution would be a built-in mechanism in Matlab to define aliases for packages (e.g. package = package-v1). Unfortunately, no such mechanism exists in Matlab as of R2011b. Even with support for aliasing, care has to be taken when implementing and using versioned packages, as discussed next.
We will first look at versioned packages from the user’s perspective, as someone who uses a library with multiple different versions.
Since the used packages have to be imported into each and every m-file, one has to be careful when writing code that uses versioned packages. For example, if the explicit syntax package-v1.f()
is used everywhere in the code, then the update to package-v2
requires a more or less painful replacement of package-prefixes. Therefore the idea of explicitly using things in a versioned package should be reconsidered. This can be partly remedied by either introducing aliasing (not currently available), or by always importing names instead. However, this still does not solve the problem completely, since the import (or aliasing) declaration in each m-file still contains a reference to the versioned package (as in import package-v1
).
Mentioning the versioned package can be avoided by indirecting the name of the versioned package to a global function packageVersion()
, which would look like this:
function name = packageVersion()
name = 'package-v1'
The import is then carried out by
import([packageVersion, '.*'])
The implementers perspective is similar to the user’s. In a way he is the user of his own package. However, the packageVersion()
function can not be implemented in the same way as in the user code. The problem is that each version of the library defines a function named internalPackageVersion()
, and then some of them will, most likely incorrectly, take precedence over the others. Therefore, the implementer of a versioned package actually needs the internalPackageVersion()
to return the package from which it was called from. It would be nice if there were such a function in Matlab (to return the containing package of the currently running m-file); as of R2011b there seems to be no such function. Fortunately, this function can be simulated as follows:
function name = internalPackageVersion()
% Find out the m-file of the calling function.
callStack = dbstack('-completenames');
callerPath = callStack(2).file;
packageName = regexpi(callerPath, ...
'\+(package[^\\/]*)', 'tokens', 'once');
name = '';
if ~isempty(packageName)
name = packageName{1};
end
The next problem is that the internalPackageVersion()
function can not be located in the package itself, since calling it would again require to specify the name of the package (precisely what we aim to avoid by using the internalPackageVersion()
function). However, there is a natural location for this function: the parent directory of the package (which must be included in the Matlab path in any case to use the package).
The final problem is that the version of internalPackageVersion()
is still arbitrarily resolved when there are multiple versions of the library in the Matlab path. This can be solved by making sure that this function behaves identically in every version (as would be the case with the function given above). Then it does not matter which version of the function is actually run.
To summarize what has been said on versioned packages:
packageVersion()
(or similar),import([packageVersion, '.*'])
(or similar, more specific imports),internalPackageVersion()
(or similar), import([internalPackageVersion, '.*'])
(or similar, more specific imports), and internalPackageVersion()
must be equivalent between different versions of the package.