このページは部分的に翻訳されています。一部原文の内容が含まれています。
state
文書中の状態の管理。
文書中で何回か計算し、最後の計算結果を次の計算で使用するために記憶しておきたいとします。 以下と同等のコードを試すと10、13、26、21と出力されることを期待するでしょう。 しかしTypstではそうはなりません。 このコードを試してみると、TypstはVariables from outside the function are read-only and cannot be modified.というエラーメッセージを出力することが分かります。
// This doesn't work!
#let star = 0
#let compute(expr) = {
star = eval(
expr.replace("⭐", str(star))
)
[New value is #star.]
}
#compute("10") \
#compute("⭐ + 3") \
#compute("⭐ * 2") \
#compute("⭐ - 5")
状態と文書のマークアップ
なぜこうなるのでしょうか? 一般的に副作用を伴うこの手の計算は文書のマークアップにおいて問題を引き起こすためで、Typstではこれをエラーとして扱います。 この結果を理解するには、計算処理が文書内で生成物がレイアウトされる順序と同じ順序で行われる必要があります。 今回の単純な例ではこの条件が満たされますが、一般的には必ずしもそうとは限りません。
見出しの番号付けという、類似した状態ですが、少し異なる例を見てみましょう。 各見出しで見出しカウンターの値を増やしたいとします。 簡単そうですよね? ただ1を足すだけです。 残念ながらそう単純ではないのです。 以下の例を考えます。
#set heading(numbering: "1.")
#let template(body) = [
= Outline
...
#body
]
#show: template
= Introduction
...

ここで、Typstはまずshowルール以降の文書本体を処理し、Introduction見出しを検知します。
続いてtemplate関数に生成コンテンツを渡します。
その後、初めてOutlineを検知します。
単にカウンター値を増やすとIntroductionは1、Outlineは2となります。
Typstにおける状態管理
それでは代わりにどうするのでしょうか?
Typstの状態管理システムを使用します。
識別用のキーとなる文字列とオプションの初期値とともにstate関数を呼び出すことで状態値が得られます。
この状態値はいくつかの関数を公開しており、最も重要な2つの関数がgetとupdateです。
-
get関数は状態の現在値を取得します。 値は文書中で変化するため、これはコンテキストが利用可能な場合にのみ使用できるコンテキスト関数です。 -
update関数は状態に修正を加えます。 任意の値が使用できます。 関数ではない値が渡された場合、状態にその値が設定されます。 関数が与えられた場合、その関数は前の状態を受け取り、新しい状態を返さなければなりません。
最初の例は以下のようになります。
#let star = state("star", 0)
#let compute(expr) = {
star.update(old =>
eval(expr.replace("⭐", str(old)))
)
[New value is #context star.get().]
}
#compute("10") \
#compute("⭐ + 3") \
#compute("⭐ * 2") \
#compute("⭐ - 5")

Typstが管理する状態は常に評価順ではなくレイアウト順で更新されます。
updateメソッドはコンテンツを返し、その影響は文書に返されたコンテンツが挿入された場所で生じます。
こうして、計算結果を変数へ保存できるようになり、正しい結果を表示しています。
...
#let more = [
#compute("⭐ * 2") \
#compute("⭐ - 5")
]
#compute("10") \
#compute("⭐ + 3") \
#more

この例はもちろん少々極端ですが、これが実際に本当に必要となることがよくあります! 良い例は見出しカウンターです。 これはTypstのカウンターシステムが状態システムにとてもよく似ているためです。
タイムトラベル
Typstの状態管理システムを使用するとタイムトラベルもできます!
文書内の任意の位置でその状態がどの値になっているのかを、どこからでも突き止められます。
特に、atメソッドを用いると特定の任意の位置での状態値が取得でき、finalメソッドを用いると文書の終わりでの状態値を取得できます。
...
Value at `<here>` is
#context star.at(<here>)
#compute("10") \
#compute("⭐ + 3") \
*Here.* <here> \
#compute("⭐ * 2") \
#compute("⭐ - 5")

注意事項
全ての状態値を解決するために、Typstはコードを複数回評価します。 しかしながら、実際に状態操作が完全に解決されるかは保証されません。
例えば、状態の最終的な値に依存して更新する状態を作成した場合、決して収束しなくなるでしょう。
以下の例はこの実演です。
状態を1で初期化し、続いて自身の最終値に1を足した値に更新します。
したがって値は2になるべきですが、最終値が2となったので3に更新します。以下同様です。
この例では有限値が表示されていますが、これは単にTypstが数回試行した後に諦めるためです。
// This is bad!
#let x = state("key", 1)
#context x.update(x.final() + 1)
#context x.get()

一般に、コンテキスト内部で更新する状態を作成しないようにしてください。 可能であれば、更新内容をコンテキストに依存しない値として、あるいは前の値から新しい値を計算する関数として定義してください。 どうしても避けられない場合がありますが、その場合は結果が適切に収束することを保証することはあなたの責任です。
コンストラクタ引数引数は関数への入力値です。関数名の後に括弧で囲んで指定します。
キーで識別される新しい状態の作成。
state(,any)->key
key状態を識別するキー。
この文字列キーで、状態への更新が識別されます。
同じkeyで複数の状態を作ると、どれを更新しても同じ状態として扱われます。
initany位置引数位置引数位置引数は順序通りに指定することで、引数名を省略して設定できます。
init状態の初期値。
同じkeyでもinitが異なる場合、各状態は自分の初期値を使いますが、
更新は共有されます。つまり、ある場所での状態値は、その状態の初期値と
それ以前の更新から計算されます。
例を表示
#let banana = state("key", "🍌")
#let broccoli = state("key", "🥦")
#banana.update(it => it + "😋")
#context [
- #state("key", "🍎").get()
- #banana.get()
- #broccoli.get()
]

デフォルト値:none
定義定義これらの関数や型には、関連する定義を持たせることができます。定義にアクセスするには、対象の関数や型の名前を指定した後に、ピリオド区切りで定義名を記述します。
getコンテキスト関数コンテキスト関数コンテキスト関数は、コンテキストが既知の場合にのみ使用できます。
get現在のロケーションでの状態値を取得。
これはstate.at(here())と等価です。
self.get()->anyatコンテキスト関数コンテキスト関数コンテキスト関数は、コンテキストが既知の場合にのみ使用できます。
atself.at()->anyfinalコンテキスト関数コンテキスト関数コンテキスト関数は、コンテキストが既知の場合にのみ使用できます。
final文書の終わりでの状態値の取得。
self.final()->anyupdate
update状態値を更新。
Returns an invisible piece of content that must be inserted into the document to take effect. This invisible content tells Typst that the specified update should take place wherever the content is inserted into the document.
State is a part of your document and runs like a thread embedded in the document content. The value of a state is the result of all state updates that happened in the document up until that point.
That's why state.update returns an invisible sliver of content that
you need to return and include in the document — a state update that is
not "placed" in the document does not happen, and "when" it happens is
determined by where you place it. That's also why you need context to
read state: You need to use the current document position to know where
on the state's "thread" you are.
Storing a state update in a variable (e.g.
let my-update = state("key").update(c => c * 2)) will have no effect
by itself. Only once you insert the variable #my-update somewhere
into the document content, the update will take effect — at the position
where it was inserted. You can also use #my-update multiple times at
different positions. Then, the update will take effect multiple times as
well.
In contrast to get, at, and
final, this function does not require context. This
is because, to create the state update, we do not need to know where in
the document we are. We only need this information to resolve the
state's value.
self.update(anyfunction)->update
update更新に使う値または関数。
- 関数ではない値が与えられた場合、状態にその値を設定します。
- 関数が与えられた場合、その関数は前の状態を受け取り、新しい状態を返さなければなりません。
以前の値に基づいて更新する場合は、コンテキストから 以前の値を取得するよりも、関数形式を使うことを推奨します。 これによりコンパイラが最終状態を効率よく解決でき、 必要なレイアウト反復回数を抑えられます。
次の例では、fill.update(f => not f)は期待通りに
箇条書きの項目の奇数行を塗ります。
これをcontext fill.update(not fill.get())に置き換えると、
各更新が追加の反復を必要とし、5回以内に収束しません。
例を表示
#let fill = state("fill", false)
#show list.item: it => {
fill.update(f => not f)
context {
set text(fill: fuchsia) if fill.get()
it
}
}
#lorem(5).split().map(list.item).join()
