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.
Scopedoesn't exist, but that's what the OP would likeScopeto do. The OP wants such a scoping construct. (It is right; contexts are the closest thing.) – C. E. Sep 05 '13 at 06:32Scope) with the global scope. – Helium Sep 05 '13 at 06:52Scope? In Mathematica there isn't really a difference between functions and variables like in some other languages. – Szabolcs Sep 05 '13 at 13:18