I learned how to use OOP in Mathematica as shown by Leonid Shifrin (and others in this forum over the years).
It works well, until I moved the module that represents the object to inside a package, then I found all the public methods of the module no longer can be directly accessed as before and they all have to be made global at the package level now.
It is easier to explain all this by an example.
Using this small example by Leonid taken from this post which I modified very little
personClass[fname_, lname_] := Module[{fn = fname, ln = lname, self},
SetAttributes[self, HoldAll];
self@getFirstName[] := fn;
self@setFirstName[name_] := fn = name;
self@getLastName[] := ln;
self@setLastName[name_] := ln = name;
self@getFullName[] := fn <> " " <> ln;
self];
Now in the notebook one can do a person object using the call
obj = personClass["joe", "doe"]
After that, the object methods can be accessed using
obj@getFullName[]
This all works well. The problem comes when I wanted to put this object in a package. This is what I did
BeginPackage["mypkg`"]
Unprotect @@ Names["mypkg`*"];
ClearAll @@ Names["mypkg`*"];
personClass::usage = "personClass[fname,lname]"
Begin["`Private`"]
personClass[fname_, lname_] :=
Module[{fn = fname, ln = lname, self},
SetAttributes[self, HoldAll];
self@getFirstName[] := fn;
self@setFirstName[name_] := fn = name;
self@getLastName[] := ln;
self@setLastName[name_] := ln = name;
self@getFullName[] := fn <> " " <> ln;
self
];
End[];
Protect @@ Names["mypkg`*"];
EndPackage[];
And saved the above in mypkg.m
file. Everything looks OK so far. Except it is no longer possible to access those object methods directly as before.
This is what happens in the notebook
Get["mypkg.m"]
obj = mypkg`personClass["joe", "doe"]
Now when I do like before
obj@getFullName[]
Mathematica does not see the object methods. It gives
mypkg`Private`self$4172[getFullName[]]
None of the object methods are visible, like the case when the module was in the notebook before moving it to a package. I understand why that is (different context now), but I am looking for easier way to do this.
The solution I found is to modify the package and actually make all Person class methods global by adding an explicit usage statement for each public method in each Object module I want to put inside the package. So the package now looks like this
BeginPackage["mypkg`"]
Unprotect @@ Names["mypkg`*"];
ClearAll @@ Names["mypkg`*"];
personClass::usage = "personClass[fname,lname]"
getFullName::usage = "...."
setFirstName::usage = "...."
getLastName::usage = "...."
setLastName::usage = "...."
getFullName::usage = "...."
Begin["`Private`"]
personClass[fname_, lname_] :=
Module[{fn = fname, ln = lname, self},
SetAttributes[self, HoldAll];
self@getFirstName[] := fn;
self@setFirstName[name_] := fn = name;
self@getLastName[] := ln;
self@setLastName[name_] := ln = name;
self@getFullName[] := fn <> " " <> ln;
self
];
End[];
Protect @@ Names["mypkg`*"];
EndPackage[];
And now reloading the package with
Get["mypkg`"]
The calls now work as they did in the notebook outside the package scope
But this defeats the whole point of using a module as a class for an object. If one have to manually retype the same name of each public method in each module with a usage statement like this. Also looking the usage statements, one has no idea from where each one of these methods came from. This makes managing this code a mess.
The question is: Is there a way to automate this, or better way to use objects inside packages than the above so I do not have to do this for each inner module inside the package like this? managing these usage statements will be a headache for very large modules.
May be there is a way to export all those public methods of the Person class automatically to the package level?
Update
Here is a simple example of the problem of using this method of making Objects in Mathematica. There are 2 classes (i.e. modules) in this example, each with a public method called process
.
But since the name is the same, the usage for the first one is lost and only the second usage method shows up.
BeginPackage["mypkg`"]
Unprotect @@ Names["mypkg`*"];
ClearAll @@ Names["mypkg`*"];
classA::usage = "classA[arg1,arg2]"
process::usage = "classA`process[]"
classB::usage = "classB[arg1]"
process::usage = "classB`process[]"
Begin["`Private`"]
classA[arg1_, arg2_] :=
Module[{a = arg1, b = arg2, self},
SetAttributes[self, HoldAll];
self@process[] := a+b;
self
];
classB[arg1_] :=
Module[{a = arg1, self},
SetAttributes[self, HoldAll];
self@process[] := a^2;
self
];
End[];
Protect @@ Names["mypkg`*"];
EndPackage[];
Saving the above as mypkg.m
and then doing
<<mypkg`
?mypkg`*
?mypkg`process
So the the first usage is lost, since it is the same name. This is just one of the problems with using this way of doing OOP that shows up when putting these modules inside a package.
Everything works well outside of the package ofcourse. But I needed to put these modules inside a package, since this is the standard way to distribute an application in Mathematica to others.
Note that in spite of the usage message getting lost (this is documentation problem), I am able to actually make objects of class A and class B OK and call the process method for each with no problem. Here is screen shot
The above is all correct. So if there is a way to fix this usage problem, this setup could actually work. I just need to update the usage messages each time a new public method is added/changed for each member class.
self@"getFirstName"[] := fn
. $\endgroup$Begin["
Private"];
$\endgroup$getFullName; getLastName; ...
is enough. $\endgroup$