# ToString with delayed replacement [duplicate]

I want a toString method that replaces each occurrence of f[x_].

obj1ToString[x_] := StringRiffle[x, ""];
obj2ToString[y_] := ToString[y /. f[x_] :> "(" <> obj1ToString@x <> ")"];

obj2ToString[g@f[{1, 2, 3}]]
(* "g[(123)]" *)
obj2ToString[f[{1, 2, 3}] &]
(* "(<>obj1ToString[{1, 2, 3}]<>) & " *)


As you can see, it doesn't work in all contexts. How do I fix this?

• What is the desired output of obj2ToString[f[{1, 2, 3}] &]? Jun 30 '21 at 16:13

## Edit

As @CarlWoll points out, the original solution is unstable - easily breaking for some arguments of f. The better solution is to do something like

toString[y_]:= ToString[y /. f[x_]:> With[{eval="("<>StringRiffle[x,""]
<> ")"},eval /; True]];


which is based on this thread here: Replacement inside held expression

## Original

Just turn your whole expression into a string then do your replacement.

toString[y_] := StringReplace[ToString[y],"f[" ~~ Shortest[x__] ~~ "]" :>
"(" <> StringRiffle[ToExpression@x, ""] <> ")"];

• In general, I recommend avoiding approaches that convert to strings and then use string manipulation functions. In particular, using single argument ToString is very bad, consider toString[f[{1/2}]]. Your replacement also has issue, e.g., toString[f[{g[x]}]. Finally, approaches using ToExpression can be scary if the function can be applied to arbitrary content. Jun 30 '21 at 20:16
• @CarlWoll, all fair points. Given the OP's definition of obj1ToString using StringRiffle it is clear they expect the argument of f to always be a List of symbols. Given what's written, it's unclear what behavior they even want from something like toString[f[{g[x]}] (although it's safe to assume \$Failed isn't it). I suppose I should have waited for their clarification before shooting off this ugly hack of an answer. I agree it's dangerous as written. When OP clarifies what they're looking for I'll improve the answer. Jun 30 '21 at 20:33