5
$\begingroup$

This post is related to this one

Let us see extremely simple test

StringTemplate["1``3"][2] // RepeatedTiming
"1" <> ToString[2] <> "3" // RepeatedTiming

gives

{0.000120292, "123"}
{1.0749*10^-6, "123"}

Then, I looked into the Trace of StringTemplate. It give out pages of complex trace result.

StringTemplate["1``3"][2] // Trace

gives

{{StringTemplate[1``3],TemplateObject[Templating`PackageScope`changeDelimiters[Templating`Parsing`PackagePrivate`parseStringTemplate[1``3],OptionValue[StringTemplate,{},Delimiters]],Templating`PackageScope`parseDefaultOptions[Options[StringTemplate],{}]],{{OptionValue[StringTemplate,{},Delimiters],Automatic},Templating`PackageScope`changeDelimiters[Templating`Parsing`PackagePrivate`parseStringTemplate[1``3],Automatic],Templating`Parsing`PackagePrivate`parseStringTemplate[1``3],Module[{Templating`Parsing`PackagePrivate`slotIndex$=0,Templating`Parsing`PackagePrivate`exprIndex$=0,Templating`Parsing`PackagePrivate`globalIndex$=1,Templating`Parsing`PackagePrivate`parsed$},Templating`Parsing`PackagePrivate`parsed$=Templating`Parsing`PackagePrivate`toElementList[1``3,True,False];Templating`Parsing`PackagePrivate`parsed$=Replace[Templating`Parsing`PackagePrivate`parsed$,{{TemplateSlot,Templating`Parsing`PackagePrivate`inner$_}:>(Templating`Parsing`PackagePrivate`slotIndex$++;Templating`Parsing`PackagePrivate`templateErrorWrapper[StringTemplate::badslot,Templating`Parsing`PackagePrivate`parsed$,Templating`Parsing`PackagePrivate`inner$,Templating`Parsing`PackagePrivate`globalIndex$++,Templating`Parsing`PackagePrivate`slotIndex$,Templating`Parsing`PackagePrivate`toTemplateSlot]),{TemplateExpression,Templating`Parsing`PackagePrivate`inner$_}:>(Templating`Parsing`PackagePrivate`exprIndex$++;Templating`Parsing`PackagePrivate`templateErrorWrapper[StringTemplate::badexpr,Templating`Parsing`PackagePrivate`parsed$,Templating`Parsing`PackagePrivate`inner$,Templating`Parsing`PackagePrivate`globalIndex$++,Templating`Parsing`PackagePrivate`exprIndex$,Templating`Parsing`PackagePrivate`toTemplateExpression]),Templating`Parsing`PackagePrivate`a$_:>(Templating`Parsing`PackagePrivate`globalIndex$++;Templating`Parsing`PackagePrivate`a$)},{1}];If[FreeQ[Templating`Parsing`PackagePrivate`parsed$,$Failed],Templating`Parsing`PackagePrivate`parsed$,$Failed]]
...
...
...
There are tons of more!

At first, I though I know why StringTemplate takes so long. But then I tried to write a faster one myself. Currently, I only got this

ClearAll[stringTemplate];
stringTemplate[string_][x__] := Module[{},
  xList = TextString /@ {x};
  split = StringSplit[string, "``" -> "``"];
  pos = Flatten@Position[split, "``"];
  Do[
   split[[pos[[i]]]] = xList[[i]], {i, 1, Length@xList}];
  StringJoin@split
  ]

Which only support unnumbered "``", and it is not that fast either

stringTemplate["1``3"][2] // RepeatedTiming
{0.00001226, "123"}

I found it is hard for me to write a stringtemplate support numbered or named slot. So is it possible to write a more efficient StringTemplate then built-in?

$\endgroup$
6
  • 2
    $\begingroup$ This reads as a complaint, thinly disguised as a question. I am not sure what answer this community could provide. It is a bit unfair to compare a use case like the first one, in which StringTemplate is obviously far too complex a tool for the job. The old post you linked had a better example of the trade-off between the readability and ease of use of StringTemplate, and the speed of a series of manually-coded StringJoin + conversions. For everyday code that is not time-sensitive, I tend to choose readability (= maintainability) over performance if the penalty is bearable. $\endgroup$
    – MarcoB
    Jul 5 at 15:43
  • 1
    $\begingroup$ @MarcoB Thank you for reply. I must confess I had a complain. Actually, for me, even a simple case like that in the post, I would choose to StringTemplate, because it is much more readable, and not error prone. But were you not surprised by the trace of StringTemplate? I made this post to get a reasonable explanation that StringTemplate really needs to be that complex. If not, then StringTemplate apparently needs to be fixed as I naively think. $\endgroup$
    – matheorem
    Jul 5 at 15:54
  • 7
    $\begingroup$ Have you tried ToString @ StringForm[...]? $\endgroup$ Jul 5 at 18:09
  • $\begingroup$ @SjoerdSmit Thank you so much! StringForm is much faster, I did not notice this before. $\endgroup$
    – matheorem
    Jul 6 at 1:49
  • 3
    $\begingroup$ StringForm is an old function that fills the need of printf. It's very useful for displaying messages, but just be aware that it doesn't actually produce strings; just something that looks like a string in the FrontEnd. So if you need an actual string, ToString @ StringForm[...] usually works. $\endgroup$ Jul 6 at 7:27

2 Answers 2

5
$\begingroup$

Here's an attempt:

MyStringTemplate[form_String][repls__] :=
  With[
    {positions = StringPosition[form, "``"],
     stringRepls = ToString /@ {repls}},
    StringReplacePart[form, PadRight[stringRepls, Length@positions, ""], positions]]

But I'm not really sure this qualifies as an answer. Or rather, I'm not sure the question was well formed. As the original post points out, StringTemplate is just a special constructor for a TemplateObject. The whole domain of templating functionality in Mathematica goes way beyond strings. So I think that it's a bit misguided to criticize StringTemplate as if it were a simple analogue of printf.

$\endgroup$
4
  • $\begingroup$ "I'm not sure the question was well formed" - it wasn't. $\endgroup$
    – Jason B.
    Jul 5 at 17:51
  • $\begingroup$ Great use of StringReplacePart, Thank you so much! $\endgroup$
    – matheorem
    Jul 6 at 3:47
  • $\begingroup$ Well, I feel a bit silly after seeing the comment about StringForm. $\endgroup$
    – lericr
    Jul 6 at 4:48
  • $\begingroup$ This does not work with associations. MyStringTemplate["a"]@<|"a" -> 1|> $\endgroup$
    – Kuba
    Jul 6 at 5:16
3
$\begingroup$

The most closest to StringTemplate that I write at the moment

ClearAll[myST];
myST[str_String][val__]:=Module[{},
If[Quiet@Head@val===Association,
split=StringSplit[str,RegularExpression["`.*?`"]->"$0"];
StringJoin[split/.(TextString/@KeyMap["`"<>#<>"`"&,val])],
ToString[StringForm[str,Sequence@@(TextString/@{val})]]]];

it supports unnumbered, numbered, named slots.

myST["1``3"][2] // RepeatedTiming
StringTemplate["1``3"][2] // RepeatedTiming

myST["1`a`3"][<|"a" -> 2|>] // RepeatedTiming
StringTemplate["1`a`3"][<|"a" -> 2|>] // RepeatedTiming

gives

{7.63838*10^-6, "123"}
{0.000121687, "123"}
{0.0000115606, "123"}
{0.000130099, "123"}
$\endgroup$

Not the answer you're looking for? Browse other questions tagged or ask your own question.