Why does \substring not work inside \equal?

Why does this code output "False":

\documentclass{article}
\usepackage{stringstrings}
\usepackage{xifthen}

\begin{document}
\ifthenelse{\equal{\substring{ab}{1}{1}}{a}}{True}{False}
\end{document}


There are many alternative solutions to get the desired result: Test if the first character of a string is 'a', but I would like tho understand why the above code behaves this way and what would be the closest possible solution to get the desired behavior.

• \equal tests the expansion of the two expressions are equal and the stringstrings functions are not expandable they make many internal assignments – David Carlisle Jul 28 '20 at 19:46

The macro \substring does not work by pure expansion: it produces a (long) series of commands to print the requested substring.

With [q] you can make it save the result in \thestring. Well, it always does, but [q] suppresses output at the calling spot.

\documentclass{article}
\usepackage{stringstrings}
\usepackage{xifthen}

\begin{document}
\substring[q]{ab}{1}{1}
\ifthenelse{\equal{\thestring}{a}}{True}{False}
\end{document}


This will print “True”.

I'd prefer the more powerful expl3 routine:

\documentclass{article}
\usepackage{xparse}
\usepackage{xifthen}

\ExplSyntaxOn
\NewExpandableDocumentCommand{\substring}{mmm}
{
\tl_range:nnn { #1 } { #2 } { #3 }
}
\ExplSyntaxOff

\begin{document}

\ifthenelse{\equal{\substring{ab}{1}{1}}{a}}{True}{False}

\ifthenelse{\equal{\substring{abcde}{-2}{-1}}{de}}{True}{False}

\end{document}


You see that you can also extract substrings from the end. See also https://tex.stackexchange.com/a/467527/4427 for a reimplementation of \ifthenelse with expl3.

If you're free to use LuaLaTeX, consider using Lua's powerful string functions, including string.sub (short for "string subset", I suppose).

For instance, the following program defines a LaTeX macro called \TestFirstChar. \TestFirstChar{ab}{a} typesets "True".

\documentclass{article}
\newcommand\TestFirstChar[2]{%
\directlua{if (string.sub("#1",1,1)=="#2") then tex.sprint("True")
else tex.sprint("False")
end}}

\begin{document}
\TestFirstChar{ab}{a}
\end{document}