Call function in package from package

84 views (last 30 days)
How I can use use function present in package from the package? If i have package with:
+mypkg
fun1.m
fun2.m
I would that in fun1.m i can call fun2.m. I only could call fun2.m with mypkg.fun2? There are some alternative?

Accepted Answer

Sayan Saha
Sayan Saha on 11 May 2018
According to the documentation
all references to functions in the package must use the package name prefix, unless you import the package. See the example in the following link for importing package functions:
  5 Comments
Rik
Rik on 21 Apr 2021
Comment posted as flag by @Rob Ewalds:
All responders are unanimous in their response: This is poor design, and introduces significant hassle. Could you please stop ignoring all the feedback on this, and pick this up? We don't need 1000+ new features per release, we need a programming-core that gets the basics right. It's clearly a choice: it's not that you don't have the resources.

Sign in to comment.

More Answers (4)

Danny Kumpf
Danny Kumpf on 30 Nov 2020
I also agree that this is poor design, and here is another reason why: Let's say I have a package +pkg1 that is a dependency of a couple other packages I'm working on (+pkg2 and +pkg3). If I put +pkg1 into +pkg2, then not only do I have to call pkg1's internal functions with pkg1.some_fn(), I also have to preface them with pkg2: pkg2.pkg1.some_fn(). This sucks if I want to use pkg1 as a dependency of pkg3 as well, becuase now I need two versions of pkg1: one that calls its internal functions like pkg2.pkg1.some_fn(), and one that calls them with pkg3.pkg1.some_fn().
Here's my hacky workaround.
Paste the following line to be the first line of every function within +pkg1 that calls another pkg1 function:
eval(sprintf('import %s.*', strjoin(regexp(mfilename('fullpath'), '(?<=+)\w*', 'match'), '.')));
This line:
  • Gets the full file path, eg C:\repos\+pkg2\+pkg1\some_fn.m
  • Uses regexp to find all packages on the path, ie `+<pkg_name>` sub-strings
  • Joins the package names together with '.', eg: `pkg2.pkg1`
  • Uses `eval()` to call matlab's `import` statement: `import pkg2.pkg1.*`
After executing, you can call any functions that exist in pkg1 directly, without including the package names. This means that, from within a pkg1 function, you don't need to know whether pkg1 is a package, or whether it is a sub-package of any other package(s). This works even if pkg1 itself is NOT a package, but instead is just added to the matlab path (because `import .*` does nothing). This means a user could use the same repo either as a package or on the matlab path, without changing the code.
But, some negatives are that this line:
  • Uses `eval()` which I think is frowned upon
  • Forces you to do a regexp every time you call a package function, which could be slow
  • May cause namespace collisions if the imported package shares a function name with something else?
  1 Comment
Dimitrii Nikolaev
Dimitrii Nikolaev on 13 Oct 2021
Qiute the usefulliest comment over here.
Situations, when you need to subclass some class, nested in a package like:
classdef some_subclass < pkg2.pkg1.some_class
or check argument of a function using arguments syntax for validation like:
arguments %<--BTW, funny, that this keyword is not highlighted now :/
someargument (1,1) pkg2.pkg1.some_class
end
will remain a unsolveable problem :(

Sign in to comment.


Burcin Bektas
Burcin Bektas on 13 Aug 2020
Edited: per isakson on 13 Aug 2020
Agree with everyone above that this is poor design. I did find a hack that seems to work:
someObject=packageNameThatIShouldntHaveToWrite.SomeClass
can be written as
eval(['someObject = ', metaclass(obj).ContainingPackage.Name, '.SomeClass;', ]);
if calling from a class. "obj" above is the object variable SomeClass constructor returns.

Steven Lord
Steven Lord on 21 Apr 2021
Everything I'm writing here is my own opinion about what would happen if we made the change requested in this discussion thread.
Let's assume that package functions could call other functions in the same package without using the package name. What impact would that have? In particular how would you call a function from outside any package if there's a function by the same name in the package? Could I call the built-in bar function from within mypackage.foo if I had a mypackage.bar function?
In order to insulate yourself from files being added to the package breaking your code, each and every "global" function that you call would need to be called in that way. If foo is a function in a package:
function y = foo(x)
y = sin(x).^2-2*cos(x).^3;
end
Does that call the built-in sin function? It would be impossible to say without knowing if there's a sin function in the package. The answer to that question could change with no modification to the foo package function! So to safeguard and assure you're calling the built-in sin function let's assume that all the "global" functions were treated as being in a package named globalfuns. Do you want to have to write the following?
function y = foo(x)
y = globalfuns.sin(x).^2-2*globalfuns.cos(x).^3; % Calls the built-in sin and cos functions
end
That's a lot more verbose than the first version of the function. But that's not the whole story. The .^ operator has another name: it is the power function. So if you had a power function in your package would .^ call that function? Again, that would be impossible to say. To be sure it wouldn't you'd need:
function y = foo(x)
y = globalfuns.power(globalfuns.sin(x),2)-2*globalfuns.power(globalfuns.cos(x),3);
end
But the * operator is also called mtimes.
function y = foo(x)
y = globalfuns.power(globalfuns.sin(x),2)-globalfuns.mtimes(2,globalfuns.power(globalfuns.cos(x),3));
end
That's a lot less readable than the first case. But that's what you would need to do to make sure that people can't break your function by adding power, sin, mtimes, or cos functions to your package.
With the current system and the original function:
function y = foo(x)
y = sin(x).^2-2*cos(x).^3;
end
you would have to opt into calling the package sin or cos functions if you wanted to call them. Otherwise you get the built-in functions. People could add functions to or remove functions from the package without affecting the foo function above.
How do you opt in?
function y = foo(x)
y = mypackage.sin(x).^2-2*cos(x).^3; % Use the package sin, built-in cos
end
% or
function y = foo(x)
import mypackage.*
y = sin(x).^2-2*cos(x).^3; % Use mypackage.sin and/or mypackage.cos if they exist, built-ins if not
end
For package functions, the package name is effectively part of the function's name. [Not for purposes of a function like isvarname but it is for purposes of calling the function.] Just as you would have to update the callers of a regular function myfun if you changed then name from myfun to mybar to ensure they keep calling the correct function, if you change the name of mypackage.myfun to mynewpackage.myfun you need to update the callers whether they be in the mypackage package, the mynewpackage function, or outside any package.
In my opinion, changing either the file name or the package name should be a rare occurrence representing a major change in the organization and architecture of your code. It's not something you should do on a whim, and you should allocate time to react to the major reorganization / rearchitecting of the code.
  5 Comments
Ashley Trowell
Ashley Trowell on 14 Jul 2021
This is also a big issue for me. Mathworks shouldn't actively discourage good coding style in their design choices.
I'm not convinced by the argument that making packages hard to use prevents name collisions. In fact, this is the opposite of the truth, packages PREVENT collisions, that's what a namespace IS. Clear example: I created an object oriented simulation architecture, which among other things, has class names like Platform, Antenna, and Radar. This suite of classes is many thousands of lines of code, and perhaps a hundred m-files I use these a lot, so they sit in my path. Now it turns out, I'm not the only person who like names like Platform, Radar, and Antenna, and before long, I'm experienceing collisions. The sensible thing would be to wrap my project in a package. It wouldn't be too bad for me to have to type radar = myPackage.Radar() in the future. However... now I have thousands of lines of code that need to be checked for references between classes. In some cases, there is a major ergonomics challenge, since a line of code might have called a few disparate functions and been fine before
obj.newValue = fun1(1,2,3) * fun2(1,2,3) + fun3(1,2,3)
But now:
obj.newValue = myPackage.fun1(1,2,3) * myPackage.fun2(1,2,3) + myPackage.fun3(1,2,3)
This is suddenly not so fun. It encourages using a shorter package name than would really be advisable. It also makes simple find/replace refactoring hard to implement.
I don't see why be can be worried that I might have myPackage.plot defined when I can at anytime overwrite the default plot function with an accidental plot = 1.
Furthermore, this leads to several IDE issues which make a bad situation worse. First, there is a distinct lack of refactoring tools. Even if changing a package name is considered a big change, to be honest, I like to make big changes quickly. I've got other things to do than rename import statements.
Secondly, using import statements breaks my ability to use Ctrl+D to trace to a functions definition. Folks trying to understand the code can end up perplexed for a while until they go looking for an import, which they rarely ever do.

Sign in to comment.


Matt Cooper
Matt Cooper on 7 Nov 2022 at 19:48
Moved: Rik on 8 Nov 2022 at 5:56
This doesn't answer the OP's question or solve all of the issues you raise, but here's what I did to convert an existing project into a package. First, to address the "shorter package name than would be advisable", put the package in a folder with a descriptive name, and the package contents in ONE subfolder, which is an acronym for the descriptive name: DescriptiveProjectName/+dpn. Then simply do an fscanf,strrrep,fprintf workflow to read in each function in +dpn/ and replace all function calls with new function calls that have dpn. appended to them. In other words, calls to Platform, Radar, and Antenna would be replaced by dpn.Platform, dpn.Radar, and dpn.Antenna. Alternatively, you can use the same fscanf,strrrep,fprintf workflow to include a wildcard import statement at the top of each function in your package, instead of appending the package prefix to every function call. This roughly mirrors a typical python namespace with import statements at the top of a calling script that replace descriptive package names with acronyms, and all subsequent function calls use the acronym prefix e.g. import matplotlib.pyplot as plt followed by plt.plot(...).
I wrote a quick script to do this for my package and attached it here. In my case, the project already had an acronym as a prefix on all function filenames e.g. dpn_func1, dpn_func2, ... and so on (which is fairly common). This simplified the find/replace logic, but also meant I had to replace the H1 line and the function name in my inputParser calls, and I used a system command to issue git mv for each file. If your project code doesn't have a common prefix, you can just generate a list of function names in your package folder using dir and do a strcmp. If you have lots of subfolders/subpackages it will get more complicated (which I did), but I found it much more pleasing to collapse all my subfolders/packages into one folder, and it no longer seems necessary or beneficial to have a bunch of subfolders/packages (and greatly simplifies the functionSignatures.json file). But there's no reason you cannot keep the subfolders/packages, you just need to loop through and apply something similar to the script I attached.
  2 Comments
Matt Cooper
Matt Cooper on 8 Nov 2022 at 17:25
Edited: Matt Cooper on 10 Nov 2022 at 22:30
Sounds good, thank you. I attached a new version of the script with documentation and some safety measures (dryrun flag) to prevent accidental code rewrites.

Sign in to comment.

Categories

Find more on Scope Variables and Generate Names in Help Center and File Exchange

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!