0
$\begingroup$

The official documentation of Unevaluated contains the following example:

SetAttributes[symbolLength, HoldAll];
symbolLength[s_Symbol] := StringLength[SymbolName[Unevaluated[s]]];

ClearAll[myvar];
myvar = 42;
symbolLength[myvar] (* Output: 5 *)

But I am unable to extend this example to a list. The following toy function does not work:

ClearAll[fn1];
SetAttributes[fn1, HoldAll];
fn1[args : {__Symbol}] := Map[SymbolName /* StringLength] @ Unevaluated[args];

ClearAll[a, bc, def];
{a, bc, def} = {10, 20, 30};
fn1[{a, bc, def}]
(*
    ERROR
    SymbolName: Argument 10 at position 1 is expected to be a symbol.
    StringLength: String expected at position 1 in StringLength[SymbolName[10]].
*)

What is the trick I am missing?

$\endgroup$
3

1 Answer 1

1
$\begingroup$

Later in the documentation you'll encounter this:

Unevaluated works only where it appears; it is not propagated:

What this means is that as soon as the evaluation engine encounters the Unevaluated expression a second time, the Unevaluated gets stripped. For example, if you evaluate just this,

Unevaluated[{a, bc, def}]

the output will be Unevaluated[{a, bc, def}]. If you evaluate this,

z = Unevaluated[{a, bc, def}]

the output will be {10, 20, 30}, but that's not actually what OwnValues[z] is--it's what you get after the evaluation engine does the replacement for z and then continues evaluating:

OwnValues[z]

is actually {HoldPattern[z] :> {a, bc, def}}. The Unevaluated was stripped, but since it found itself inside of a SetDelayed, the a, bc, def are still held and not immediately evaluated.

The "tricks" have been provided in the comments. You can propogate the Unevaluated yourself by mapping it over your Unevaluated list.

fn1[args : {__Symbol}] := StringLength /@ SymbolName /@ Unevaluated /@ Unevaluated[args]
(*suggested by @LukasLang*)

The outer Unevaluated will be immediately stripped to do the mapping but each element will now be wrapped in Unevaluated. Those new Unevaluateds will be stripped (but no further evaluation will occur yet) once SymbolName is applied, which is what you want at that point.

Or, you can make fn1 Listable, as @Kuba suggested. This means that the function you define will automatically be applied to each element of a list when a list is passed as the argument. To be clear, in that case you'd define fn1 for a single argument:

SetAttributes[fn1, {HoldAll, Listable}];
fn1[arg_Symbol] := StringLength[SymbolName[Unevaluated[arg]]]
$\endgroup$

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.