(In most situations in practical life expandability and a sentinel token being placed behind the last token of the argument is not an issue. So in practical life wipet's answer definitely is to be preferred. My answer is for the case that expandability is an issue due to need (which seems unlikely) or due to "academical interest".)
In case you wish to test whether the result of "hitting" \startswithtokenmymacro's argument's first token with \expandafter has a first token which is exactly the control word token \mymacro, the following might do the trick as long as none of the tokens of that result is \outer:
\makeatletter
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
\expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument has a first token which
%% is exactly the token \mymacro
%%.............................................................................
%% \UDCheckWhetherLeadingMymacro{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked> does have a
%% leading \mymacro>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked> does not have a
%% a leading \mymacro>}%
\newcommand\UDCheckWhetherLeadingMymacro[1]{%
\romannumeral\UD@CheckWhetherNull{#1}%
{\expandafter\UD@stopromannumeral\UD@secondoftwo}%
{%
% Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
% and thus do not disturb when the test is carried out within \halign/\valign:
\expandafter\UD@firstoftwo\expandafter{%
\expandafter\expandafter\expandafter\UD@stopromannumeral
\romannumeral\expandafter\UD@secondoftwo
\string{\UD@CheckWhetherLeadingMymacroB.#1\mymacro}{}%
}{}%
}%
}%
\@ifdefinable\UD@CheckWhetherLeadingMymacroB{%
\long\def\UD@CheckWhetherLeadingMymacroB#1\mymacro{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\expandafter\expandafter\expandafter\UD@stopromannumeral
\expandafter\expandafter\expandafter}%
\expandafter\UD@secondoftwo\expandafter{\string}%
}%
}%
%%=============================================================================
\makeatother
\documentclass{minimal}
%% As the test is on the presence of tokens, not on meanings of possibly
%% defined macros, the following two definitions are not needed:
%\def\mymacro#1{Does something with #1}
%\let\mymacrocopy=\mymacro
\def\foo{\mymacro{1}}
\def\bar{\mymacro{2}}
\def\baz{\mymacro{3}}
\def\qux{\mymacro{4}\extra}
\def\mymacrosecond{\something\mymacro{3}}
\def\nomymacro{\something\different}
\def\hiddenmymacro{\foo}
\def\mymacroinbraces{{\mymacro{1}}something}
\def\mymacrocopyfoo{\mymacrocopy{1}}
\def\mymacrocopybar{\mymacrocopy{2}}
\def\mymacrocopybaz{\mymacrocopy{3}}
\def\mymacrocopyqux{\mymacrocopy{4}\extra}
\def\mymacrosecond{\something\mymacrocopy{3}}
\def\mymacrocopyinbraces{{\mymacrocopy{1}}something}
\def\bat{{\mymacro}{1}}%
\def\bau{{xx\fi}}
\def\bav{}
\def\baw{ }
\newcommand{\startswithtokenmymacro}[1]{%
\expandafter\UDCheckWhetherLeadingMymacro\expandafter{#1}{true}{false}%
}
\begin{document}
\startswithtokenmymacro{\foo} - expected: true
\startswithtokenmymacro{\bar} - expected: true
\startswithtokenmymacro{\baz} - expected: true
\startswithtokenmymacro{\qux} - expected: true
\startswithtokenmymacro{\romannumeral0 \mymacro} - expected: true
\startswithtokenmymacro{\csname mymacro\endcsname} - expected: true
\startswithtokenmymacro{\mymacrosecond} - expected: false
\startswithtokenmymacro{\nomymacro} - expected: false
\startswithtokenmymacro{\hiddenmymacro} - expected: false
\startswithtokenmymacro{\mymacroinbraces} - expected: false
\startswithtokenmymacro{\mymacrocopyfoo} - expected: false
\startswithtokenmymacro{\mymacrocopybar} - expected: false
\startswithtokenmymacro{\mymacrocopybaz} - expected: false
\startswithtokenmymacro{\mymacrocopyqux} - expected: false
\startswithtokenmymacro{\mymacrosecond} - expected: false
\startswithtokenmymacro{\mymacrocopyinbraces} - expected: false
\startswithtokenmymacro{{\foo}} - expected: false
\startswithtokenmymacro{\bat} - expected: false
\startswithtokenmymacro{\bau} - expected: false
\startswithtokenmymacro{\bav} - expected: false
\startswithtokenmymacro{\baw} - expected: false
\end{document}
In case you wish to test whether the result of "hitting" \startswithmeaningofmymacro's argument's first token with \expandafter has a first token which has the same meaning as the token \mymacro, the following might do the trick as long as none of the tokens of that result is \outer:
Check whether that result is empty. If this is the case, then it is not the case that that result has a first token which has the same meaning as the token \mymacro.
If that result is not empty, check whether that result's first token is an explicit character token of category 1 (begin group), e.g., a curly left brace {1. If this is the case, then it is not the case that that result has a first token which has the same meaning as the token \mymacro. If this is not the case, then check whether that result's first token is an explicit space token. If this is the case, then it is not the case that that result has a first token which has the same meaning as the token \mymacro. If this is not the case, then the first token of that result can safely be extracted by means of undelimited macro arguments and brace hacks and afterwards its meaning can be compared via \ifx to the meaning of the token \mymacro:
\makeatletter
%%=============================================================================
%% PARAPHERNALIA:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@PassFirstToSecond, \UD@Exchange,
%% \UD@stopromannumeral, \UD@CheckWhetherNull,
%% \UD@CheckWhetherBrace, \UD@CheckWhetherLeadingExplicitSpace,
%% \UD@CheckWhetherMeaningsEqual, \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
\expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is an explicit character token
%% of category 1 (begin group):
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has a leading
%% explicit catcode-1-character-token>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked does not have a
%% leading explicit catcode-1-character-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
\romannumeral\expandafter\UD@secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@firstoftwo}{%
\expandafter\UD@stopromannumeral\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument's first token is an explicit
%% space-token:
%%.............................................................................
%% \UD@CheckWhetherLeadingExplicitSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked> does have a
%% leading explicit space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked> does not have a
%% a leading explicit space-token>}%
\newcommand\UD@CheckWhetherLeadingExplicitSpace[1]{%
\romannumeral\UD@CheckWhetherNull{#1}%
{\expandafter\UD@stopromannumeral\UD@secondoftwo}%
{%
% Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
% and thus do not disturb when the test is carried out within \halign/\valign:
\expandafter\UD@firstoftwo\expandafter{%
\expandafter\expandafter\expandafter\UD@stopromannumeral
\romannumeral\expandafter\UD@secondoftwo
\string{\UD@CheckWhetherLeadingExplicitSpaceB.#1 }{}%
}{}%
}%
}%
\@ifdefinable\UD@CheckWhetherLeadingExplicitSpaceB{%
\long\def\UD@CheckWhetherLeadingExplicitSpaceB#1 {%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\expandafter\expandafter\expandafter\UD@stopromannumeral
\expandafter\expandafter\expandafter}%
\expandafter\UD@secondoftwo\expandafter{\string}%
}%
}%
%%-----------------------------------------------------------------------------
%% Check whether meanings of two tokens are equal.
%%.............................................................................
\newcommand\UD@CheckWhetherMeaningsEqual[2]{%
\romannumeral
\expandafter\UD@firstoftwo\expandafter{%
\romannumeral\ifx#1#2%
\expandafter\UD@stopromannumeral\expandafter\expandafter
\expandafter\UD@stopromannumeral\expandafter\UD@firstoftwo\else
\expandafter\UD@stopromannumeral\expandafter\expandafter
\expandafter\UD@stopromannumeral\expandafter\UD@secondoftwo\fi
}{}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%% \UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% \UD@ExtractFirstArg{{AB}CDE} yields {AB}
%%
%% Due to \romannumeral-expansion the result is delivered after two
%% expansion-steps/after "hitting" \ExtractFirstArg with \expandafter
%% twice.
%%
%% \UD@ExtractFirstArg's argument must not be blank.
%%
%% Use frozen-\relax as delimiter for speeding things up.
%% I chose frozen-\relax because David Carlisle pointed out in
%% <https://tex.stackexchange.com/a/578877>
%% that frozen-\relax cannot be (re)defined in terms of \outer and cannot be
%% affected by \uppercase/\lowercase.
%%
%% \ExtractFirstArg's argument may contain frozen-\relax:
%% The only effect is that internally more iterations are needed for
%% obtaining the result.
%%
%%.............................................................................
\@ifdefinable\UD@RemoveTillFrozenrelax{%
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}%
{\long\def\UD@RemoveTillFrozenrelax#1#2}{{#1}}%
}%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral\expandafter
\UD@PassFirstToSecond\expandafter{\romannumeral
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\expandafter\expandafter\ifnum0=0\fi}{\UD@stopromannumeral#1{}}%
}{%
\UD@stopromannumeral\romannumeral\UD@ExtractFirstArgLoop
}%
}{%
\newcommand\UD@ExtractFirstArg[1]%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@stopromannumeral#1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillFrozenrelax#1}}%
}%
%%=============================================================================
%% Check whether brace-balanced argument has a first token whose meaning
%% equals the meaning of the token \mymacro
%%.............................................................................
%% \UDCheckWhetherLeadingMymacroMeaning
%% {<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument which is to be checked> does
%% have a first token whose meaning equals the meaning of the token
%% \mymacro>}%
%% {<Tokens to be delivered in case <argument which is to be checked> does
%% not have a first token whose meaning equals the meaning of the token
%% \mymacro>}%
\newcommand\UDCheckWhetherLeadingMymacroMeaning[1]{%
\romannumeral\UD@CheckWhetherNull{#1}{\UD@secondoftwo}%
{%
\UD@CheckWhetherBrace{#1}{\UD@secondoftwo}%
{%
\UD@CheckWhetherLeadingExplicitSpace{#1}{\UD@secondoftwo}%
{%
\expandafter\expandafter\expandafter
\UD@CheckWhetherMeaningsEqual
\UD@ExtractFirstArg{#1}{\mymacro}{\UD@firstoftwo}{\UD@secondoftwo}%
}%
}%
}%
{\expandafter\UD@stopromannumeral\UD@firstoftwo}%
{\expandafter\UD@stopromannumeral\UD@secondoftwo}%
}%
%%=============================================================================
\makeatother
\documentclass{minimal}
%% As the test is on meanings of tokens, he following two definitions
%% affect the resukts of the test:
\def\mymacro#1{Does something with #1}
\let\mymacrocopy=\mymacro
\def\foo{\mymacro{1}}
\def\bar{\mymacro{2}}
\def\baz{\mymacro{3}}
\def\qux{\mymacro{4}\extra}
\def\mymacrosecond{\something\mymacro{3}}
\def\nomymacro{\something\different}
\def\hiddenmymacro{\foo}
\def\mymacroinbraces{{\mymacro{1}}something}
\def\mymacrocopyfoo{\mymacrocopy{1}}
\def\mymacrocopybar{\mymacrocopy{2}}
\def\mymacrocopybaz{\mymacrocopy{3}}
\def\mymacrocopyqux{\mymacrocopy{4}\extra}
\def\mymacrosecond{\something\mymacrocopy{3}}
\def\mymacrocopyinbraces{{\mymacrocopy{1}}something}
\def\bat{{\mymacro}{1}}
\def\bau{{xx\fi}}
\def\bav{}
\def\baw{ }
\newcommand{\startswithmeaningofmymacro}[1]{%
\expandafter\UDCheckWhetherLeadingMymacroMeaning\expandafter{#1}{true}{false}%
}
\begin{document}
\ttfamily
\startswithmeaningofmymacro{\foo} - expected: true
\startswithmeaningofmymacro{\bar} - expected: true
\startswithmeaningofmymacro{\baz} - expected: true
\startswithmeaningofmymacro{\qux} - expected: true
\startswithmeaningofmymacro{\mymacrocopyfoo} - expected: true
\startswithmeaningofmymacro{\mymacrocopybar} - expected: true
\startswithmeaningofmymacro{\mymacrocopybaz} - expected: true
\startswithmeaningofmymacro{\mymacrocopyqux} - expected: true
\startswithmeaningofmymacro{\romannumeral0 \mymacro} - expected: true
\startswithmeaningofmymacro{\romannumeral0 \mymacrocopy} - expected: true
\startswithmeaningofmymacro{\csname mymacro\endcsname} - expected: true
\startswithmeaningofmymacro{\csname mymacrocopy\endcsname} - expected: true
\startswithmeaningofmymacro{\mymacrosecond} - expected: false
\startswithmeaningofmymacro{\nomymacro} - expected: false
\startswithmeaningofmymacro{\hiddenmymacro} - expected: false
\startswithmeaningofmymacro{\mymacroinbraces} - expected: false
\startswithmeaningofmymacro{\mymacrocopyinbraces} - expected: false
\startswithmeaningofmymacro{{\foo}} - expected: false
\startswithmeaningofmymacro{\bat} - expected: false
\startswithmeaningofmymacro{\bau} - expected: false
\startswithmeaningofmymacro{\bav} - expected: false
\startswithmeaningofmymacro{\baw} - expected: false
\end{document}
When looking at the code you sometimes see things wrapped in an extra \UD@firstoftwo{...}{} or the like. I did this to ensure that tokens provided as macro arguments by the user after every expansion step except the last one which delivers the result are nested between a pair of matching curly braces. This in turn is for the case of the macro-mechanism being applied while LaTeX is scanning for elements of an alignment which in turn happens in the course of carrying out tabular environments and the like. In this situation, in case things were not nested between braces, the expansion-cascade initiated by the macro mechanism might be intercepted by TeX's scanning for elements of the alignment.
By the way: If you are picky, then you can say that \ifx is not fully reliable when you wish to find out whether two tokens, e.g., macro tokens, trigger the same actions/expansion cascade.
E.g., with the example below the macros \macroA, \macroB, \macroC, \macroD do exactly the same. Nonetheless with \ifx-comparison they are considered different in case in their definitions' ⟨parameter texts⟩ different character tokens of category 6(parameter) are used for denoting the parameters:
\newlinechar=`\^^J %
% Let's use Z in the same way as #, i.e., for denoting parameter:
\catcode`\Z=6 %
% Let's define some macros which do exactly the same:
\def\macroA#1{In parentheses the result of processing the argument: (#1)}%
\def\macroB#1{In parentheses the result of processing the argument: (Z1)}%
\def\macroCZ1{In parentheses the result of processing the argument: (Z1)}%
\def\macroDZ1{In parentheses the result of processing the argument: (#1)}%
\def\space{ }%
% Reset Z to be a letter:
\catcode`\Z=11 %
\def\printmessagecompare#1#2{%
\message{%
^^J%
\string\ifx\string#1\string#2\space equal\string\else\space different\string\fi\space yields:
\ifx#1#2 equal\else different\fi
}%
}%
\message{^^JThis is the result of \string\macroA{Argument}: \macroA{Argument}}%
\message{^^JThis is the result of \string\macroB{Argument}: \macroB{Argument}}%
\message{^^JThis is the result of \string\macroC{Argument}: \macroC{Argument}}%
\message{^^JThis is the result of \string\macroC{Argument}: \macroC{Argument}}%
\printmessagecompare\macroA\macroA
\printmessagecompare\macroA\macroB
\printmessagecompare\macroA\macroC
\printmessagecompare\macroA\macroD
\printmessagecompare\macroB\macroA
\printmessagecompare\macroB\macroB
\printmessagecompare\macroB\macroC
\printmessagecompare\macroB\macroD
\printmessagecompare\macroC\macroA
\printmessagecompare\macroC\macroB
\printmessagecompare\macroC\macroC
\printmessagecompare\macroC\macroD
\printmessagecompare\macroD\macroA
\printmessagecompare\macroD\macroB
\printmessagecompare\macroD\macroC
\printmessagecompare\macroD\macroD
\csname stop\endcsname
\bye
You get the following messages on the terminal/console/shell:
This is the result of \macroA{Argument}: In parentheses the result of processin
g the argument: (Argument)
This is the result of \macroB{Argument}: In parentheses the result of processin
g the argument: (Argument)
This is the result of \macroC{Argument}: In parentheses the result of processin
g the argument: (Argument)
This is the result of \macroC{Argument}: In parentheses the result of processin
g the argument: (Argument)
\ifx\macroA\macroA equal\else different\fi yields: equal
\ifx\macroA\macroB equal\else different\fi yields: equal
\ifx\macroA\macroC equal\else different\fi yields: different
\ifx\macroA\macroD equal\else different\fi yields: different
\ifx\macroB\macroA equal\else different\fi yields: equal
\ifx\macroB\macroB equal\else different\fi yields: equal
\ifx\macroB\macroC equal\else different\fi yields: different
\ifx\macroB\macroD equal\else different\fi yields: different
\ifx\macroC\macroA equal\else different\fi yields: different
\ifx\macroC\macroB equal\else different\fi yields: different
\ifx\macroC\macroC equal\else different\fi yields: equal
\ifx\macroC\macroD equal\else different\fi yields: equal
\ifx\macroD\macroA equal\else different\fi yields: different
\ifx\macroD\macroB equal\else different\fi yields: different
\ifx\macroD\macroC equal\else different\fi yields: equal
\ifx\macroD\macroD equal\else different\fi yields: equal
E.g., macros not processing any arguments and doing exactly the same but not defined with the same constellation of prefixes like \long or \outer or \protected are considered different with \ifx-comparison:
\newlinechar=`\^^J %
% Let's define some macros which do exactly the same:
\def\macroA{Expansion of the macro.}%
\long\def\macroB{Expansion of the macro.}%
\def\space{ }%
\def\printmessagecompare#1#2{%
\message{%
^^J%
\string\ifx\string#1\string#2\space equal\string\else\space different\string\fi\space yields:
\ifx#1#2 equal\else different\fi
}%
}%
\message{^^JThis is the result of \string\macroA: \macroA}%
\message{^^JThis is the result of \string\macroB: \macroB}%
\printmessagecompare\macroA\macroA
\printmessagecompare\macroB\macroB
\printmessagecompare\macroA\macroB
\printmessagecompare\macroB\macroA
\csname stop\endcsname
\bye
You get the following messages on the terminal/console/shell:
This is the result of \macroA: Expansion of the macro.
This is the result of \macroB: Expansion of the macro.
\ifx\macroA\macroA equal\else different\fi yields: equal
\ifx\macroB\macroB equal\else different\fi yields: equal
\ifx\macroA\macroB equal\else different\fi yields: different
\ifx\macroB\macroA equal\else different\fi yields: different
\def\bat{{\mymacro}{1}}. How do you want\startswithmymacro{\bat}to behave? – Ulrich Diez Feb 21 '24 at 02:16\startswithmymacroto behave in case\mymacrois undefined? (Even when the token\mymacrois undefined it may still occur as the first token of\startswithmymacro's argument's first token's expansion.) – Ulrich Diez Feb 21 '24 at 02:46