What is the correct way to replace "csxdef/foreach" with pure expl3 code?

I'd do a little different. The code as you put it iterates through the comma list 2+<num> times, with <num> being the number of items in the list. The first iteration is in \clist_set:Nn, which loops through it trimming spaces. The second iteration is in \clist_count:N, which loops again to count the number of items. And as many iterations as there are items in the list with \clist_item:Nn, which doesn't just skip to the item you want, but scans each item until it finds the one you want. It's rather suboptimal. In fact, using l3benchmark to measure the performance, 10 thousand iterations of your code runs in 1.32 s, while the code below runs in 0.39 s, more than 3x speedup.

I suggest using \clist_map_inline:nn (or something similar), which will iterate only once through the list, and use an auxiliary counter to keep track of the item number.

I'd say that you should use a <tl var> here with \tl_new:c and \tl_set:cx rather than \cs_set:cx (which, by the way, is much slower than \cs_set:cpx). And, since the generated items aren't functions (in expl3 terminology), but variables, this alternative seems better. Then the accessor function can just be changed to be \tl_use:c, which will check if the requested variable exists and issue an error if appropriate. Using \use:c here is not desired because if the requested variable does not exist it will expand to \relax and will not raise an error.

Finally, if you plan to reuse the created token lists, then instead of \tl_new:c (which raises an error if the variable exists), you can use \tl_clear_new:c which will clear the variable, or create it, depending if it existed or not.

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\int_new:N \l_pablo_tmp_int
\NewDocumentCommand{\mycmdtwo}{ m m }
  {
    \int_zero:N \l_pablo_tmp_int
    \clist_map_inline:nn {#2}
      {
        \int_incr:N \l_pablo_tmp_int
        \tl_clear_new:c { l_pablo_#1 \int_use:N \l_pablo_tmp_int _tl } % Redundant here, but good practice in general
        \tl_set:cx { l_pablo_#1 \int_use:N \l_pablo_tmp_int _tl } {##1}
      }
  }
\NewDocumentCommand\mycmd {m} { \tl_use:c { l_pablo_#1_tl } }
\ExplSyntaxOff
\begin{document}
Test mycmdtwo
\mycmdtwo{B}{P,Q,R}
\mycmd{B1} \mycmd{B2} \mycmd{B3}

% repeat
Test mycmdtwo
\mycmdtwo{B}{P,Q,R}
\mycmd{B1} \mycmd{B2} \mycmd{B3}
\end{document}

TeXhackers note: \tl_set:cx does \cs_set_nopar:cpx (i.e., \expandafter\xdef\csname...\endcsname, so the \tl_new:c is redundant. But it is in the best practices to declare the variable before using it.


This is more of a comment that is too long for a comment....

Since it seems that you want to be able to redefine these commands I think that you need to use \cs_set:cpx (or \cs_gset:cpx or \cs_set:cx or \cs_set:cx), but you don't need two functions here and, instead, the following is sufficient:

\documentclass{article}
\usepackage{xparse}
\usepackage{etoolbox}
\usepackage{pgffor}
\ExplSyntaxOn
\NewDocumentCommand{\mycmdtwo}{ m m }
  {
    \clist_set:Nn \l_tmpa_clist {#2}
    \int_step_inline:nn {\clist_count:N \l_tmpa_clist}
    {
      \cs_set:cpx {l_#1##1:} { \clist_item:Nn \l_tmpa_clist {##1} }
    }
  }
\NewDocumentCommand\mycmd{m}{\use:c{l_#1:}}
\ExplSyntaxOff
\begin{document}

Test mycmdtwo
\mycmdtwo{B}{P,Q,R}
\mycmd{B1} \mycmd{B2} \mycmd{B3}

% repeat
Test mycmdtwo
\mycmdtwo{B}{P,Q,R}
\mycmd{B1} \mycmd{B2} \mycmd{B3}

\end{document}

I have also given a "helper" command \mycmd.

You can also use \cs_set:cx or \cs_gset:cx but, as Joseph points out in the comments, \cs_set:cpx and \cs_gset:cpx are much faster.


Token list variables are different from functions. Your code is meant to store token lists, rather than define actions.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\mycmd}{mm}
 {
  \seq_set_from_clist:Nn \l__pablo_mycmd_seq { #2 }
  \seq_indexed_map_inline:Nn \l__pablo_mycmd_seq
   {
    \tl_clear_new:c { l_pablo_#1##1_tl }
    \tl_set:cn { l_pablo_#1##1_tl } { ##2 }
   }
 }
\NewExpandableDocumentCommand{\pablouse}{m}
 {
  \tl_use:c { l_pablo_#1_tl }
 }
\ExplSyntaxOff

\begin{document}

Test mycmd
\mycmd{A}{X,Y,Z,W}

\pablouse{A1} \pablouse{A2} \pablouse{A3} \pablouse{A4}

\end{document}

A benchmark of my solution compared with Phelype Oleynik's yields

8.22e-5 seconds (252 ops)
1.08e-4 seconds (321 ops)

Top is my solution, bottom is Phelype's.