5
$\begingroup$

I have an package with a bunch of symbols, many of which I exposed during development and which are now referenced in other subpackages. At this point, though, I don't want to expose these symbols to the user to prevent cluttering their workspace and I want to truly hide the symbols, rather than just hide them from the front-end.

Obviously I could rewrite my package to submerge all of these symbols, but that would mean hours of work with a high likelihood of breaking things.

Is there a way to do this?

$\endgroup$
4
$\begingroup$

This trick appears on the site periodically, but it's still worth getting out there once more and I've recently used it to clear up my package namespace significantly.

You can simply reassign a Symbol's Context and this change will propagate wherever it was used.

So for the case at hand, say we have a package, "MyPack`":

BeginPackage["MyPack`"];
impSym1::usage = "Implementation symbol1";
impSym2::usage = "Implementation symbol2";
MyPackSym::usage = "Symbol worth exposing at top level";
Begin["`Private`"];
MyPackSym[x_] :=
  With[{r = impSym1@impSym2[x]},
   r /; Head[r] =!= impSym1
   ];
impSym2[x_] :=
  FE`makePlainText[ToBoxes@x];
impSym1[s_String] :=

  ToCharacterCode[s] // Counts // KeyMap[FromCharacterCode];
End[];
EndPackage[];

(* Example usage *)

MyPackSym[CloudDirectory[]]

<|"C" -> 1, "l" -> 3, "o" -> 5, "u" -> 3, "d" -> 4, "O" -> 1, 
 "b" -> 3, "j" -> 2, "e" -> 4, "c" -> 4, "t" -> 4, "[" -> 1, "h" -> 1,
  "p" -> 1, "s" -> 3, ":" -> 1, "/" -> 4, "w" -> 4, "." -> 2, 
 "f" -> 2, "r" -> 2, "a" -> 5, "m" -> 2, "-" -> 5, "4" -> 5, "1" -> 3,
  "3" -> 3, "2" -> 5, "6" -> 1, "7" -> 1, "9" -> 4, "5" -> 1, 
 "]" -> 1|>

But then we realize we'd really rather have impSym1 and impSym2 in a subcontext, say "`Imp`".

We can simply use Set with the Context of the symbols:

Context[impSym1] = "MyPack`Imp`";
Context[impSym2] = "MyPack`Imp`";

And for good measure we'll remove the old syntax coloring in the FE (using the answer here):

FESetSymbolColoring[{impSym1, impSym2}, "MyPack`", "Removed"]

And then we can look at the DownValues of MyPackSym:

MyPackSym // DownValues

{HoldPattern[MyPackSym[MyPack`Private`x_]] :> 
  With[{MyPack`Private`r = 
     MyPack`Imp`impSym1[MyPack`Imp`impSym2[MyPack`Private`x]]}, 
   MyPack`Private`r /; Head[MyPack`Private`r] =!= MyPack`Imp`impSym1]}

And we see the reassignment we hoped for. And the function still works as it should:

MyPackSym[CloudDirectory[]]

<|"C" -> 1, "l" -> 3, "o" -> 5, "u" -> 3, "d" -> 4, "O" -> 1, 
 "b" -> 3, "j" -> 2, "e" -> 4, "c" -> 4, "t" -> 4, "[" -> 1, "h" -> 1,
  "p" -> 1, "s" -> 3, ":" -> 1, "/" -> 4, "w" -> 4, "." -> 2, 
 "f" -> 2, "r" -> 2, "a" -> 5, "m" -> 2, "-" -> 5, "4" -> 5, "1" -> 3,
  "3" -> 3, "2" -> 5, "6" -> 1, "7" -> 1, "9" -> 4, "5" -> 1, 
 "]" -> 1|>

One major caveat is that this only applies to symbols that have already been introduced. If you have a symbol s in context "C`", you need to reassign the Context after all symbols / objects that use / will use it have been defined.

My standard usage for this looks like:

PackageScopeBlock[e_, scope_String: "Hidden"] :=

  With[{s = "$Name`Private`" <> StringTrim[scope, "`"] <> "`"},
   If[! MemberQ[$PackageContexts, s], AppendTo[$PackageContexts, s]];
   Cases[
    HoldComplete[e],
    sym_Symbol?(
       Function[Null,
        MemberQ[$PackageContexts, Quiet[Context[#]]],
        HoldAllComplete
        ]
       ) :>
     RuleCondition[Set[Context[sym], s], True],
    Infinity
    ];
   e
   ];
PackageScopeBlock~SetAttributes~HoldAllComplete;

where any symbol in the $PackageContexts that's inside the block gets rescoped. Then I just do:

PackageScopeBlock[
  a::usage = "";
  b::usage = "";
  ];
c::usage = "";

So that a and b will be rescoped, but c will not.

|improve this answer|||||
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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