Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Mutual package imports in Mathematica

25 views
Skip to first unread message

Leonid Shifrin

unread,
Sep 20, 2010, 5:42:48 AM9/20/10
to
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


0 new messages