# How to overload SystemStringJoin to automatically use ToString on arguments?

I am using an overloaded version of the StringJoin function since years now, without any problem, as I've invested a lot of time and effort earlier to make its behaviour consistent and predictable (see discussion here). I am fully aware that modifying built-in symbols is not a good idea.

The new StringJoin automatically converts any non-string input to String thus I don't have to type ToString every time. It still threads over lists, i.e. StringJoin[{"1", "2", "3"}] returns "123". Also StringJoin[{1, 2, 3}] returns the same, but StringJoin[{1, 2, 3}, "s"] gives "{1, 2, 3}s". The code is:

toString[expr_String] := expr;
toString[expr_] := ToString@expr;
Unprotect[SystemStringJoin];
Attributes[SystemStringJoin] = {};
SystemStringJoin[expr___] := StringJoin@{expr};
SystemStringJoin[expr_List] :=
Fold[StringInsert[#1, toString@#2, -1] &, "", expr];
Protect[SystemStringJoin];


For me, it works as expected, and I am really happy to use it as it saves me a lot of typing. Though there is one minor annoyance I cannot track down. Consider the following example:

Append[test, 1];


Note, that the message is printed with List-s wrapping each argument of the printed StringForm (that is: 1 and Append[test, 1]). Interestingly, StringForm on its own works as expected:

StringForm["Test variable insertion: 1, 2.", 111, 222]


Question: Can anyone explain why Message fails to print its result correctly?

Caveat:

Modifying System symbols could have many unexpected side-effects. It happens for this modified StringJoin too (apart from the above example): as Import uses StringJoin, the modification causes a massive performance drop when importing e.g. images. For details, see this post.

-

Short answer: because StringJoin has this little-known behavior normally:

StringJoin["abc", {"def"}, "ghi"]

(*
==> "abcdefghi"
*)


while after you overloaded it, the result is

"abc{def}ghi"


You start by using

Trace[Append[test, 1], TraceInternal -> True]


from the Trace output, you can locate the following code:

StringJoin[(If[ListQ[#1], StringJoin["\[NoBreak]", #1, "\[NoBreak]"],  #1] & ) /@
StringSplit[
"Nonatomic expression expected at position 1 in 2.",
SystemDumpdel : "" ~~ DigitCharacter ... ~~ "" :>
{SystemDumpdel}]
]


returning

"Nonatomic expression expected at position \[NoBreak]{}\[NoBreak] in \[NoBreak]{}\[NoBreak]."


Conclusions

Don't overload built-ins globally. Who knows what else you will break? There are other options to do what you want. For example, you can use InternalInheritedBlock to create local environments, as explained e.g. here.

-
Pardon me, but it is not my fault if I mess up something that is not documented at all (though I will suffer the consequences). Generally I never modify anything that's built-in, but this function proved really useful throughout the years. Of course it's a PITA when you have to deploy packages to others... –  István Zachar Feb 6 '12 at 14:44
So, Leonid, I guess you highly DO NOT recommend to put a Flatten in the 6th line to read: SystemStringJoin[expr_List] := Fold[StringInsert[#1, toString@#2, -1] &, "", Flatten@expr];  –  István Zachar Feb 6 '12 at 14:56
@Istvan The problem is that the system itself is using many of its own functions, to implement other functions. And Mathematica as a language does not have a formal specification, and is defined by its single implementation. In practice, I think it is just not possible to anticipate all the consequences of your modifications for such a huge system. Even in your example, the problem happened in a rather unexpected place, and only because you overloaded a single function, you could make a connection. Overload many, and it will go off hand. This is just my opinion, of course. –  Leonid Shifrin Feb 6 '12 at 16:17
@IstvánZachar The behavior of StringJoin when given lists is documented in the third Basic Example, even if it's not under More Information. –  Brett Champion Feb 6 '12 at 18:46
@BrettChampion: indeed, I did realize that later. @Leonid: Not at all, since I don't use StringJoin in heavy computation. Moreover, my question was answered perfectly :) –  István Zachar Feb 7 '12 at 8:59

I agree that a user needs to proceed with caution when modifying System functions. However, once a long time ago I saw a Wolfram Research tech-support web-page that provided a way to overload a System function to patch a bug. My code below uses the same trick which is very neat!

$modifyStringJoin = True; wasProtected = Unprotect[StringJoin]; StringJoin[args__] /;$modifyStringJoin := Block[{$modifyStringJoin}, StringJoin[Map[ToString, Flatten[{args}], Heads -> False]] ]; Apply[Protect, wasProtected, Heads -> False];  This version has linear complexity. Also, if you ensure $modifyStringJoin is not True, the code above is disabled.

---- Going off on a tangent -----

Using Apply[Protect, wasProtected] ensures ToString is Protected 'if and only if' it was protected before evaluating the code above.

Notice I use the InputForm of Map, Apply, and I include (Heads->False). I always do this to ensure my code is not brokent if somebody evaluates

   SetOptions[Map, Heads->True];


However, I don't know what system code would be broken if you do that!

Imagine what would happen to the system code if you evaluate

Unprotect[Plus];
Attributes[Plus, {Flat, Listable, NumericFunction, OneIdentity, Orderless}];


or if you overload ReplacePepeated!

-