21
$\begingroup$

Mathematica, unlike many other languages, defines the variables in the global scope unless it is explicitly asked to define a variable in a local scope, e.g., using Module, With, or Block.

My question is how to define a scoping construct which defines the variables locally by default unless otherwise stated. For instance:

x = 1;
y = 1;
Scope[{x (* specify global variables here *) },
  x = 2;
  y = 2;
  a = 0;
  (* x and y both equal to 2 and a is 0 here *)
]
(* x equals to 2, y equals to 1, a is undefined here *)

How can one define a function Scope as demonstrated above?

$\endgroup$
7
  • 2
    $\begingroup$ May be you can look at Begin["scope`"] ..... End[] and see if this does what you want. $\endgroup$
    – Nasser
    Commented Sep 5, 2013 at 5:22
  • $\begingroup$ reference.wolfram.com/mathematica/tutorial/Contexts.html $\endgroup$
    – Nasser
    Commented Sep 5, 2013 at 5:28
  • $\begingroup$ @Kuba The example does nothing because Scope doesn't exist, but that's what the OP would like Scope to do. The OP wants such a scoping construct. (It is right; contexts are the closest thing.) $\endgroup$
    – C. E.
    Commented Sep 5, 2013 at 6:32
  • $\begingroup$ @Kuba: 'locally' means in the local scope. I updated the question and added a new variable. I hope it is clear now. Actually, I want to define all the variables in the local scope and only share the ones which are specified in the list (the first parameter of Scope) with the global scope. $\endgroup$
    – Helium
    Commented Sep 5, 2013 at 6:52
  • $\begingroup$ @Anon It seems no one is sharing my thougths about ambiguity of the question so I'm deleting my comments :) $\endgroup$
    – Kuba
    Commented Sep 5, 2013 at 8:12

3 Answers 3

15
$\begingroup$

Overview

Here's two functions that operate in a manner close to what you want. The first uses a locally defined context to provide the scoping. The second uses Block, and is likely closer to what you wish.


Version 1

Here's a single function that does what you want:

ClearAll[Scope];
SetAttributes[Scope, HoldAll];

Scope[{globals : (_Symbol | _Set | _SetDelayed) ...}, body_] := 
Internal`InheritedBlock[{$ContextPath},
  (* Put globals into Global` or equivalent *)
  globals;
  Module[{context, localbody = MakeBoxes[body]},
    Internal`WithLocalSettings[
      BeginPackage[SymbolName@context <> "`"],
      ReleaseHold@MakeExpression@localbody,
      EndPackage[]
    ]
  ]
]

I'll detail more on how it works, later.

Using the example from Mr.Wizard,

x = 1;
y = 1;
Scope[{x = 2},
 Print[$ContextPath];
 y = 2;
 a = 0;
 Names["`*"]
]

{x, y, a}
(* {context$15569`,System`} *)
(* {"a", "y"} *)
(* {2, 1, a} *)

Unfortunately, it also produces shadowing messages for both y and a because they are first created within the global context, and then added to the local one. I have, yet, to work out how to prevent this.


Version 2

This version uses Block instead of moving everything to a separate context. To do so, it needs a couple of helper functions:

ClearAll[flatHold];
SetAttributes[flatHold, {Flat, HoldAll}];
{a__flatHold} ^:= flatHold[a]

which deals with nested Hold, and to simplify things later

(scope:Block|Module)[flatHold[a__], body_] ^:= scope[{a}, body]

which allows us to do this:

Block[flatHold[a, b], a = 5; b = 6; {a, b}]
Module[flatHold[a, b], a = 5; b = 6; {a, b}]
(* 
 {5, 6}
 {5, 6}
*)

This works because Block and Module have the attribute HoldAll; an attribute of HoldAllComplete, however, would have prevented up-values from firing. Also, we need a method for acquiring the symbols we wish to localize:

ClearAll[getSymbols];
SetAttributes[getSymbols, HoldAllComplete];
getSymbols[(Set|SetDelayed|TagSet|TagSetDelayed)[a_Symbol,__]] := flatHold[a]
getSymbols[(Set|SetDelayed)[a_,_]] := 
 Cases[Unevaluated@a , 
  r_Symbol /; !MemberQ[Attributes[r], Protected|Locked|ReadProtected]:> flatHold[r], 
  {0, Infinity}, 1, Heads->True]
getSymbols[expr_] :=
 Cases[Unevaluated@expr,
  s:_Set | _SetDelayed |_TagSet | _TagSetDelayed:> getSymbols[s], Infinity]

The second form of getSymbols is as complicated as it is because of having to deal with SubValues, and the fact that Head@q[r][y] == q[r]. There is probably a better way, though. Unfortunately, it does not deal with UpSet or UpSetDelayed, which I leave as an exercise to whoever can come up with something. The third form allows the user to pass in an arbitrary expression.

With those, the new version of Scope becomes

ClearAll[Scope2];
SetAttributes[Scope2, HoldAll];

Scope2[{globals:(_Symbol | _Set|_SetDelayed)...}, body_]:=
(
 (* Put globals into Global` or equivalent *)
 globals;

 With[{vars = getSymbols[body]//DeleteDuplicates}, Block[vars, body]]
)

which for all practical purposes is a one liner. Which as you can see

x = 1;
y = 1;
Scope2[{x = 2},
 q[x_] := x^2;
 r /: q[r] := 5;
 {y = 2, a = 0, q[2], q[r]}
]

{x, y, a, r}
DownValues[q]
UpValues[r]
(* 
 {2, 0, 4, 5}
 {2, 1, a, r}
 {}
 {}
*)

OwnValues, DownValues, and some UpValues are localized to the body of Scope2.

$\endgroup$
9
  • $\begingroup$ I'll definitely be playing with this this evening. $\endgroup$
    – Mr.Wizard
    Commented Sep 5, 2013 at 16:07
  • $\begingroup$ @Mr.Wizard its flaw is annoying me. To work around it, I think Block is a good option, but that requires getting all the symbols that are defined within body. I can easily get a list of Symbols that are Set, TagSet, and their delayed versions, but not UpSet or UpSetDelayed. A method would be to evaluate those, see what UpValues are generated, and clear them, but I'm hoping to find a less hackish method. $\endgroup$
    – rcollyer
    Commented Sep 5, 2013 at 16:11
  • $\begingroup$ @Mr.Wizard added a new version which deals with the shadow problem. I still don't have a handle on UpSet and UpSetDelayed, but it works well, otherwise. $\endgroup$
    – rcollyer
    Commented Sep 5, 2013 at 17:07
  • $\begingroup$ I only have a couple of minutes to skim this, but is there a reason you can't extract all non-System-context Symbols? $\endgroup$
    – Mr.Wizard
    Commented Sep 6, 2013 at 1:07
  • $\begingroup$ @Mr.Wizard I think that would vacuum up to much. Consider: q[x] = 7 where x is global with a value. Siphoning off all non-System symbols would create issues. Of course, I could avoid any symbols on the $ContextPath, but then this would under sample the symbols, e.g. if x has a global value, it would not be localized. So, I don't see a clear way to do it by more general means. Let me know if you see one. $\endgroup$
    – rcollyer
    Commented Sep 6, 2013 at 3:19
10
$\begingroup$

I believe this is related in concept to Call Functions From File Without Modifying Context (Sandbox).

As Nasser comments, you may find utility in Begin and End and related BeginPackage and EndPackage. You'll have some trouble turning this into a function as normally Begin and End must be on separate lines from the rest of your code.

If it is acceptable to use lines before and after your code rather than a Module-like function you do your own $ContextPath manipulation. Here is a start:

Module[{isEx, conlst},
 BeginLocal[] := Module[{con},
   conlst = $ContextPath;
   BeginPackage[SymbolName@con <> "`"];
  ];
 System`EndLocal[] :=
   If[ValueQ@conlst, EndPackage[]; $ContextPath = conlst; Clear@conlst];
]

Now:

x = 1;
y = 1;

BeginLocal[]

Global`x = 2;
y = 2;
a = 0;

EndLocal[]

{x, y, a}
{2, 1, a}

Note that I had to reference the global x with an explicit context. I am trying to find a way around that now. One must deal with the fact that there is (apparently) no way to expose specific symbols to the context path.

$\endgroup$
2
$\begingroup$

The OPs question begins with: "Mathematica, unlike many other languages, defines the variables in the global scope unless it is explicitly asked to define a variable in a local scope, e.g., using Module, With, or Block."

This is not completely correct, and Mathematica has a method of doing local scoping -- it is just different from most other languages. It is possible to specify that all variables within each notebook be local to that notebook. This is done with the menu item:

Evaluation -> Notebook's Default Context -> Unique to this Notebook

So for instance, if you open a notebook (or choose a new notebook) and set

a = 5

then a has this value only within the given notebook. Opening another window, the value of a is unspecified.

$\endgroup$
1
  • 1
    $\begingroup$ Additionally, c/c++ did not have a "complete" form of this until very recently. At best, you'd scope to the nearest enclosing function/class/namespace/file (and file was iffy). More recently, {int a = 5;} scopes a to the enclosing braces. Mma could do this via Block and crew, long ago. And, the function parameters have always been scoped, so it is only the local vars that needed extra attention. $\endgroup$
    – rcollyer
    Commented Sep 5, 2013 at 18:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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