3

I would like to get a list of variable names as strings. For example,

x = 1;
y = 2;
Map[SymbolName, Unevaluated /@ Unevaluated@{x, y}]

{"x", "y"}

This works well. However, if I use

Map[SymbolName @* Unevaluated, Unevaluated@{x, y}]

It does not work and drops an error message

SymbolName::sym: Argument 1 at position 1 is expected to be a symbol.

What's the difference between the two?

Yi Wang
  • 7,347
  • 4
  • 28
  • 38
  • 1
    related/duplicate: http://mathematica.stackexchange.com/q/54762/5 – rm -rf Jul 22 '14 at 04:50
  • @rm-rf thanks for pointing that out. If at the first moment I realized it is a Composition problem, I would have found that post :) – Yi Wang Jul 22 '14 at 04:53

2 Answers2

1

After more thoughts, I think I understood what's going on. Because a composed function SymbolName @* Unevaluated does not exhibit the HoldAllComplete attribute of Unevaluated. So the expression is evaluated before feeding into SymbolName @* Unevaluated.

A lesson is that, if we want to keep HoldFirst, HoldAll ... attributes, we have to map the functions one by one, instead of all together.

Yi Wang
  • 7,347
  • 4
  • 28
  • 38
1

As rm -rf pointed out this is because of the behavior I was addressing in my question:

Unfortunately as explained there (at least at present) there is no built-in way to make a composite function that respects hold Attributes.

For the purpose of performing the action in your question your original code is one of the standard methods. For more examples and details see:

You could manually create a Function object with a hold attribute, or you could use my comp function from the linked thread, e.g.:

Map[comp[SymbolName, Unevaluated], Unevaluated@{x, y}]
{"x", "y"}

Most often however I use what I call the "injector pattern" to avoid evaluation.
Here a variation with Cases is appropriate:

Cases[Hold[x, y], s_ :> SymbolName @ Unevaluated @ s]
{"x", "y"}
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371