Mutual package imports in Mathematica
- To: mathgroup at smc.vnet.net
- Subject: [mg112564] Mutual package imports in Mathematica
- From: Leonid Shifrin <lshifr at gmail.com>
- Date: Mon, 20 Sep 2010 05:42:50 -0400 (EDT)
Hello group, This post is about mutual package imports. I encountered such a situation which led to certain (surmountable) difficulties in my work. I looked up on the group's archive and did not find anything on the topic (may be I was not looking long enough), so after I solved my problem, I decided to post my findings below in the hope that they may be of some help to others getting into the same situation. If this is a repetition of some well-known common wisdom, my apologies. Suppose you have two packages, the main one, and the helper one. The main one needs some of the functionality of the helper one, and you want to keep the context of the helper package on the context path after the main one loads. But, here is the problem: the helper one also needs some of the functionality of the main one. Such situation may be preferable for better designs in some rare cases. So, here is a naive attempt: (* Main package *) BeginPackage["MyMainPackage`",{"MyHelperPackage`"}] Fun1::usage="Fun1[x_] squares its argument"; Fun2::usage= "Fun2[x_] computes a square root of its argument"; Fun3::usage = "Fun3[x_,y_] is a more complex function"; Begin["`Private`"] (*Needs["MyHelperPackage`"];*) Fun1[x_]:=x^2; Fun2[x_]:=Sqrt[x] Fun3[x_,y_]:=(x+y)*HFun1[x,y]; End[] EndPackage[] (* Helper package *) BeginPackage["MyHelperPackage`"] HFun1::usage="This is a helper function"; Begin["`Private`"]; Needs["MyMainPackage`"]; HFun1[x_,y_]:=Fun1[x]+Fun2[y] End[] EndPackage[] Everything looks fine, so the function HFun1 ought to use functions Fun1 and Fun2 from the main package (this was what I was thinking anyway)). Now we try to use this: In[37]:= Needs["MyMainPackage`"] In[38]:= HFun1[1, 2] Out[38]= MyHelperPackage`Private`Fun1[1] + MyHelperPackage`Private`Fun2[2] In[39]:= Fun3[1, 2] Out[39]= 3 (MyHelperPackage`Private`Fun1[1] + MyHelperPackage`Private`Fun2[2]) Alas, this did not happen. The point is that the package MyHelperPackage` is read before those definitions in the main package (because listing in in a list of dependent packages in BeginPackage["MyMainPackage`",...] causes Needs to be called on MyHelperPackage` before even the public part of the main package is read, and therefore, while the context "MyMainPackage`" *is* on the contextpath of MyHelperPackage` when the latter is read, the function names are not yet there in that context and could not be found. Therefore, MyHelperPackage` made up its own private names for Fun1 and Fun2, which is obviously what we don't want. Solution: 1. If we don't need the context of the helper package to remain on the contextpath after the main package gets loaded, then there is no problem at all - import the helper package privately. In that case, the public portion of the main package with all public functions is read first, and this those names are already there by the time the helper package is loaded. Of course, the helper package must still import the main one (hidden import say). In code, this will now look like: BeginPackage["MyMainPackage`"] Fun1::usage="Fun1[x_] squares its argument"; Fun2::usage= "Fun2[x_] computes a square root of its argument"; Fun3::usage = "Fun3[x_,y_] is a more complex function"; Begin["`Private`"] Needs["MyHelperPackage`"]; Fun1[x_]:=x^2; Fun2[x_]:=Sqrt[x] Fun3[x_,y_]:=(x+y)*HFun1[x,y]; End[] EndPackage[] Now we can check: In[44]:= Needs["MyMainPackage`"] In[45]:= HFun1[1, 2] Out[45]= HFun1[1, 2] In[46]:= Fun3[1, 2] Out[46]= 3 (1 + Sqrt[2]) We see that the problem has been solved in part, but that the helper context is then still unavailable: the HFun1[1, 2] evaluates to itself. 2. If we do need to leave the helper package on the context path (as in our example), this can be accomplished by the following trick: add lines BeginPackage["MyMainPackage`",{"MyHelperPackage`"}] EndPackage[] to the end of your main package, which in its final form will look like: BeginPackage["MyMainPackage`"] Fun1::usage="Fun1[x_] squares its argument"; Fun2::usage= "Fun2[x_] computes a square root of its argument"; Fun3::usage = "Fun3[x_,y_] is a more complex function"; Begin["`Private`"] Needs["MyHelperPackage`"]; Fun1[x_]:=x^2; Fun2[x_]:=Sqrt[x] Fun3[x_,y_]:=(x+y)*HFun1[x,y]; End[] EndPackage[] BeginPackage["MyMainPackage`",{"MyHelperPackage`"}] EndPackage[] In this case, starting with a fresh kernel, everything works as planned: In[53]:= Needs["MyMainPackage`"] In[54]:= HFun1[1, 2] Out[54]= 1 + Sqrt[2] In[55]:= Fun3[1, 2] Out[55]= 3 (1 + Sqrt[2]) Now, one may argue that mutual imports may reflect bad design, but IMO they can, very occasionally, be quite handy and lead to actually better designs. In any case, this is another possibility, whether or not to use it is a different issue. I hope that I don't waste everyone's time and bandwidth, and that someone will find this useful or perhaps adds something I missed. Regards, Leonid