2
$\begingroup$

I try to Reassign Lists after adding new Elements as the following Examples:

m1 = Table[{"A", "B"}, 3]; m2 = Table[{"C", "D"}, 4];
n1 = Range[3]; n2 = Range[4];

With

AppendTo[ToExpression["m1"],n1]

I want to set this to the new List

m1={{"A", "B"}, {"A", "B"}, {"A", "B"}, {1, 2, 3}}

resp.

AppendTo[ToExpression["m2"],n2]

the new List

m2={{"A", "B"}, {"A", "B"}, {"A", "B"}, {1, 2, 3, 4}}
$\endgroup$
1
  • $\begingroup$ Experiment with replacing = with := and see how you get on. Then refer to the documentation to learn what's going on. $\endgroup$ Commented Mar 4, 2020 at 13:56

1 Answer 1

2
$\begingroup$

Summary

Presumably in the context of the real application the string containing the variable name is not known in advance like it is in the question. Otherwise, we could simply reference variables like m1 and m2 directly and none of the following complexity would apply. But under that assumption, use:

ToExpression["m1", InputForm, Hold] /. _[s_] :> AppendTo[s, n1]

or:

ToExpression["m1", InputForm, Function[, AppendTo[#, n1], HoldFirst]]

The Problem, Restated

The function AppendTo has the attribute HoldFirst so its first argument is not evaluated. That is why we get an error message when we try the AppendTo expression from the question:

AppendTo[ToExpression["m1"], n1]
(* Set::write: Tag ToExpression in ToExpression[m1] is Protected. *)

AppendTo is expecting a variable name as its first argument but it is getting the composite expression `ToExpression["m1"]. The first argument is not evaluated "enough". So, let's try evaluating it:

AppendTo[Evaluate@ToExpression["m1"], n1]
(* AppendTo::rvalue: {{A,B},{A,B},{A,B}} is not a variable with a value, so its value cannot be changed. *)

AppendTo is still not getting a variable name. Now it is getting the list that is the value of m1. The first argument is now evaluated "too much". What to do?

Solutions

To work around this problem, we need to interpret the variable string as an expression but prevent it from evaluating immediately after this interpretation. We can use the optional third argument of ToExpression to hold the result:

ToExpression["m1", InputForm, Hold]
(* Hold[m1] *)

Of course, we want to AppendTo the symbol, not just Hold it. We can use a pattern replacement:

ToExpression["m1", InputForm, Hold] /. _[s_] :> AppendTo[s, n1]
(* {{"A", "B"}, {"A", "B"}, {"A", "B"}, {1, 2, 3}} *)

m1
(* {{"A", "B"}, {"A", "B"}, {"A", "B"}, {1, 2, 3}} *)

Or, we can replace the third argument of ToExpression with a function that applies AppendTo directly:

ToExpression["m1", InputForm, Function[, AppendTo[#, n1], HoldFirst]]
(* {{"A", "B"}, {"A", "B"}, {"A", "B"}, {1, 2, 3}} *)

m1
(* {{"A", "B"}, {"A", "B"}, {"A", "B"}, {1, 2, 3}} *)

We have used the third argument to Function to specify that it should not evaluate its first argument since that would constitute "too much" evaluation.

Security Note

ToExpression will interpret any expression, not just symbols. So if the string containing the purported symbol name comes from an untrusted source then we ought not blindly use whatever random expression we are given. It is prudent to at least ensure that the expression is of a type we expect, in this case a symbol:

$nasty = "Evaluate@DeleteDirectory[\"realvaluablestuff\", DeleteContents -> True]";
ToExpression[$nasty, InputForm, HoldComplete] /. {_[s_Symbol] :> AppendTo[s, n1], _ -> $Failed}
(* $Failed *)

HoldComplete is used instead of Hold to stymie the clever hacker that put in that Evaluate. The function equivalent would be Function[, ..., HoldAllComplete].

It is out of the scope of this response to try to make even this simple case bulletproof. But be aware of these issues when untrusted strings are involved.

$\endgroup$

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.