How to convert a directory into a package?

2019-02-03 09:23发布

问题:

I have a directory with some helper functions that should be put into a package. Step one is obviously naming the directory something like +mypackage\ so I can call functions with mypackage.somefunction. The problem is, some functions depend on one another, and apparently MATLAB requires package functions to call functions in the very same package still by explicitly stating the package name, so I'd have to rewrite all function calls. Even worse, should I decide to rename the package, all function calls would have to be rewritten as well. These functions don't even work correctly anymore when I cd into the directory as soon as its name starts with a +.

Is there an easier solution than rewriting a lot? Or at least something self-referential like import this.* to facilitate future package renaming?


edit I noticed the same goes for classes and static methods, which is why I put the self-referential part into this separate question.

回答1:

In truth, I don't know that you should really be renaming your packages often. It seems to me that the whole idea behind a package in MATLAB is to organize a set of related functions and classes into a single collection that you could easily use or distribute as a "toolbox" without having to worry about name collisions.

As such, placing functions and classes into packages is like a final step that you perform to make a nice polished collection of tools, so you really shouldn't have much reason to rename your packages. Furthermore, you should only have to go through once prepending the package name to package function calls.

... (pausing to think if what I'm about to suggest is a good idea ;) ) ...

However, if you really want to avoid having to go through your package and prepend your function calls with a new package name, one approach would be to use the function mfilename to get the full file path for the currently running package function, parse the path string to find the parent package directories (which start with "+"), then pass the result to the import function to import the parent packages. You could even place these steps in a separate function packagename (requiring that you also use the function evalin):

function name = packagename

  % Get full path of calling function:
  callerPath = evalin('caller', 'mfilename(''fullpath'')');

  % Parse the path string to get package directories:
  name = regexp(callerPath, '\+(\w)+', 'tokens');

  % Format the output:
  name = strcat([name{:}], [repmat({'.'}, 1, numel(name)-1) {''}]);
  name = [name{:}];

end

And you could then place this at the very beginning of your package functions to automatically have them include their parent package namespace:

import([packagename '.*']);

Is this a good idea? Well, I'm not sure what the computational impacts will be if you're doing this every time you call a package function. Also, if you have packages nested within packages you will get output from packagename that looks like this:

'mainpack.subpack.subsubpack'

And the call to import will only include the immediate parent package subsubpack. If you also want to include the other parent packages, you would have to sequentially remove the last package from the above string and import the remainder of the string.

In short, this isn't a very clean solution, but it is possible to make your package a little easier to rename in this way. However, I would still suggest that it's better to view the creation of a package as a final step in the process of creating a core set of tools, in which case renaming should be an unlikely scenario and prepending package function calls with the package name would only have to be done once.



回答2:

I have been exploring answers to the same question and I have found that combining package with private folders can allow most or all of the code to be used without modification.

Say you have

+mypackage\intfc1.m
+mypackage\intfc2.m
+mypackage\private\foo1.m
+mypackage\private\foo2.m
+mypackage\private\foo3.m

Then from intfc1, foo1, foo2, and foo3 are all reachable without any package qualifiers or import statements, and foo1, foo2, and foo3 can also call each other without any package qualifiers or import statements. If foo1, foo2, or foo3 needs to call intfc1 or intfc2, then that needs qualification as mypackage.intfc1 or an import statement.

In the case that you have a large set of mutually interdependent functions and a small number of entry points, this reduces the burden of adding qualifiers or import statements.

To go even further, you could create new wrapper functions at the package level with the same name as private functions

+mypackage\foo1.m          <--- new interface layer wraps private foo1
+mypackage\private\foo1.m  <--- original function

where for example +mypackage\foo1.m might be:

function answer = foo1(some_parameter)
    answer = foo1(some_parameter);  % calls private function, not itself
end

This way, unlike the intfc1 example above, all the private code can run without modification. In particular there is no need for package qualifiers when calling any other function, regardless of whether it is exposed by a wrapper at the package level.

With this configuration, all functions including the package-level wrappers are oblivious of the package name, so renaming the package is nothing more than renaming the folder.