
私は自分の生活を少し自動化したいと思っていましたが、enumitem
パッケージには、以前のリストに関してカウントを再開できるカスタム リストを作成する方法があることを発見しました。それがなければ、enumi
と を使用してカウンターを明示的に操作する必要があります\setcounter
が、正しくカウントできないことがよくあるので、面倒な作業になる可能性があります。 :-) とにかく、 をenumitem
使用すると、 を使用したときの連続カウントが機能しますresume*
。ただし、そうすることで、cleverref による参照が壊れてしまいます。そのリストから項目を参照すると、追加のresume,,
テキストが印刷されます。これをどうやって取り除くことができますか? プリアンブルで何を間違えているのでしょうか、それともこれは何らかのバグでしょうか?
\documentclass[a4paper,english]{article}
\usepackage{enumitem}
\usepackage{amssymb}
\usepackage{cleveref}
% defining theorem environments for their specialized versions
\makeatletter
\newenvironment{base@thm}[3]%
{$\;$\linebreak\noindent\mbox{$\triangleright\;\textsc{\textbf{\large #1} #2 (#3).}$}}
{\hfill$\;$\linebreak}
\makeatother
% lemma environments take a label and a name
\newcounter{lemma}
\crefname{lemma}{lemma}{lemmata}
\makeatletter
\newenvironment{lemma}[2]%
{\refstepcounter{lemma}\begin{base@thm}{Lemma}{\thelemma}{#2}\label[lemma]{lemma:#1}\def\@currentlabel{#2}\label{t@lemma:#1}}
{\end{base@thm}\noindent}
\makeatother
% referencing a lemma
\newcommand{\lemmaref}[1]{\Cref{lemma:#1}~(\ref{t@lemma:#1})}
\newcommand{\lemref}[1]{\Cref{lemma:#1}}
\newlist{passumptionslist}{enumerate}{1}
\setlist[passumptionslist]{label=(\alph*)}
\crefalias{asm}{passumptionslisti}
\crefalias{goal}{enumi}
\crefname{asm}{assumption}{assumptions}
\crefname{goal}{goal}{goals}
\newenvironment{assumptions}
{\begin{passumptionslist}[label=(\alph*)]
}
{
\end{passumptionslist}
}
\newenvironment{goals}
{\begin{enumerate}[label=(\roman*)]
}
{
\end{enumerate}
}
\newcommand{\IH}[1][]{I{\kern-1.5pt}H#1}
\newenvironment{passumptions}[1]
{%
\begin{passumptionslist}[resume*,label={$\left(#1_{\arabic*}\right)$}]%
}
{%
\end{passumptionslist}%
}
% individual cases
\newcommand{\asm}[2]{\item\label[asm]{asm:#1} {$#2$}}
\newcommand{\goal}[2]{\item\label[goal]{goal:#1} {$#2$}}
\newcommand{\asmref}[1]{\Cref{asm:#1}}
\newcommand{\goalref}[1]{\Cref{goal:#1}}
\newenvironment{proof}[1][\textbf{Proof}]%
{\restartlist{passumptionslist}\par\noindent\ignorespaces\mbox{\textsc{#1}.}$\;$\\\noindent}
{$\;$\hfill$\square$\\\ignorespacesafterend}
\begin{document}
\begin{lemma}{lemma}{Some Lemma Name}
If
\begin{assumptions}
\asm{lemma}{a}
\end{assumptions}
then
\begin{goals}
\goal{lemma}{b}
\end{goals}
\end{lemma}
\begin{proof}
Unfold \goalref{lemma} and introduce the assumptions.
\begin{passumptions}{H}
\asm{lemma0}{v\Downarrow b}
\end{passumptions}
What is left to show is:
\begin{goals}
\goal{lemma0}{c}
\end{goals}
From \asmref{lemma0}, get:
\begin{passumptions}{H}
\asm{lemma1}{a=v}
\end{passumptions}
Rewrite \asmref{lemma} and \asmref{lemma0} using \asmref{lemma1}:
\begin{passumptions}{H}
\asm{lemmap}{v=c}
\asm{lemma0p}{a\Downarrow b}
\end{passumptions}
Use \asmref{lemma0p}:
\begin{passumptions}{H}
\asm{lemma2}{a=b}
\end{passumptions}
Finally, rewrite \goalref{lemma0} via \asmref{lemma1}.
Then, \asmref{lemma2} solves it.
\end{proof}
\end{document}
答え1
この回答は、なぜresume,,
印刷されるのかについて説明します。
基本的に、互いに衝突する 2 つの事柄があります。
[resume*]
先行詞がない場合、リストは壊れます
この動作は、次の単純な MWE で確認できます。
\documentclass{article}
\usepackage{enumitem}
\begin{document}
\begin{enumerate}[resume*]
\item Test
\end{enumerate}
\end{document}
なぜこれが機能しなくなるのでしょうか? を有効にするにはresume*
、enumitem
コードは、リストの前回の発行から次回のリストの呼び出しまでリスト構成を渡す必要があります。これは、\enit@resumekeys@<list>
(ここで<list>
はリストの名前、つまりenumerate
または に置き換えられますpassumptionslist
) を (基本的に) リストにオプションの引数として渡されたものに設定することで行われますenumitem
。これはリストの最後に実行されますなので、\end{enumerate}
上の例で に遭遇すると、いいえデフォルト値が作成されます。そのため、リストが最初に呼び出される前に、マクロ\enit@resumekeys@<list>
は未定義。
ここで、resume*
がリストに渡されると、enumitem
はリストへの最後の呼び出しから構成をロードしようとし、 を調べます\enit@resumekeys@<list>
。特に、これは 構文を使用して行われる\csname ... \endcsname
ため、\enit@resumekeys@<list>
が未定義の場合でもエラー (未定義マクロ エラー) は発生しません。 を使用して\csname ... \endcsname
未定義マクロにアクセスすると、常に が返されます\relax
。
現在の呼び出しのキーが以前に指定されたキーを上書きできるようにするには、enumitem
先頭に付加するこれをオプション引数に追加します。したがって、resume*
MWE で を処理した後、有効なオプション引数は になります\relax,resume
。
しかし、オプションのリストを処理するには、enumitem
カンマで区切られたリストを一度に1項目ずつ処理する必要があります。これは標準的なトリックを使用して行われます。マクロを定義するには、
\def\enitkv@do#1,{%
\ifx\relax#1\empty\else
\enitkv@split#1==\relax %% This processes the key, ignore it for now
\expandafter\enitkv@do\fi}
マクロはカンマで区切られます。そのため、カンマで区切られたリストを入力すると、次の文字列まで次の文字列を処理し,
、その文字列 (およびカンマ) を破棄して、次の引数に対して同じ処理を繰り返します。
このマクロはどうやって停止するかを知るのでしょうか? に遭遇すると停止します\relax
。そして実際には、このマクロは次のように呼び出されます。
\enitkv@do#2,\relax,
はオプションの引数のリストです#2
。これで問題が分かります。resume*
先行詞なしで使用する場合上記のマクロが呼び出されると、
\enitkv@do\relax,resume,\relax,
最初の\relax
ヒットでマクロは終了します。つまり、残っているのはぶら下がった部分です。
resume,\relax,
これらはマクロの引数ではないので、テキストとして処理されます。そして、\relax
何もしないので、印刷すると次のようになります。
resume,,
あなたが見るもの。
グループ
しかし、ちょっと待ってください、あなたは環境もassumptions
で定義しpassumptionslist
、それを前にpassumptions
コードの最初のものを呼び出します。これは、実際には先行項が存在するはずであることを意味しませんか?
これについては、次の 2 つの MWE を比較する必要があります。
MWE 1 (グループなし)
\documentclass{article}
\usepackage{enumitem}
\begin{document}
\begin{enumerate}
\item Test
\end{enumerate}
\begin{enumerate}[resume*]
\item Test
\end{enumerate}
\end{document}
MWE 2(グループ)
\documentclass{article}
\usepackage{enumitem}
\begin{document}
\bgroup
\begin{enumerate}
\item Test
\end{enumerate}
\egroup
\begin{enumerate}[resume*]
\item Test
\end{enumerate}
\end{document}
MWE 1 は適切に動作しますが、MWE 2 は不適切に動作します。では、ここで何が起こっているのでしょうか?
以下は の機能ですenumitem
が、良いアイデアかどうかはわかりません。 現在のオプションをリストに保存する場合、 はenumitem
4 つの異なるケースで異なる動作をします。
- または で
series
はないオプションがある場合、コードは を使用して、現在のカウンター値と現在のオプション引数の両方を保存します。resume
resume*
\global\def
- またはでも
series
あるオプションがある場合、コードは を介してカウンターのみを保存しますが、現在のオプション引数は保存しません。resume
resume*
\global\def
- が存在しない
series
が、 が存在する場合resume*
、コードは を介してカウンタのみを保存します\global\def
が、オプションの引数は保存しません。 series
または でもない単純なリストがある場合resume*
(ただし、奇妙なことに、resume
ここでは問題ありません)、コードは を使用してカウンターとオプションの引数の両方を保存します\def
。
これにより、いくつかの興味深い副作用が発生します。
\begin{enumerate}[resume]...\end{enumerate}
を で囲むと\newenvironment
、4 番目のケースがトリガーされます。ただし、環境がグループ化されているため、 は\def
環境外に伝播しません。これが、で観察したように番号付けが間違っている理由を説明しています。このコメント。- 元のコードを参考にすると、 を介して定義された環境
\begin{passumptionslist}...\end{passumptionslist}
でラップされているため、 を呼び出すと、パッケージは の現在のカウンターとリスト オプションを保存しようとしますが、ケース 4 の場合、これらはローカル定義を介して作成されるため、 に達するとすぐに失われます。assumptions
\newenvironment
\begin{assumptions}...\end{assumptions}
\end{passumptionslist}
\end{assumptions}
上記で特定された最初の問題は、少し変わったエッジケースです。 の通常の使用では、以前に使用されたことのないリストをenumitem
呼び出すべきではありません[resume*]
。とはいえ、このエッジケースは、 のリストの終わりを区別するために別のトークンを使用するだけで修正できます\def\enitkv@do#1,
。
2 番目の問題は、実際には文書化されています (ただし、やや不十分です)。enumitem のドキュメントの現在のバージョンでは、resume
ローカルであることがセクション 3.3 で簡単に説明されています。ただし、この奇妙な相互作用はresume*
明確に文書化されていません (間違いなく文書化される必要があります)。
答え2
さまざまな環境にラップされる基盤の使用は不要だと思います。すべてをspassumptionslist
として定義する方がよいでしょう。\newlist
%%%% UNTIL ANNOUNCED, SAME AS YOUR CODE %%%%
\documentclass[a4paper,english]{article}
\usepackage{enumitem}
\usepackage{amssymb}
\usepackage{cleveref}
% defining theorem environments for their specialized versions
\makeatletter
\newenvironment{base@thm}[3]%
{$\;$\linebreak\noindent\mbox{$\triangleright\;\textsc{\textbf{\large #1} #2 (#3).}$}}
{\hfill$\;$\linebreak}
\makeatother
% lemma environments take a label and a name
\newcounter{lemma}
\crefname{lemma}{lemma}{lemmata}
\makeatletter
\newenvironment{lemma}[2]%
{\refstepcounter{lemma}\begin{base@thm}{Lemma}{\thelemma}{#2}\label[lemma]{lemma:#1}\def\@currentlabel{#2}\label{t@lemma:#1}}
{\end{base@thm}\noindent}
\makeatother
% referencing a lemma
\newcommand{\lemmaref}[1]{\Cref{lemma:#1}~(\ref{t@lemma:#1})}
\newcommand{\lemref}[1]{\Cref{lemma:#1}}
%%%% CHANGES BELOW %%%%
% 1. Removed passumptslist
% 2. Removed your crefaliases
% The crefname are kept
\crefname{asm}{assumption}{assumptions}
\crefname{goal}{goal}{goals}
% 3. Defined assumptions, passumptions, and goals directly as new lists
\newlist{assumptions}{enumerate}{1}
\newlist{passumptions}{enumerate}{1}
\newlist{goals}{enumerate}{1}
% 3. (cont'd) and set their formatting directly
\setlist[assumptions]{label=(\alph*)}
\setlist[goals]{label=(\roman*)}
% 4. Set the format for passumptions; define macro to change the label.
\makeatletter
\newcommand*\setPassLabel[1]{\gdef\@passLabel{#1}}
\setlist[passumptions]{resume,label={$(\@passLabel_{\arabic*})$}}
\makeatother
%%%%% BELOW AGAIN UNCHANGED, EXCEPT MARKED LINES %%%%%%%
\newcommand{\IH}[1][]{I{\kern-1.5pt}H#1}
% individual cases
\newcommand{\asm}[2]{\item\label[asm]{asm:#1} {$#2$}}
\newcommand{\goal}[2]{\item\label[goal]{goal:#1} {$#2$}}
\newcommand{\asmref}[1]{\Cref{asm:#1}}
\newcommand{\goalref}[1]{\Cref{goal:#1}}
%%%% Make the list that is restarted the passumptions list, and not passumptionslist.
\newenvironment{proof}[1][\textbf{Proof}]%
{\restartlist{passumptions}\par\noindent\ignorespaces\mbox{\textsc{#1}.}$\;$\\\noindent}
{$\;$\hfill$\square$\\\ignorespacesafterend}
\begin{document}
\begin{lemma}{lemma}{Some Lemma Name}
If
\begin{assumptions}
\asm{lemma}{a}
\end{assumptions}
then
\begin{goals}
\goal{lemma}{b}
\end{goals}
\end{lemma}
\begin{proof}
\setPassLabel{H} %%% Set the label for the passumptions.
Unfold \goalref{lemma} and introduce the assumptions.
\begin{passumptions} %%% No more mandatory argument (Ditto below)
\asm{lemma0}{v\Downarrow b}
\end{passumptions}
What is left to show is:
\begin{goals}
\goal{lemma0}{c}
\end{goals}
From \asmref{lemma0}, get:
\begin{passumptions}
\asm{lemma1}{a=v}
\end{passumptions}
Rewrite \asmref{lemma} and \asmref{lemma0} using \asmref{lemma1}:
\begin{passumptions}
\asm{lemmap}{v=c}
\asm{lemma0p}{a\Downarrow b}
\end{passumptions}
Use \Cref{asm:lemma0p}; \asmref{lemma0p}:
\begin{passumptions}
\asm{lemma2}{a=b}
\end{passumptions}
Finally, rewrite \goalref{lemma0} via \asmref{lemma1}.
Then, \asmref{lemma2} solves it.
\end{proof}
\begin{proof}
\setPassLabel{K}
\begin{passumptions}
\asm{lemma3}{c}
\end{passumptions}
\end{proof}
\end{document}
説明
すべてのリスト環境を、リスト をラップする
\newlists
として定義するのではなく、として直接定義する方がよいと思います。 その理由は、この方法により、オプションの引数を介してオンザフライでリストを変更したり、 をグローバルに使用したりして、インターフェイスを使用してリストを変更できる一貫性が確保され、リストの使用方法の柔軟性が向上するためです。\newenvironments
passumptionslist
enumitem
\setlist
私の知る限り、 は
\crefalias
コードに影響を与えません。特に で潜在的に問題を引き起こすことがわかっているため、これらを削除しました。 問題ないのは、 と を使用するときはいつでもhyperref
オプション引数を使用しているため、オプション引数から型情報を取得できるからです。\label
\asm
\goal
\Cref
\newlists
と を使用すると、\setlist
書式設定の構成がより透明になると思います。これは、作業が必要な唯一の部分です。元のコードでは、基礎となるリストの を
passumptions
設定するために使用する必須引数を使用して を定義しました。私はこの設定に同意しません。まず、コードから、が「証明ごとに」使用されていることは明らかなので、理想的には、同じ証明内の連続するものは同じラベル タイプを持つ必要があります。(項目に H1、H2、次に C3、D4、H5 と番号が付けられると奇妙に見えると思います。) それを念頭に置いて、ラベルをグローバルに変更する方法があった方がよいでしょう (後続のすべてのリストに対して)。ちなみに、そうすることで、としてコーディングすることも可能になります。この方法で作成されたリストは必須引数を取ることができないためです。label
passumptions
passumptions
passumptions
passumptions
\newlist
この設計哲学に基づいて、 のラベル記号/文字を設定するコマンドを作成し
passumptions
、その記号を使用してこのリストの書式を設定します。また、resume
このリストは各証明の開始時にリセットされるまで再開されることを意図しているため、キーも追加しました。