10

I'm trying to make a function f that takes a symbol, a value, and a context, and creates the symbol in the requested context and assigns it the passed value. The obvious doesn't work. I.e.:

Attributes[f]={HoldAllComplete};
f[symbol_,value_,context_]:=(Begin[context];Set[symbol,value];End[])

Creates the symbol in the current context, as opposed to the one that is passed as an argument. This seems to happen because the FrontEnd, as soon as it sees f getting evaluated, creates the symbol in the current context despite HoldAllComplete. I tried adding Remove[symbol] to the function but that doesn't work. Any thoughts?

Mohammed AlQuraishi
  • 1,002
  • 5
  • 15
  • I should clarify that I really need to pass the symbol, not the symbol's name. I realize it can be done when passing the symbol's name, but I can't use that approach because it would break a lot of existing code. I realize that I can overload the function and have a legacy version that takes the symbol and does not allow contexts, and one that takes in strings and does allow for contexts, but ideally I would like to avoid such a hack. – Mohammed AlQuraishi Apr 04 '13 at 18:47

3 Answers3

7

The problem is that if you pass a symbol, it will be created already during the parsing stage, when you pass it, in the current context. Therefore I suggest to pass its string name instead. This function will do the job:

ClearAll[f];
f[symbolName_String, value_, context_] :=
  Block[{$ContextPath},
    BeginPackage[context];
    ToExpression[
       symbolName, 
       StandardForm, 
       Function[name, Set[name, value], HoldAll]
    ];
    EndPackage[]
  ] 

for example

f["a", 10, "Test`"]
Test`a

(* 10 *)

If you still want to pass a symbol, you can use this:

ClearAll[f];
f[symbol_, value_, context_] :=
  With[{set = MakeBoxes[symbol = value]},
    Block[{$ContextPath},
      BeginPackage[context];
      ReleaseHold[MakeExpression@set];
      EndPackage[]]];

which is a version of the code I used here. But be aware that you will also create the symbol symbol in the current working context, so you may additionally use Remove to remove it.

So, for example:

f[a, 20, "Test`"]
Test`a

(* 20 *)
Leonid Shifrin
  • 114,335
  • 15
  • 329
  • 420
  • Thanks for the suggestion but this won't work for my needs. See the comment that I added to the question. – Mohammed AlQuraishi Apr 04 '13 at 18:49
  • 2
    @OleksandrR. The symbol with the same name may exist in some other context currently on the $ContextPath, in which case that one will be used and assigned a value - which is obviously not what one would like here. The use of BeginPackage and EndPackage effectively allows to reset the $ContextPath to just {"MyContext`","System`"}, which solves this problem. – Leonid Shifrin Apr 04 '13 at 19:24
  • @MohammedAlQuraishi I added a symbol-based version, see my edit. – Leonid Shifrin Apr 04 '13 at 19:29
  • @LeonidShifrin: Great, thank you! – Mohammed AlQuraishi Apr 05 '13 at 03:02
  • I just spent a while recreating and debugging code equivalent to your second method. I really should read your answers better before starting on my own. – Mr.Wizard Apr 05 '13 at 15:16
  • 1
    @Mr.Wizard I will use your usual reply: "great minds think alike" :) – Leonid Shifrin Apr 05 '13 at 15:41
4

I believe this is what you want:

f[symname_String, value_, context_] := 
 (Begin[context];
  With[{s = Symbol[symname]}, Set[s, value]]; End[])

Then use it like this:

 f["myvar", 4, "MyContext`"]

Verify:

? MyContext`myvar

Hope that helps

MaTECmatica
  • 588
  • 4
  • 12
  • Really? Maybe if you do a ClearAll[f], or restart a new, fresh Mathematica session? or give the function another name? I am guessing your old definition for f is in conflict with the one I propose. Notice the idea is the same as the one by Leonid, give the name as a string "myvar" instead of myvar – MaTECmatica Apr 04 '13 at 18:46
  • 1
    It does work for me. But, invoking f twice for the same symbol will produce an error. You need to take Leonid's approach of using ToExpression rather than Symbol if you want to avoid that. – Oleksandr R. Apr 04 '13 at 18:56
  • @QuantumMathematica Yes indeed, on a fresh kernel it does work. – Leonid Shifrin Apr 04 '13 at 19:33
2

I believe this meets your specification:

Attributes[f] = {HoldAllComplete};

f[symbol_, value_, context_] :=
 ToHeldExpression[context <> SymbolName@Unevaluated@symbol] /. _[x_] :> (x = value)
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371