12
$\begingroup$

I'm trying to create a function "InContext" to execute a piece of code inside a certain context (and to return the result of evaluating that piece of code).

One approach is to place the code inside a given string.

InContext[context_String]:=Module[{action},
Begin[context];
action = ToExpression[#];
End[];
action]&;

This can be checked by giving the code string "a=1":

InContext["customcontext`"]["a=1"]
(*1*)
a
(*a*)
customcontext`a
(*1*)

However I would like to use a full Mathematica Expression as code (i.e. not inside a string), ideally something like Hold[code].

I've tried, for example:

InContext2[context_String]:=Module[{action},
Begin[context];
action = ReleaseHold[#];
End[];
action]&;

But the result is not the intended, as the code gets evaluated in the Global context:

InContext2["customcontext`"][Hold[b=2]];
(*2*)
b
(*2                      should be b*)
customcontext`b
(*customcontext`b        should be 2 *)

Why is the context changing differently this time, and how can it be made to change "correctly" so that the code is evaluated in the custom context?

$\endgroup$
1
  • $\begingroup$ @Rolf, why do you suspect that it isn't possible? $\endgroup$
    – Labareda
    Nov 23, 2013 at 21:45

3 Answers 3

10
$\begingroup$

This is possible in the interactive session with $PreRead. I will adopt my solution to the same problem posted in this Mathgroup thread. To quote my explanation from there, the essence of the present solution is to delay the parsing of the code (body) that must be executed inside a given context until run-time, that is, replace code

BeginPackage[yourcontext]
body
EndPackage[]

with something like

BeginPackage[yourcontext]
Unevaluated[ToExpression[body-in-the-box-form]]
EndPackage[]

This leads to parsing being performed after BeginPackage statement has been executed, which solves the problem.

So, here is the code:

ClearAll[stringify];
stringify[code_RowBox] := 
   ToString[code /. x_String :> ToString[x, InputForm]];


ClearAll[shieldBody];
shieldBody[body_String]:=
    RowBox[{"Unevaluated","[",RowBox[{"ToExpression","[",body,"]"}],"]"}]


ClearAll[namespaceF];
namespaceF[code_]:=
   code/. 
      RowBox[{"inContext","[",RowBox[{context_,",",body_RowBox}],"]"}]:>
         RowBox[
            {                   
               RowBox[{"BeginPackage","[",RowBox[{context}],"]"}],
               ";",
               shieldBody[stringify[body]],
               ";",
               RowBox[{"EndPackage","[","]"}]
            }
         ];

To make this work, you will need to assign this to $PreRead:

$PreRead = namespaceF;

The reason you need $PreRead is that it is the only hook which is called before the parsing takes place. Anything you do after the parsing is over, including using $Pre, putting stuff in Hold, etc., won't help, because the contexts assigned to symbols during the parse-time. This also explains why your code didn't work.

Here is an illustration then:

inContext["TestContext`", Print[$Context]; z = 7; Print[Context[z]]]
During evaluation of In[18]:= TestContext`

During evaluation of In[18]:= TestContext`
Context[z]

(* "TestContext`" *)

Of course, since BeginPackage is used, the symbol gets imported (the context stays in the $ContextPath before Global`), so subsequent references to z in Mathematica session will reference TestContext`z. If this importing is not desirable, one can change the BeginPackage to some different code.

Note that this construct does not support nested contexts created with Begin and End - this is doable but I did not implement this. For instance, in the following you may expect the symbol to be created in the `Private` context:

inContext["TestContext`", Begin["`Private`"];test = 10;End[]]

But this is not so - it was created in TestContext` and subsequently imported, since by the parse-time that was the current context:

Context[test]

(* "TestContext`" *)

Now, for the packages, $PreRead won't work, but one can get similar functionality by overloading Get / Needs. I won't elaborate on this more here at this time, though.

$\endgroup$
8
$\begingroup$

As Leonid has explained the problem is that the symbols are created and get their context at parse time, so if you need to avoid generating them in the current (usually "Global`") context, using $PreRead as he explained is the only possibility.

If you don't care that the symbols you use are created in the current context AND the context you want to evaluate in then you can do something relatively simple (it's probably worth noting that Module also behaves this way as well, and for the same reason). I intentionally keep this very simple, but it should work for basic code:

SetAttributes[evaluateInContext, HoldRest]

evaluateInContext[cntxt_, code_] := With[{
        codeAsString = ToString[Hold[code], InputForm]
    },
    Block[{
           $Context = cntxt, 
               $ContextPath = DeleteCases[$ContextPath, $Context]
       },
       ReleaseHold[ToExpression[codeAsString]]
    ]
]

What it does is to convert the code to an input string and reparse and evaluate that string in the desired context. The trick here is to intermediately set $Context to the desired context and remove the current context from $ContextPath, so that the symbols that already were generated during parsing in the current context are not found when evaluating ToExpression. This should now work as desired for basic code, e.g.:

 evaluateInContext["Test`", a = 1; b = 2; c = a + b;]

You should note that all three symbols are created (if they didn't exist) in the current context, but they don't get values, not even intermediately. All definitions are made to the symbols in the desired context, which you can both check with e.g.:

Names[$Context <> "*"]
ReleaseHold[{OwnValues /@ Hold[a, b, c]}]
ReleaseHold[{OwnValues /@ Hold[Test`a, Test`b, Test`c]}]

One of the relatively obvious drawbacks is that because we remove the current context from $ContextPath none of the definitions of symbols in it will be found within the reparsed code. Because of that something like the following will not work as one might wish:

x = 1;
evaluateInContext["Test`", a = x; b = 2; c = a + b; ]

Here the x used is Test`x, not Global`x (assuming we start in the global context) which was set to the value 1. Thus Test`a is now set to Test`x and not to 1, as one might naively expect and certainly wish in most situations. Of course it could be argued that this is so "by design" and is what you asked for, but it might make things somewhat uncomfortable in practice. What one could do is to either use With to insert values or implement additional arguments/options which would give the user the possibility to let the code know which of the symbols should be used from the current context. It should be emphasized that without a hook like $PreRead there is no way to know which did already exist in the current context and which didn't for the code to evaluate in the new context. The "visibility" of the already existing symbols in the current context is of course a quite important feature that other answers/comments probably implicitly understood as a requirement. With that requirement what you want indeed is impossible to achieve without Leonids $PreRead trick, which doesn't have that problem as it can leave $ContextPath untouched...

$\endgroup$
4
  • $\begingroup$ +1. I was aware of this possibility, but did not post this kind of solution exactly because the symbols are also created in the current context (which, as you noted, may not be a big issue). Your solution is somewhat similar to what I did here. $\endgroup$ Nov 24, 2013 at 12:09
  • $\begingroup$ @Leonid: I haven't seen the answer you mentioned, but I remembered that it was you who helped me to understand better the problem of symbols being created during parsing. I actually think that the inability to easily use the definition of the existing symbols in the calling context might in practice be the most important problem of this approach. This only leaves a relatively small area where it can be applied (and provides a real advantage)... $\endgroup$ Nov 24, 2013 at 15:42
  • 1
    $\begingroup$ "the inability to easily use the definition of the existing symbols ..." - sorry, I am not in my best shape today, so could you expand a bit on that (you lost me here)? By the way, there is a little-known capability: by calling Context[symbol]=newcontext, you can actually change the symbol's context (move it with all its definitions to another context). I only discovered this relatively recently, and did not yet exploit the consequences. $\endgroup$ Nov 24, 2013 at 15:47
  • $\begingroup$ Sorry, have had two very busy days. I tried to explain more clearly what I meant (although I'm not confident that I was successful). The use of Context seems to be a really interesting feature, I'll certainly play with that. It seems to be the key to interesting solutions for these kind of problems... $\endgroup$ Nov 28, 2013 at 7:43
4
$\begingroup$

Since it was mr. Leonid Shifrin who provided a proper solution, I feel less guilty of my brute-force, very limited try: it adds the context to the first symbol in every Set and SetDelayed.

a =.; b =.; c =.
context2`a =.; context2`b =.; context2`c =.

InContextSetAndSetDelayed := Function[{context, code}, 
  code 
     /. SetDelayed -> f
     /. f[name_Symbol, what_] :> f[Evaluate@Symbol[context <> SymbolName@name], what] 
     /. f -> SetDelayed 

     /. Set -> f
     /. f[name_Symbol, what_] :> f[Evaluate@Symbol[context <> SymbolName@name], what] 
     /. f -> Set 

  // ReleaseHold
]

InContextSetAndSetDelayed["context2`", HoldForm[a = 3; b := RandomInteger[10]; c := 13]]

Print@{a, b, c}
(* {a, b, c} *)
Print@{context2`a, context2`b, context2`c}
(* {3, 3, 13} *)    
Print@{context2`a, context2`b, context2`c}
(* {3, 6, 13} *)

It only works with single symbols on the left-hand side, and you cannot cross-reference them, as in a = 10; b = a

If you're wondering why Set and SetDelayed must be translated to f (a proper unique symbol is preferable), so am I!

$\endgroup$
4
  • 1
    $\begingroup$ the reason is that you need to prevent the patterns in your rules from evaluation. You can achieve the same thing with using only one ReplaceAll with this rule: HoldPattern[(s : (Set | SetDelayed))[name_Symbol, what_]] :> s[Evaluate@Symbol[context <> SymbolName@name], what]. | is a shortcut for Alternatives. If you need to match symbols used to build patterns, you might sometimes not only have to use HoldPattern but also Verbatim... $\endgroup$ Nov 24, 2013 at 15:53
  • $\begingroup$ Thank you! I completely forgot about Alternatives... HoldPattern is working wonders, but not when I try to pattern-match Pattern, as in HoldPattern[Pattern[a_, _]] :> a)... am I pushing it too far? $\endgroup$
    – Aisamu
    Nov 28, 2013 at 13:56
  • 1
    $\begingroup$ No, you need Verbatim for that one (and everything that is used to make up patterns): testit_ /. Verbatim[Pattern][a_, _] :> a. When a combination of both is needed things become a little hard to read :-) $\endgroup$ Nov 28, 2013 at 22:08
  • $\begingroup$ ohhhhhhh, that makes perfect sense! Haha I must have tried nearly every combination of HoldX, Verbatim, Attributes, you name it.... You crossed off a very important item from my "Mathematica mysteries" list. Thank you again! $\endgroup$
    – Aisamu
    Nov 28, 2013 at 23:54

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.