14

How can you introduce a package, so that it is listed in $Packages, without adding it to $ContextPath?

Note that this question is concerned with defining a package when evaluating code, e.g. through evaluating a cell, rather than loading a package from a file.

This is useful if you want to add capabilities without introducing new "bare" symbols in any way -- they live in their own context and must be referenced through an explicit context, unless the user adds that context back to the context path.

Joel Klein
  • 5,225
  • 1
  • 31
  • 44
  • Do I understand correctly that you cannot use the equivalent of Block[{$ContextPath}, Needs["Combinatorica`"]] because the package is defined in a notebook, and it must be in multiple, separate input lines (therefore can't be wrapped)? BeginPackage affects parsing, so it takes effect only starting with the next input. – Szabolcs Feb 04 '13 at 23:06
  • This seems to work even if you were to define the package and not load it (basically what Szabolcs said) – rm -rf Feb 04 '13 at 23:19
  • 1
    @rm-rf Nope, that can't be used if you define the package in a cell. The reason is that as soon as you wrap the definition in Block, it becomes a single input that is parsed before BeginPackage is evaluated and changes the context. So while it may appear to work on first try, the package symbols will all be created in the Global context. – Szabolcs Feb 04 '13 at 23:21
  • @Szabolcs Ah yes, you're right. – rm -rf Feb 04 '13 at 23:22
  • Ref: http://stackoverflow.com/questions/7917550/why-doesnt-this-use-of-begin-work BTW I upvoted this because when one starts to think of alternative solutions, it'll become clear that Joel's solution is not as trivial to figure out as it looks. – Szabolcs Feb 04 '13 at 23:22
  • @Szabolcs, thanks. I came across this problem in the course of my work, and thought there are multiple ways to approach it. I was not able to whip off the right solution immediately either; I first tried wrapping the BeginPackage in a With, similar to the Block, and that made the package symbols get parsed into Global`. So I had to work at this a bit to arrive at my proposed answer. – Joel Klein Feb 04 '13 at 23:33
  • Ironically, just a day later I discovered that in 9.0.1 the Predictions` context is added to the context path. It seems like a bug. – Szabolcs Feb 06 '13 at 02:11
  • It is a bug, it's known. – Joel Klein Feb 06 '13 at 03:12

3 Answers3

15

There is a simpler way:

BeginPackage["ChurchNumerals`"];

ZERO::usage = "ZERO is the Church encoding of zero";
ONE::usage = "ONE is the Church numeral encoding of the integer 1";
TWO::usage = "TWO is the Church numeral encoding of the integer 2";

Begin["`Private`"];

ZERO = Function[f, Function[n, n]];
ONE = Function[f, Function[n, f[n]]];
TWO = Function[f, Function[n, f[f[n]]]];

End[];

Block[{$ContextPath}, EndPackage[]];

It is the EndPackage which is responsible for keeping the context on the $ContextPath, so you can just Block only it.

Note that putting a Block around the whole thing won't work in the FrontEnd, since only the top-level statements are parsed one by one in a cell - so in that case, the symbols would end up created in a wrong (Global`) context.

Leonid Shifrin
  • 114,335
  • 15
  • 329
  • 420
  • Any chance of fixing this sometimes annoying different behaviour of FrontEnd and Kernel parser? – Rolf Mertig Feb 05 '13 at 00:32
  • @RolfMertig I think this is by design. But I am not the most knowledgable person with regard to these issues :) – Leonid Shifrin Feb 05 '13 at 00:34
  • 1
    I think there should not be different parsers. It is hard to figure out the difference, harder to program around them and even harder to try to teach this. – Rolf Mertig Feb 05 '13 at 00:41
  • @RolfMertig I agree, there should be one parser, although parsing in the FrontEnd may need some modifications w.r.t. parsing packages still. I don't know enough of these internals to add more to this, alas. – Leonid Shifrin Feb 05 '13 at 00:44
  • 1
    Why does this work? I find it weird that a line like this Block[{$ContextPath}, EndPackage[]]; can change the $ContextPath. Big +1 of course, I'm learning something new – Rojo Feb 05 '13 at 00:49
  • 2
    @Rojo This Block does not change the $ContextPath, it prevents the EndPackage from changing it globally. It is EndPackage which attempts to change things here. Soon, I will run out of tricks to surprise you :-). – Leonid Shifrin Feb 05 '13 at 00:53
  • 1
    ...but before running that line the $ContextPath is just {ChurchNumerals\,System`}, and afterwards it goes back to havingGlobal`` and the rest – Rojo Feb 05 '13 at 00:56
  • @Rojo My guess is that $ContextPath is redefined by BeginPackage, and it is its standard function to remove from it all contexts except System` and add the package's context to it. However, the previous value is stored on some kind of stack. The way it is then restored must be something like $ContextPath = Join[$oldContextPath, publicly-imported-packages]. Using Block, we block this modification. Then likely EndPackage sees $ContextPath as a symbolic quantity, and this prompts it to recover the previous value on the stack. Something like this. – Leonid Shifrin Feb 05 '13 at 01:05
  • 1
    I wouldn't have guessed, confusing. Even if it were true that you run out of tricks, we've come a long way – Rojo Feb 05 '13 at 01:45
  • @Rojo Yeah, right. I still remember that post, for some reason, although it was nothing particularly special. I actually remember many of my posts, perhaps too many. But you've made a long way since then. You and rm -rf are among a few here who have made huge leaps in terms of Mathematica skills in an amazingly small period of time. You guys are scaring me. – Leonid Shifrin Feb 05 '13 at 01:55
  • Wow, good one. It's crazy that that works. – Joel Klein Feb 05 '13 at 04:29
  • @JoelKlein Oh yeah :) – Leonid Shifrin Feb 05 '13 at 04:34
  • @LeonidShifrin “Soon, I will run out of tricks...”—7 years past, and no, you haven't! :) – kkm -still wary of SE promises Nov 16 '21 at 02:21
  • @kkm Thanks :) I wonder what prompted your comment though, since this post is pretty old. – Leonid Shifrin Nov 16 '21 at 12:27
  • Because it took me 7 years to discovered another amazing trick of yours that I had been unaware of. What else could? Thanks for these, really appreciated! :) Srsly, I was looking for a solution to this exact problem: avoid polluting the `Global`` namespace with over 9000 declarations. – kkm -still wary of SE promises Nov 16 '21 at 20:52
  • @kkm Glad that this helped :) – Leonid Shifrin Nov 17 '21 at 12:23
12

Just using DeleteCases seems to work too:

  BeginPackage["ChurchNumerals`"];
    ZERO::usage = "ZERO is the Church encoding of zero";
    ONE::usage = "ONE is the Church numeral encoding of the integer 1";
    TWO::usage = "TWO is the Church numeral encoding of the integer 2";
    Begin["`Private`"];
    ZERO = Function[f, Function[n, n]];
    ONE = Function[f, Function[n, f[n]]];
    TWO = Function[f, Function[n, f[f[n]]]];
    End[];
    EndPackage[];
    $ContextPath = DeleteCases[$ContextPath, "ChurchNumerals`"]
Rolf Mertig
  • 17,172
  • 1
  • 45
  • 76
5

One way to do this is by taking note of how EndPackage works -- by adding \$Context to \$ContextPath:

ChurchNumerals`Private`prevContext = Context[];
BeginPackage["ChurchNumerals`"];
  ZERO::usage = "ZERO is the Church numeral encoding of zero";
  ONE::usage = "ONE is the Church numeral encoding of the integer 1";
  TWO::usage = "TWO is the Church numeral encoding of the integer 2";
  Begin["`Private`"];
    ZERO = Function[f, Function[n, n]];
    ONE = Function[f, Function[n, f[n]]];
    TWO = Function[f, Function[n, f[f[n]]]];
  End[];
  $Context = ChurchNumerals`Private`prevContext;
EndPackage[];

Now the package is on $Packages but not $ContextPath, and is used qualified with the package name:

Map[#[Function[n, n + 1]][0] &, 
   {ChurchNumerals`ZERO, ChurchNumerals`ONE, ChurchNumerals`TWO}]
Joel Klein
  • 5,225
  • 1
  • 31
  • 44
  • 3
    I would use the variable to store the context path and restore it after EndPackage instead of the current context. Simpler for the cases where EndPackage also adds the dependency list (second argument of BeginPackage) – Rojo Feb 04 '13 at 23:48
  • Yeah, that works too. In my case I'm bending over backward not to introduce anything into context path, so I didn't think of that case of the 2nd argument to BeginPackage. – Joel Klein Feb 05 '13 at 04:04