9
$\begingroup$

V 12.1 on windows.


I am loading pkg2, which in its Private section loads pkg1 in order to call one of its function.

After using pkg2, I found that pkg1 is now visible and can be called without loading it.

I thought the whole idea of loading a package from the private section of another package, is to keep it private and not to leak it to the top user level. Otherwise, why load it from private section in first place?

I am using the layout based on this answer load-a-mathematica-package-from-within-a-package

Here is MWE. First I have the two packages in same folder the main notebook for now to make it easier to test

enter image description here

Here is pkg2.m. This is the one I want to use,

BeginPackage["pkg2`"];
Unprotect@@Names["pkg2`*"];
ClearAll@@Names["pkg2`*"];

test::usage

Begin["`Private`"];
Needs["pkg1`"];

test[]:=Module[{},    
    Print["Inside test[] in pkg2"];
    pkg1`foo[]    
]
End[]; (* `Private` *)
Protect@@Names["pkg2`*"];
EndPackage[]

I used Needs in the above. Get also works. It seems Needs found pkg1.m since it is in same folder as pkg2.m automatically without having to give full path. Notice that the caller sets the current directory to where the packages are, and I think this is why it worked without the full path. Mathematica seems to search current folder first.

And here is pkg1.m

BeginPackage["pkg1`"];
Unprotect@@Names["pkg1`*"];
ClearAll@@Names["pkg1`*"];

foo::usage

Begin["`Private`"];

foo[]:=Module[{},

Print["in pkg1  foo OK"]

]
End[]; (* `Private` *)
Protect@@Names["pkg1`*"];
EndPackage[]

And here is main.nb, which loads pkg2.m. And once this package is loaded, then pkg1 becomes visible to the user as well, which is not what I want

enter image description here

SetDirectory[NotebookDirectory[]]
Get["pkg2.m"]
?? pkg1`*

Question is : What is the correct to have package A use package B in its private section without making B visible to the user?

$\endgroup$
1
  • 2
    $\begingroup$ I think that you are already doing the correct thing and that there is no problem. It is not possible to load a package and make it invisible to the end user. The best you can do is load it like you are so it isn’t on the context path. $\endgroup$
    – Jason B.
    May 31, 2020 at 11:30

1 Answer 1

12
$\begingroup$

I use the following pattern. Package B will not be made visible to the user when A is loaded, but it will be usable internally by A.

BeginPackage["A`"]
Needs["B`"]

foo::usage = "foo[] does things";

Begin["`Private`"]

foo[x_] := x^2

End[]
EndPackage[]

This contrasts with the following which makes both A and B visible when only A is loaded:

BeginPackage["A`", {"B`"}]

foo::usage = "foo[] does things";

Begin["`Private`"]

foo[x_] := x^2

End[]
EndPackage[]

Both of these will of course load package B. If B is not loaded, it cannot possibly be used by package A. The difference is in whether B is made accessible to the user when the user requests only A.


You ask why the following returns things,

?B`*

or "Why is B visible"?

The answer is that B is not actually visible. B` will not be on the context path ($ContextPath). If there is a function B`fun provided by package B, you can only call it as B`fun[], but not as fun[].

?B`fun will work but ?fun will not work.

"Visibility" means whether functions in a package can be called without typing out the full context. Package B must be loaded to be usable by A. This means that B`fun must exist. The way Mathematica ensures that B does not interfere with anything is to not have the context B` on the context path, and therefore not allow access to B's symbols without writing out their context. However, all symbols in Mathematica are accessible is providing their full context. Nothing can be fully hidden.


Based on your comment:

But the names of that second package creep up to use space (even though if the user does not know the other package name, they will not know how to call them).

I assume you are looking not to load B until absolutely necessary. Here's a solution to that.

Assume that package B provides B`fun, but this function is only required by A`something. Then we can write package A as follows:

BeginPackage["A`"]

foo::usage = "foo[]";
something::usage = "something[]";

Begin["`Private`"]

foo[x_] := x^2

something[] := 
  (Block[{$ContextPath}, Needs["B`"]];
   B`fun[])

End[]
EndPackage[]

Now Needs["A`"] will not immediately load B. B will only be loaded when something[] is called for the first time. I used Block[{$ContextPath}, ...] to ensure that B won't be made visible to the user when they call something[]. I also needed to explicitly spell out the context of B`fun[] in the definition of something, as B` would not be loaded or on the context path at the time when the definition of something is parsed.

$\endgroup$
3
  • $\begingroup$ Thanks. Your example still makes the other package loaded inside the first one "visible". I understand what you are saying by that it is however not on the context path. But the names of that second package creep up to user space (even though if the user does not know the other package name, they will not know how to call them). This part I understand. I was hoping Mathematica will not show anything related to the other package used internally. $\endgroup$
    – Nasser
    May 31, 2020 at 8:06
  • $\begingroup$ @Nasser In order for package A to be able to use package B, it is necessary to load B, i.e. to evaluate everything in B.m. There is not way around this. If B is not loaded, it cannot be used by anything or anyone. Not the user, not package A. $\endgroup$
    – Szabolcs
    May 31, 2020 at 8:08
  • $\begingroup$ @Nasser See my last edit. $\endgroup$
    – Szabolcs
    May 31, 2020 at 8:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.