Why are there so many underscores in LaTeX3 commands?

The function you mention has a name consisting of three parts: cs, set and eq.

  • cs is the “module name”: the function deals with functions (for definitions and things alike); cs stands for “control sequence”, probably it's not the best name, but it's too late for changing it.

  • set is the main action type; in this module there are basically three action types: new, set and gset: the first one is for globally defining new functions, checking that the name is not already used; the second and third ones do a definition or redefinition with no check, the difference is that set acts locally (in the scope of the current group), whereas gset does the definition or redefinition globally.

  • eq is how the definition is performed and stands for “equal”.

Thus the name can be read as “locally define a function to be a copy of some already existing one.

Next comes the “signature“, in this case :NN, which means that the function \cs_set_eq:NN should be followed by two unbraced function names.

Not only it makes visual syntax checking straighforward, but it is internally used for defining variants. We can compare it with what is available with other tools, standard LaTeX and etoolbox:

\let\foo\baz
\letcs\foo{baz}
\cslet{foo}\baz
\csletcs{foo}{baz}

All four perform the same task; of course, the three commands provided by etoolbox are used when one or both names need to be constructed from arguments to other macros. With expl3 we have the c argument type to denote a control sequence whose name should be formed by the token in a braced argument (internally using, of course \csname...\endcsname):

\cs_set_eq:NN \carla_foo:n \carla_baz:n
\cs_set_eq:Nc \carla_foo:n {carla_baz:n}
\cs_set_eq:cN {carla_foo:n} \carla_baz:n
\cs_set_eq:cc {carla_foo:n} {carla_baz:n}

and there's no thinking involved when the team defines them: after defining the first one in terms of primitives (\tex_let:D, but this is irrelevant), all is needed is

\cs_generate_variant:Nn \cs_set_eq:NN { Nc, cN, cc }

that you can compare with the corresponding code in etoolbox.sty:

\newrobustcmd{\cslet}[2]{%
  \expandafter\let\csname#1\endcsname#2}

% {<cstoken>}{<csname>}

\newrobustcmd{\letcs}[2]{%
  \ifcsdef{#2}
    {\expandafter\let\expandafter#1\csname#2\endcsname}
    {\undef#1}}

% {<csname>}{<csname>}

\newrobustcmd*{\csletcs}[2]{%
  \ifcsdef{#2}
    {\expandafter\let
     \csname#1\expandafter\endcsname
     \csname#2\endcsname}
    {\csundef{#1}}}

Variants are a very nice feature of expl3 and they rely on functions having a correct signature. Look for \cs_generate_variant:Nn on the site in order to get an idea of its usefulness.


Underscores

\csSetEq wouldn't work: for such basic, internal commands you need names that a normal user can't accidental overwrite. That's why internal LaTeX2e commands have @ and context uses ! and also underscores: \c_anch_sidebars_current \c!bottomoffset.

expl3 couldn't use @ as it could to easily clash with existing commands and packages and from the other possible non-letters (!,?, *,+,...) imho underscores are not a bad choice: When you get used to it you can read many commands like sentences. \cs_set_eq = commands set equal, \tl_if_exist, \regex_extract_once.

The :NN-syntax

One problem when reading (La)TeX code is that it is very difficult to see how many arguments a command has (if any). This makes it difficult to understand the processing of a command and to debug errors due to missing or wrong argument. A command like e.g. \@sect takes 6 arguments but you have to go back to the definition to see this. With the :NN-syntax you have a much better overview.


If you have

\let\oldmacro\somemacro

but your macro names are being passed (or generated) as names so you have {oldmacro} and {newmacro} then you can go

\expandafter\let\csname oldmacro\expandafter\endcsname\csname newmacro\endcsname

but this is not very clear and when embedded in some larger set of macros is easy to get wrong and introduce weird bugs.

L3 version you just want a the variant assignment that takes two names as braced groups instead of two tokens so:

\cs_set_eq:cc{oldmacro}{newmacro}

these variant forms are either provided or can be easily generated for any command that ends in :NN you can specify a cc variant or a :Nx variant (where the second argument is edef-expanded first) etc.

The choice of _ rather than camel casing is less structural it's a design choice, but camel casing works less well as you can not stop camel cased commands being used mid document, but _ is set up like @ in plain tex and latex2e, as only a letter in code sections.