25
$\begingroup$

Given a function with the attribute HoldFirst, HoldAll or similar, and a variable, list, how can I pass the variable's name to the function without the variable being evaluated prematurely? So it is "list" that is to be passed to the function (e.g. AppendTo) instead of list.

The following method fails, as AppendTo holds the evaluation of Symbol @ "list", and since Symbol[...] is not a valid variable for which a value can be assigned, an error message appears:

list = {1, 2, 3};
AppendTo[Symbol @ "list", 1];
list
Set::write: Tag Symbol in Symbol[list] is Protected.>>

{1, 2, 3}

I should mention, that for certain reasons the obvious direct assignment below is not possible, as I must pass the list as a string:

list = {1, 2, 3};    
list = Append[list, 2]; (* this works, but is not what I am looking for *)

{1, 2, 3, 2}

The next approach also fails, as it issues an error message while returning the correct result:

list = {1, 2, 3};
held = AppendTo[Evaluate @ ToExpression["list", InputForm, Hold], 3];
ReleaseHold @ held;
list
AppendTo::rvalue: Hold[list] is not a variable with a value, so its value cannot be changed.>>

{1, 2, 3, 3}

The final approach reveals that ReleaseHold removes the outermost Hold, but leaves the innermost intact.

list = {1, 2, 3};
held = Hold @ AppendTo[Evaluate@ToExpression["list", InputForm, Hold], 4];
ReleaseHold @ held
AppendTo::rvalue: Hold[list] is not a variable with a value, so its value cannot be changed.>>

AppendTo[Hold[list], 4]
$\endgroup$

5 Answers 5

22
$\begingroup$

How about this:

list = {1, 2, 3};
ToExpression["list", InputForm, Hold] /. Hold[v_] :> AppendTo[v, 3]

{1, 2, 3, 3}

list

{1, 2, 3, 3}

$\endgroup$
2
  • $\begingroup$ This works as well for a list of variables, and seems to be a bit more straightforward than celtschk's answer. I am doing some more testing. $\endgroup$ Mar 6, 2012 at 22:58
  • 3
    $\begingroup$ ToExpression["list", InputForm, Hold] can be written as ToHeldExpression@"list" $\endgroup$
    – masterxilo
    Jul 22, 2016 at 13:18
17
$\begingroup$

Not to detract from the existing answers (particularly @WReach's suggestion, which was the same solution that came to my mind as I read your question, and which I will use here), but you may find it easier to define your own references rather than using strings. (In fact, I wouldn't necessarily recommend an approach based on building Mathematica expressions out of strings as a good first choice in general.) The advantage of using your own operator is that it will be easier to get rid of it when you don't want it any more, and no complex syntax is needed to actually use it.

First, let us define the reference object (which is essentially just a wrapper):

Attributes[referenceTo] = {HoldAll, Listable};

referenceTo[str_String] :=
  referenceTo[Evaluate@ToExpression[str, InputForm, Hold]];

referenceTo@Hold[sym_Symbol] := referenceTo[sym];

referenceTo /: f_[argsPre___, ref : referenceTo[str_String], argsPost___] := 
 f[argsPre, Evaluate[ref], argsPost]

We can either use this directly, or on strings. For example:

{a, b, c} = Range[3]; (* make some definitions to prove it works *)

referenceTo[{"a", b, Hold[c]}]
    (* -> {referenceTo[a], referenceTo[b], referenceTo[c]} *)

So, while very simple, the behaviour of the wrapper is probably sufficient for most purposes in terms of creating the references in the first place (and, of course, the range of situations in which it is recognized can readily be expanded if necessary). Now we tell it when and how to dereference the symbols it wraps:

$dereferencingFunctions = {AppendTo, ReleaseHold};
referenceTo /: f_[argsPre___, referenceTo[sym_Symbol], argsPost___] /; 
 MemberQ[$dereferencingFunctions, f] :=
  f[argsPre, sym, argsPost]

You can now manipulate $dereferencingFunctions to switch this behaviour on and off for different functions as required and avoid the need for manual dereferencing as part of each call. Thus,

list = {elem1};
AppendTo[referenceTo[list], elem2];
stuff = elem3;
AppendTo[referenceTo["list"], referenceTo[stuff]];
(* a rather odd way to use it, but possibly useful: *)
AppendTo @@ referenceTo[{"list", elem4}];

evaluates correctly and without producing any messages, and gives:

list
    (* -> {elem1, elem2, elem3, elem4} *)

Or, if you prefer:

ReleaseHold@referenceTo["list"]
    (* -> {elem1, elem2, elem3, elem4} *)

In my opinion, despite its extreme simplicity, this approach manages to be much more practical than using strings directly.

$\endgroup$
5
  • $\begingroup$ While I agree with you that a proper wrapper for referencing would be the best solution, at the moment I still try to solve my issues with structural replacements. Your solution seems robust, but I wouldn't say it is simple. At least not at 2am :) $\endgroup$ Mar 7, 2012 at 1:49
  • $\begingroup$ Would it be possible to extend this approach, so that this would work as expected x=5; referenceTo["x"] = 15; x? In other words, so that variables could be set by reference as well? $\endgroup$
    – Ajasja
    May 22, 2012 at 12:54
  • $\begingroup$ @Ajasja: Yes, just write AppendTo[$dereferencingFunctions, Set] and it works automatically. (This won't work for list assignments, like {q, referenceTo[r], referenceTo["s"]} = {1, 2, 3}, though. Some changes would be needed to support that.) $\endgroup$ May 22, 2012 at 22:04
  • $\begingroup$ Thanks! I actually tried that, but it was giving me a warning that went away upon restarting a fresh kernel. Is it safe to use this with SetDelayed as well? $\endgroup$
    – Ajasja
    May 23, 2012 at 8:00
  • $\begingroup$ @Ajasja: yes, there shouldn't be any evaluation leaks as the arguments are passed right through with the symbols dereferenced. The approach is so simple that I'm sure you'll be able to find some corner cases, but for typical usage I think it is robust enough (at least, better than using strings, which was the main point here). $\endgroup$ May 24, 2012 at 0:29
8
$\begingroup$

Injector pattern:

list = {1, 2, 3};

MakeExpression["list"] /. _[sym_] :> AppendTo[sym, 4]

Function (here using the Null syntax trick):

Function[, AppendTo[#, 4], HoldAll] @@ MakeExpression["list"]
$\endgroup$
4
  • 1
    $\begingroup$ I had a different name for this pattern, and am also a big fan and frequent user of it, but Injector seems better. +1. $\endgroup$ Mar 7, 2012 at 6:20
  • $\begingroup$ The syntax highlighter definitely doesn't like your code, Mr.W (I do). $\endgroup$ Mar 7, 2012 at 10:59
  • $\begingroup$ @Istvan it took me a little bit to figure out what you mean. Normally I have "Missing arguments" highlighting turned off. Since this is AFAIK a perfectly valid use of MakeExpression I suggest that you modify your SyntaxInformation accordingly if you like to keep that setting turned on. You can also use ToHeldExpression instead if you prefer. $\endgroup$
    – Mr.Wizard
    Mar 7, 2012 at 13:55
  • $\begingroup$ It is not just the missing second argument for MakeExpression, but 1) since there is no & for the #, it is coloured purple, and 2) the comma in Function is coloured as well. Not that I mind any of these. $\endgroup$ Mar 7, 2012 at 14:07
8
$\begingroup$

Here is yet another possibility (which, in a way, combines some of the suggestions given already): define a new scoping construct, most similar to Function, to perform the task you need:

ClearAll[strFunction];
SetAttributes[strFunction, HoldAll];
SyntaxInformation[strFunction] =
     {"ArgumentsPattern" -> {_, _}, "LocalVariables" -> {"Solve", {1, 1}}};
strFunction[var_, expr_] :=
   Function[strvar,
      ToExpression[strvar, InputForm,
          Function[held, 
             Unevaluated[expr] /.HoldPattern[var] :> held, 
             HoldAll
          ]
      ]
   ];

You can use it as:

list = {1, 2, 3};
strFunction[l, AppendTo[l, 1]]["list"]

Mapping on a list of such names is also quite easy in this approach:

Map[strFunction[l, AppendTo[l, 1]], list-of-symbol-names]

You can also add type-checks, generalize to functions with more than one variable, etc.

$\endgroup$
1
  • $\begingroup$ @Ajasja Ok, I see - there is indeed an evaluation leek. Sorry, you spotted it correctly. Should change Unevaluated[var] :> held to HoldPattern[var] :> held - will edit in a moment. Thanks for spotting this - I am getting older :). Will delete my erroneous comments. $\endgroup$ May 22, 2012 at 11:59
6
$\begingroup$

This works:

ReleaseHold@Block[{list},Hold[AppendTo["list",1]]/."list"->Symbol["list"]]

The mechanism is as follows:

  • The block temporarily undefines list so that after Symbol["list"] is evaluated, evaluation stops.
  • However, the AppendTo shall not be evaluated as long as list is undefined, therefore it is wrapped in Hold.
  • Symbol shall be evaluated, therefore it may not be inside Hold. For this reason, it is injected into the Hold expression with a replacement rule.
  • When leaving the block, list gets back its old definition, therefore all we have to do now is to remove the Hold so that the generated AppendTo expression is evaluated.
$\endgroup$
1
  • $\begingroup$ Nice, and it can also be mapped over a list of variables! $\endgroup$ Mar 6, 2012 at 22:36

Your Answer

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

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