7

To define some package-wide functions/variables, what approach is recommended? I tried to use contexts with the Begin function, but this failed:

Clear[fun];
fun[] := Module[{},
   Begin["myContext`"];
   x = 1;
   End[];
   ];
fun[]
myContext`x

I would expect myContext'x to be 1, but it seems to be undefined. Why does this fragment fail, what should I do instead?

Verbeia
  • 34,233
  • 9
  • 109
  • 224
Karsten W.
  • 1,383
  • 8
  • 21
  • 1
    I gave a solution for a similar problem here, where I also discussed some of the subtleties. Note that empty Module (without localizing variables) is misleading here, and can be as well removed. – Leonid Shifrin Sep 10 '12 at 10:49

3 Answers3

9

The issue here is that you haven't actually defined myContext`x, just plain old x. It is possible to access global variables within a context (that's exactly what happens when you use built-in function within a package), and that is what you have done.

Clear[fun];
fun[] := Module[{}, Begin["myContext`"];
   x = 1;
   End[];];

fun[]

This is undefined.

myContext`x  
(* myContext`x *)

But you have set x:

x 
(* 1 *)

To make this work, set the context explicitly in the definition of your variable:

morefun[] := Module[{}, Begin["myContext2`"];
   myContext2`x = 1;
   End[];];

morefun[]

myContext2`x 
(* 1 *)
Verbeia
  • 34,233
  • 9
  • 109
  • 224
  • Thanks, if I understand correctly, Begin just sets/alters the $Context / $ContextPath variables, but if I want to use the context, I have to state this explicitly. Or use @jVincent's meta programming. – Karsten W. Sep 10 '12 at 12:01
8

I'm just going to go ahead an rephrase the answer. The problem as Leonid points out is akin to shadowing. Here is a very simple example of the behavoir:

Remove[test`x, x]
(
Begin["test`"];
x = 42;
End[];
{x, test`x} 
)

{42,test`x}

Remove[test`x, x]
Begin["test`"];
x = 42;
End[];
{x, test`x}

{x,42}

If you use Trace You will see that in the second example everything is evaluated line-by-line, which means that when we get to x=42 the active context is test`, however in the first case, we initially evaluate the CompoundExpression which means we put Global`x into scope, which means we already have an x defined when we evaluate x=42 thus it's interpreted as Global`x=42.

A way to get around this is to use the fact that MakeBoxes will automatically remove any context currently in $ContextPath while ToExpression Automatically puts any symbol not found in any context into $Context. Here I exclude the System` context in order to avoid scoping for example Sin to test`Sin:

SetAttributes[ContextScope, HoldAll]
ContextScope[context_, expression_, exclude_: {"System`"}] :=
Block[{held},
 Block[{$ContextPath = Complement[$ContextPath, {"System`"}]}, held = MakeBoxes[expression]];
 Block[{$Context = context, $ContextPath = {context}}, 
 ToExpression[held]]
]

ContextScope["test`", x = Sin[0]]
{x, test`x}

{x,0}

This will however still put Global`x in scope, it simply does not assing a value to it. So any new call to x=somethign will assign a value to Global`x and not to test`x as would normally be expected if you had done a line by line evaluation of the context switching.

jVincent
  • 14,766
  • 1
  • 42
  • 74
  • Good observation, but I would not "multiply entities". The reason for the difference is in how expressions entered in cells in the FrontEnd are parsed. The parser makes an exception for the top-level CompoundExpression (second case), parsing line-by-line - thus this works. For the first case, it parses entire body of CompoundExpression before evaluating, and therefore the symbol x is still created in the Global` context. This has been explained also here. – Leonid Shifrin Sep 10 '12 at 10:48
  • 1
    "I don't think you can start and stop a context within other evaluations" - sorry, but this is simply not the case, see e.g. this answer. It is actually this feature of dynamic control over the current context and other things affecting parsing ($ContextPath) which make the encapsulation mechanism of Mathematica much more flexible and powerful. – Leonid Shifrin Sep 10 '12 at 10:52
  • @LeonidShifrin I stand corrected. It is indeed because x is scoped upon its first appearance when the entire expression is first evaluated, which scopes it to global. Could you elaborate on "would not multiply entities" ? – jVincent Sep 10 '12 at 10:55
  • Well, I just meant Occam's razor principle, in that it is always best to describe the principle from which say 10 different observations follow, than to only describe the observations alone, which would then look like magical spells. – Leonid Shifrin Sep 10 '12 at 10:58
  • I actually did not first notice your statement about the impossibility of dunamically changing the current context (for which the examples apparently serve as an illustration), and so initially viewed your two examples just as empirical observations of these different behaviors, thus my comment on entities multiplication :-). – Leonid Shifrin Sep 10 '12 at 11:02
  • @LeonidShifrin I have corrected my answer to reflect my newfound understanding of the problem thanks to your correction. Thank you. :) – jVincent Sep 10 '12 at 11:07
  • Well,this is more or less what I suggested here. I would not say "during it's evaluation all symbols will apear before any evaluation" - this is during parsing, not evaluation. It is important to maintain the distinction. Also, in your new suggestion (as well as mine I linked to), what really happens is that by converting to boxes you delay the parsing until later time when the context has been already changed, so you change the order of parsing and (part of) evaluation. This.. – Leonid Shifrin Sep 10 '12 at 11:25
  • ... is what makes it non-trivial. Also, since people tend to read from top to bottom, I would remove the older statement on the impossibility of context changing from the answer altogether. Finally, converting to boxes still causes the initial parsing and creation of these symbols also in the Global` context - I actually mentioned this in the answer I linked to. – Leonid Shifrin Sep 10 '12 at 11:27
  • I like your new answer better, although I would still phrase the explanation differently to emphasize the distinction between parsing stage and evaluation stage. As to your new ContextScope function, I use something similar but IMO more robust and based on BeginPackage - EndPackage, which is described in e.g. this answer - but I don't see any obvious issues with your implementation. – Leonid Shifrin Sep 10 '12 at 12:00
  • @LeonidShifrin Actually this is not the case. What You parse the expression during evaluation which is what puts the symbol into Global. Later when you use MakeExpression, it strips any contexts that appear in ContextPath, since they are redundent. But this can be abused by changing ContextPath, since MakeExpression will put any symbols not found in the contextpath into $Context. – jVincent Sep 10 '12 at 12:02
  • @LeonidShifrin So while you could call it delayed parsing, it's actually a result of the way MakeExpreesion and MakeBoxes handle contexts. You could say that Evaluation of MakeExpression is parsing, but the parsing/evaluation distinction is fuzzy in that case. – jVincent Sep 10 '12 at 12:03
  • Thanks, together with Block[{$Context="MyPackage'Private'"}, ToExpression[#][5]] & /@ tests found here I think I can build a setInContext and useInContext function. Thanks! – Karsten W. Sep 10 '12 at 12:13
1

Just for reference, the way I go now is to define a global context in my add-on package, myTool'globals' and for every package function that uses a global (package-wide) variable, I make the package function dependent on the globals package:

BeginPackage["myTool`alterGlobalVar`", {"myTool`globals`"}];
...

I hope this will work out well for me.

Karsten W.
  • 1,383
  • 8
  • 21