# Macro to apply a custom command across a comma-separated list

I want to make an apply function that can apply any single-argument command to a comma-separated list of arguments, and print the results separated by commas. (ETA: I want to make the command from scratch, i.e., without using another package dependency, as a way of understanding how all the subroutines work.)

I have a partially working solution, but it doesn't work when the command to be applied is complicated. See the following example, where \lowercase can be effectively applied across a list, but \ComplicatedCommand and \MoreComplicatedCommand cannot:

\RequirePackage{luatex85}
\documentclass[letterpaper]{article}

% This command is for applying a single command to a comma-separated list of tokens, and listing the results separated by ", "
\makeatletter
\newcommand{\apply}[3][, ]{
% #1: optional separator to print between applications; default=[, ]
% #2: command to apply;
% #3: list to apply command to
\def\itemsep{\def\itemsep{#1}} % first call to \itemsep prints nothing; later calls print #1
\@for \listelement:=#3\do{\itemsep#2\expandafter{\listelement}}%
}
\makeatother

\begin{document}

\apply{\lowercase}{THESE,WORDS,PRINT,IN,LOWERCASE.}

\newcommand{\ComplicatedCommand}[1]{\lowercase{#1}}
\apply{\ComplicatedCommand}{WHY,DO,THESE,WORDS,NOT,PRINT,IN,LOWERCASE?}

\newcommand{\MoreComplicatedCommand}[1]{\lowercase{\uppercase{{\lowercase{#1}}}}}
\apply{\MoreComplicatedCommand}{ULTIMATELY,I,WANT,APPLY,TO,MAKE,THESE,LOWERCASE,AS,WELL.}

\end{document}


The result:

Can anyone fix my \newcommand{\apply} so that it can work for any \ComplicatedCommand as argument #2? If so, please provide a full working example, all the way from \documentclass to \end{document}, so anyone who reads the answer in the future can copy and paste it to see if it still works for their version of (Lua)TeX.

You have

\itemsep#2\expandafter


which means that if #2 is a macro with an argument as it is in your second two cases (\lowercase isn't a macro) then the argument will be \expandafter. You want to expand \expandafter before expanding #2. If you know #2 is always a single token you could just put extra \expandafter before it, but safer is:

so...

\documentclass[letterpaper]{article}

% This command is for applying a single command to a comma-separated list of tokens, and listing the results separated by ", "
\makeatletter
\newcommand{\apply}[3][, ]{
% #1: optional separator to print between applications; default=[, ]
% #2: command to apply;
% #3: list to apply command to
\def\itemsep{\def\itemsep{#1}} % first call to \itemsep prints nothing; later calls print #1
\def\zz{\itemsep#2}%
\@for \listelement:=#3\do{\expandafter\zz\expandafter{\listelement}}%
}
\makeatother

\begin{document}

\apply{\lowercase}{THESE,WORDS,PRINT,IN,LOWERCASE.}

\newcommand{\ComplicatedCommand}[1]{\lowercase{#1}}
\apply{\ComplicatedCommand}{WHY,DO,THESE,WORDS,NOT,PRINT,IN,LOWERCASE?}

\newcommand{\MoreComplicatedCommand}[1]{\lowercase{\uppercase{{\lowercase{#1}}}}}
\apply{\MoreComplicatedCommand}{ULTIMATELY,I,WANT,APPLY,TO,MAKE,THESE,LOWERCASE,AS,WELL.}

\end{document}

• note I dropped luatex85 as the behaviour isn't luatex related and even with luatex, you should not need luatex85 normally, that was/is just a temporary compatibility layer to give packages time to update when the luatex primitives changed. – David Carlisle Jan 11 '18 at 7:56
• Thanks! Your solution is package-free and passes my tests, and helped me understand what's going on here :) Unfortunately, though, it didn't work in the broader context I was hoping to use it in, so I've created a follow-up question for using the output of this apply command in a \directlua call; here it is if you're interested. – Andrew Critch Jan 11 '18 at 15:52

etoolbox already possesses list processing capabilities, so why not just piggyback on that:

\documentclass{article}

\usepackage{etoolbox}

% This command is for applying a single command to a comma-separated list of tokens, and listing the results separated by ", "
\newcommand{\apply}[3][, ]{%
% #1: optional separator to print between applications; default = [, ]
% #2: command to apply;
% #3: list to apply command to
\def\listitemsep{\def\listitemsep{#1}}% https://tex.stackexchange.com/a/89187/5764
\renewcommand{\do}{\listitemsep #2}% Each item should be processed this way
\docsvlist{#3}% Process entire list of items
}

\begin{document}

\apply{\MakeLowercase}{THESE,WORDS,PRINT,IN,LOWERCASE.}

\newcommand{\ComplicatedCommand}[1]{\MakeLowercase{#1}}%
\apply{\ComplicatedCommand}{WHY,DO,THESE,WORDS,NOT,PRINT,IN,LOWERCASE?}

\newcommand{\MoreComplicatedCommand}[1]{\MakeLowercase{\MakeUppercase{{\MakeLowercase{#1}}}}}%
\apply{\MoreComplicatedCommand}{ULTIMATELY,I,WANT,APPLY,TO,MAKE,THESE,LOWERCASE,AS,WELL.}

\end{document}


Each element is processed with \do, while the entire list is processed sequentially using \docsvlist.

• There is also \forcsvlist which is even closer to \apply and \DeclareListParser*. – moewe Jan 11 '18 at 8:46
• Thanks a ton for this! But, I should have said in the original question (and have now added) that I want to code the example "from scratch", without other package dependencies, so that (a) it will run faster, and (b) I can better understand what's going on. – Andrew Critch Jan 11 '18 at 14:38

Cleaner code with expl3; note how leading and trailing spaces in the input are ignored. The idea is to parse the comma separated list of items and add each one to a sequence, but as an argument to the given command. Then we can output the list with the required separator.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\apply}{O{,~}mm}
{% #1 = output separator, #2 = command to apply, #3 = list
\critch_apply:Nnn { #2 } { #1 } { #3 }
}

\seq_new:N \l_critch_apply_output_seq

\cs_new_protected:Nn \critch_apply:Nnn
{
\seq_clear:N \l_critch_apply_output_seq
\clist_map_inline:nn { #3 }
{
\seq_put_right:Nn \l_critch_apply_output_seq { #1{##1} }
}
\seq_use:Nn \l_critch_apply_output_seq { #2 }
}

\ExplSyntaxOff

\newcommand{\ComplicatedCommand}[1]{\lowercase{#1}}
\newcommand{\MoreComplicatedCommand}[1]{\lowercase{\uppercase{{\lowercase{#1}}}}}

\begin{document}

\apply{\lowercase}{THESE,WORDS,PRINT,IN,LOWERCASE.}

\apply{\ComplicatedCommand}{WHY,DO,THESE,WORDS,NOT,PRINT,IN,LOWERCASE?}

\apply[~--- ]{\MoreComplicatedCommand}{ULTIMATELY , I , WANT,APPLY ,TO,
MAKE,THESE,LOWERCASE,AS,WELL.}

\end{document}


An extended version where the second argument can be also a set of instructions to apply to each item, where the item is represented by #1; if the second argument consists of a single macro, then it is assumed to be what you want to apply to each item. The first example could be typeset in the same way as

\apply{\lowercase{#1}}{THESE,WORDS,PRINT,IN,LOWERCASE.}


Here's the code.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\apply}{O{,~}+mm}
{% #1 = output separator, #2 = command to apply, #3 = list
\critch_apply:nnn { #1 } { #2 } { #3 }
}

\seq_new:N \l_critch_apply_output_seq

\cs_new_protected:Nn \critch_apply:nnn
{
\seq_clear:N \l_critch_apply_output_seq
\bool_lazy_and:nnTF { \tl_if_single_p:n { #2 } } { \token_if_cs_p:N #2 }
{
\cs_gset_eq:NN \__critch_apply_command:n #2
}
{
\cs_gset:Nn \__critch_apply_command:n { #2 }
}
\clist_map_inline:nn { #3 }
{
\seq_put_right:Nn \l_critch_apply_output_seq { \__critch_apply_command:n {##1} }
}
\seq_use:Nn \l_critch_apply_output_seq { #1 }
}

\ExplSyntaxOff

\newcommand{\ComplicatedCommand}[1]{\lowercase{#1}}
\newcommand{\MoreComplicatedCommand}[1]{\lowercase{\uppercase{{\lowercase{#1}}}}}

\newcommand{\surround}[3]{#1\textit{#3}#2}

\begin{document}

\apply{\lowercase}{THESE,WORDS,PRINT,IN,LOWERCASE.}

\apply{\ComplicatedCommand}{WHY,DO,THESE,WORDS,NOT,PRINT,IN,LOWERCASE?}

\apply[~--- ]{\MoreComplicatedCommand}{ULTIMATELY , I , WANT,APPLY ,TO,
MAKE,THESE,LOWERCASE,AS,WELL.}

\apply[ $|$ ]{(#1)}{a, list,of , words}

\begin{tabular}[t]{@{}c@{}}
\apply[\\]{\surround{-}{!}{#1}}{a, list,of , words}
\end{tabular}

\end{document}


Tailor made for listofitems.

\documentclass{article}
\usepackage{listofitems}
\newcommand\apply[2]{%
\foreachitem\i\in\arglist{\expandafter#1\expandafter{\i}}%
}
\begin{document}
\newcommand{\ComplicatedCommand}[1]{\lowercase{#1}}
\apply{\ComplicatedCommand}{WHY,DO,THESE,WORDS,NOT,PRINT,IN,LOWERCASE?}

\newcommand{\MoreComplicatedCommand}[1]{\lowercase{\uppercase{{\lowercase{#1}}}}}
\apply{\MoreComplicatedCommand}{ULTIMATELY,I,WANT,APPLY,TO,MAKE,THESE,LOWERCASE,AS,WELL.}
\end{document}


Obviously, adding a space in the \foreachitem loop, as in

\foreachitem\i\in\arglist{\expandafter#1\expandafter{\i} }%


will add a space between looped output: