待機の貌のまま、撃った
盤は「待機」と灯っていた。なのについさっき、術式は前を横切った警備兵へ放った。
僕は王宮の防衛術式を預かっている。石壁に埋め込まれた巨大な迎撃機構で、結界に触れた敵影を捉えると、自ら構え、魔力を充填し、発動する。先代の宮廷術師が遺したものを、僕が引き継いだ。仕様書は、半分も揃っていなかった。
人払いされた制御室は、静かだった。負傷した警備兵は、もう運ばれた後だ。盤の表面には、状態を示す札のランプがずらりと並んでいる。その一番上——「待機」の札が、今も静かに灯っている。待機。撃たない、という意味の札だ。それが灯ったまま、術式は味方を撃った。
魔物使いは、もう盤の前にいた。灯った札と、僕が握りしめた手控え——術式が受けた合図の履歴——とを、黙って見比べている。すぐには何も言わない。
「先代が、書庫に書き遺していたんです」と僕は言った。それが唯一の縁だった。「“手に負えぬ獣は、魔物使いのカイナを頼れ。あれは力でなく、契約で獣を鎮める"と。……あなたが、そのカイナですか」
カイナと名乗ったその人は、手控えから目を上げずにうなずいた。王宮お抱えの術師たちは、誰もこの盤を直せなかった。だから僕は、宮廷の外へ手を伸ばした。
言い添えておくべきことが、もう一つあった。「この防衛術式は、王宮を囲う古い結界の一部なんです。近頃、その結界全体が……時折、軋むような音を立てる。術式の不調も、そのせいかもしれないと」。だがカイナは、その話には深く立ち入らなかった。今日の獣は、目の前の盤にいる。
札は、どれも当時は正しかった
僕は術式を誇れなかった。むしろ、怯えていた。
「待機中は、撃たないはずなんです。そのための札を、僕が足したんですから。なのに——盤は"待機"と灯ったまま、撃った」
カイナは黙って続きを促した。僕は言葉を選びながら、引き継いでからのことを話した。
「先代の術式は、時々おかしな振る舞いをしました。撃つべきでない時に構え、構えるべき時に黙る。原因が露れるたびに、僕は札を足してきたんです。“こういう時は撃たない"と一枚、また一枚。……どれも、その時の事故はちゃんと止まりました。だから、正しいと思っていた。でも、足すほど、別のところがおかしくなる気がして。僕が、どこかで掛け違えたんでしょうか」
確信はなかった。あるのは、自分の積み上げを信じきれない不安だけだ。
カイナは、ようやく口を開いた。札を貶める響きは、なかった。
「お前の札は、どれも当時は正しく効いた。一枚ずつ見れば、間違いは無い。——だが、札は足すほど掛け合わさる。三枚あれば、灯り方は二の三乗で八通り。お前が守りたいのは、そのうち四通りだけだろう」
「八通り……?」僕には意味が掴めなかった。「札は、ただ立てているだけです。それぞれ、上げるか下げるか、それだけの」
「その"それだけ"が、三つ集まる。上げ下げの組み合わせが、八つになる」
三つの札で、状態を数える
僕は防衛術式を写した板を取り出した。難しいことはしていない。だからこそ、穴があるとは思えなかった。
まず、術式が受け取る合図——イベントを説明した。起動、充填、発動、中断、冷却。この五つだけだ。中断の合図は、構えを解いて待機へ戻すためのもの。攻撃そのものを取り消す、外からの取り決めだと思ってくれていい。
| |
「状態は、三つの札で管理しています」と僕は言った。「待機、構え、発動。起動の合図で構えに入り、充填が満ちたら発動。中断の合図で、待機に戻す。……戻している、つもりです」
| |
僕は、最後の currentShot を指して言った。「外の誰かが"今、撃つのか"を問うときは、ここを見ます。撃つかどうかは、発動の札——firing が立っているかどうか、それだけで決める」
「待機の札は」とカイナが訊いた。「ここでは、見ないのか」
「ええ。待機の札は、盤に"待機中"と表示して、警備兵に安全を知らせるためのものです。あとは、起動の合図を受け付けてよいかの見張りに。撃つ判定そのものには——」
言いかけて、僕は黙った。撃つ判定は firing だけを見る。待機の札は、別の場所で使う。同じ「状態」のはずなのに、二つの札は、別々のところを指している。その食い違いに、その時の僕は、まだ名前をつけられなかった。
札を足すほど、ありえぬ貌が増える
「あの一撃には、ちゃんと筋がある」とカイナは言った。「合図を、ある順で送れば、必ずあの貌が現れる。その順を、ここで、こちらの手で組む」
札が一枚なら、食い違う場所が無い
カイナがまず組んだのは、合図を一つずつ手で送る仕掛けだった。
「この術式に渡す合図を、こちらが順に決める。一つ送って、状態を一段進める。また一つ送る。実際には一瞬も待たない。合図の並びだけを、こちらで作る」
これは大事なところだった。術式の send は、合図を一つ受けて状態を進めるだけだ。時計の針も、外からの割り込みも、ここには無い。だから模擬戦では、合図の列をそのまま順に流せばいい。実時間はゼロ。同じ列を流せば、何度やっても、行き着く貌はただ一つに定まる。結果は、時の運に左右されない。状態は、合図の並びだけで決まる。
| |
「お前の術式は、並行も、まぐれも無い」とカイナは言った。「合図の順番だけで、貌が決まる。なら、順番を、こちらで握ればいい」
そう言って、カイナはまず札の少なかった頃へ巻き戻した。「引き継いだ当初、お前の術式に、撃つ判定を司る札は firing 一枚だった。素直な事故の列を流してみろ。起動、充填二度、発動」
僕は合図を流した。起動で構えに入り、充填が二段で満ち、発動。
| |
(盤の札を直に読む観測窓——isStandby()・isFiring() など——は、模擬戦のために添えてある。盤のランプを覗くのと同じことだ。)
「起動した時点で、待機の札は下りる」とカイナは言った。「だから発動に至っても、待機と発動が同時に灯ることはない。札が、撃つ判定を司る一枚だけなら——矛盾の起きる場所が、そもそも無い」
「やがて、撃つ前の"構え"を区別したくて、charging を足したんです」と僕は言った。「でも、それも単独では、おかしくならなかった」
「ああ。矛盾は、最後の一枚を足したときに開く」カイナは僕を見た。「お前が引き継いで、最後に足した札。盤に"待機中"と示すための standby。あれが、何をしているか——お前自身の手で、辿ってみろ」
僕が足した札が、新しい穴を開けた
カイナが指したのは、abort の処理だった。中断の合図を受けたとき、待機の札を灯す——僕が、最後に書き足した一行だ。
「同じ事故の手順を、今度は中断まで流す」とカイナは言った。「起動、充填二度、発動。そして、発動の直後に——“やはり撃つな"の中断」
僕は合図を流した。自分の手で、一つずつ。
| |
盤を見て、僕は凍りついた。「待機」のランプと、「発動」のランプが、両方灯ったまま、消えない。
中断の合図は、待機の札を灯した。書いたとおりに。だが——発動の札を、下ろしていなかった。僕は、発動中の中断を、想定していなかった。中断とは、構えている最中に来るものだと思い込んでいた。だから abort の処理は、待機を灯し、構えを解き、充填を空にする。発動の札には、指一本触れていない。
「currentShot を、見てみろ」とカイナが言った。
| |
待機の札が灯っているのに、currentShot は標的を返した。味方の名を。
「……僕が、たった今、足したんです」と僕は言った。声が掠れた。「この矛盾を。安全を示すために足した札が、別の札と食い違って、新しい穴を開けた」
カイナは静かだった。「お前の害の正体は、これだ。同じ"状態"を、別々の札が、別々の場所で見ている。盤は standby を見て"待機"と灯す。撃つ判定は firing を見て標的を返す。二つが食い違ったとき——盤は安全を掲げ、術式は引き金を引く。横切った警備兵は、盤の"待機"を信じていた」
カイナは、盤から視線を落としたまま、手元の羊皮紙に素早くインクを走らせた。 「お前が頭の中で繋ぎ合わせた合図の流れは、こうだ。実時間はなく、ただ順序だけが獣の歩みを決めている」 彼が描いたのは、我々が犯した過ちの、あまりにも無慈悲な順序だった。

「盤が安全を示す『待機』の貌をしているのに、術式の奥深くでは『発動』の魔力で滾ったまま放置されている」と、カイナは言った。「その二つが同時に立ち上がるなど、お前も、お前の先代も考えなかった。だから、発動中の割り込みというたった一つの隙間に、この歪みが落ち込んだ」
この害は、平時には決して露れない。中断が、発動の最中に差し込まれた、ちょうどこの一続きの手順でだけ、二枚が灯る。だから先代も、僕も、長いこと見つけられなかった。
足す方向は、終わらない
カイナは盤の札を、指でゆっくりなぞった。
「三枚の札は、灯り方が八通りある。standby だけ、charging だけ、firing だけ——お前が守りたいのは、この"どれか一枚だけ灯る"四通りだ。残る四通り——二枚灯る、三枚灯る、一枚も灯らない——は、起きてはいけない貌だ」
そして、物差しの話をした。「前に、別の山で、狭い通路に一度に通す数を絞る門番を立てたことがある。あれが数えたのは"今、何頭が通路を占めているか”——同時の数、いわば場所だ。配水の井戸では、器に"一続きの刻に何杯通ったか"を数えさせた——流量、いわば時間だ。だが、ここは場所でも時間でもない」
カイナは札の列を指した。「お前は、貌を、札の掛け算で数えている。札が n 枚なら、灯り方は二の n 乗。一枚足すたび、ありえぬ貌は倍に増える。守りたい"どれか一枚だけ"の掟は、札が増えるほど破れやすくなる。お前は、増える穴を、後から塞いでいた」
僕は、ようやく腑に落ちた。自責が、理解に変わっていく。
「だから……終わらなかったのか」僕は呟いた。「塞ぐたびに札が増えて、増えるほど別の組み合わせが生まれて。札を足すほど、ありえぬ貌が倍々に膨れていた。僕は、その膨れていく穴を、ずっと後から追いかけていただけだ」
複数の独立した boolean を別々に持つと、その組み合わせの大半が無効な状態になる。この、状態の数の膨れ上がりが、僕の盤を蝕んでいた正体だった。
札で数えず、貌をひとつに畳む
足すな、畳め
「直し方は、もう一枚札を足すことじゃない」とカイナは言った。「足すな、畳め。札を一枚増やすたびに掛け算が膨らむなら、掛け算そのものをやめればいい」
「畳む……?」
「札で"待機か否か・構えか否か・発動か否か"を別々に数えるのを、やめる。貌そのものを、ただ一つにする。待機の貌、構えの貌、発動の貌、鎮まりの貌——術式は、そのどれか一つだけを、常に持つ。札を何枚上げても、獣の貌はひとつだ。なら、最初から貌をひとつだけ持たせればいい」
カイナは、それを TypeScript でどう刻むかを示した。
「取りうる貌を、ただ並べるんじゃない。それぞれの貌に名札を一つ付ける——どの貌かを示す判別子だ。そして、その貌でだけ持つ持ち物を、貌と一緒に束ねる。待機の貌に"標的"は無い。標的を持つのは、構えと発動の貌だけだ」
カイナが刻もうとしていたのは、Discriminated Union(判別可能なユニオン型)——取りうる貌を、それぞれの判別子(kind)と持ち物ごと束ねて並べた型だった。
ありえぬ貌は、書けない
カイナが刻んだ型は、これだ。
| |
「待機の貌は、{ kind: "standby" } だけだ」とカイナは言った。「標的を持つ枠が、無い。だから——{ kind: "standby", target: "味方" } と書こうとしても、書けない。“待機中なのに標的を持つ"という貌は、この型の語彙に、存在しない」
僕は、それが何を意味するか、ゆっくり理解した。さっきの矛盾——盤は待機、なのに標的を返す——は、standby の札と firing の札が別々に立てたから起きた。だが貌をひとつに畳めば、「待機の貌」が標的を持つことは、書きようがない。
「八通りのうち、ありえぬ四通りを、後から弾くんじゃない」とカイナは続けた。「畳めば、四つの正しい貌しか、最初から書けない。残りは、消える」
カイナは、札三枚の掛け算を**直積(product type)と呼んだ。二の三乗で、八通りを生む数え方だ。畳んだ後の四つの貌は、そのどれか一つ——こちらは直和(sum type)**だという。掛け算で膨れるのを、足し算で必要な分だけに畳む。ありえぬ組み合わせは、型に書けないから、存在しない。
次に、カイナは状態を進める術式を刻んだ。
| |
transition は、今の貌とイベントを受け取り、次の貌を返す。純粋関数——同じ入力には必ず同じ出力を返し、外の何も書き換えない関数だ。許される移ろいだけを、この地図に刻む。
充填の閾値 REQUIRED と冷却の段数 COOLDOWN を、どちらも 2 に置いた。事故の手順が充填を二度送るのは、この閾値に揃えてある——二段で満ちて、発動へ移る。発動の貌での中断も、冷却を二段経て、待機へ戻る。題材を最小の刻みに絞るための値で、「二で満ち、二で鎮まる」を物差しにした(その値が術式として妥当かどうかは、型の守る話ではない。後でカイナが触れる)。
「肝は、ここだ」とカイナは firing の分岐を指した。「発動してしまった術式は、中断が来ても、待機へ直帰させない。冷却の貌を経て、鎮める。だから——“待機の貌のまま発動が残る"という道が、地図のどこにも引かれていない。引かれていない道は、辿りつけない」
currentShot も変わった。もう札を見ない。今の貌だけを見る。firing の貌のときだけ標的を持つ。待機の貌は、標的を返しようがない。同じ「状態」を別々の札が別々に見る、あの食い違いは、貌をひとつに畳んだことで、根元から消えた。
貌を一つに畳む型(Discriminated Union)と、許される移ろいだけを刻む関数(transition)。この二つが組み合わさったものを、有限状態マシン(State Machine)——取りうる状態と、状態の間で許された移り変わりだけを定めた仕組み——という。地図にすると、こうだ。
カイナが指先で示したのは、防衛術式が進むべき、もう一つの盤面――新しい地図だった。それは、かつて僕が足し算で増やし続けた迷路とは異なり、簡潔で、どこか美しさすら感じさせる一本の小径に似ていた。

「standby の貌には target の枠がない。つまり、待機の貌でいる限り、標的を捕らえること自体が許されない」 カイナの指先が、地図の standby を指した。 「『待機の貌のまま撃つ』というあり得ぬ事態は、この型という契約の上で、そもそも存在を許されていないんだ」
同じ手順を、畳んで通す
「同じ事故の手順を、今度はこの地図で畳む」とカイナは言った。
純粋関数は、貌を畳むのに reduce を使える。初めの貌——待機——に、合図を一つずつ食わせて、次の貌、その次の貌、と畳んでいく。
| |
待機から始め、起動で構えへ、充填二度で満ち、発動で firing の貌へ。そして中断——発動の貌での中断は、待機へ直帰せず、鎮まりの貌へ落ちる。行き着いた貌は cooldown。currentShot は、firing の貌でないから、null。
「待機の貌のまま撃つ、という道が、地図に無い」とカイナは言った。「だから、どう合図を並べても、そこへは辿りつけない」
カイナは続けて、別の入り方も試した。待機の貌に、いきなり発動の合図——地図に道の無い、許されない入力——を送ってみる。
| |
待機の貌は、発動の合図を受けても、待機のままだった。地図に引かれていない移ろいは、ただ無視される——例外も投げず、貌も変えず。transition は純粋関数だから、同じ貌に同じ合図を与えれば、必ず同じ次の貌になる。たまたまこの順で通ったのではない。引いた道だけが通れる地図は、合図をどう並べ替えても、引かれていない道へは入り込ませない。「待機の貌のまま発動」は、ある一つの事故列でたまたま避けられたのではなく、どの合図列からも、構造として辿りつけない。
僕は、ほっとしかけた。だがカイナは、もう一段、見せるものがあると言った。
「ここまでは、走らせて確かめた。合図を流し、行き着いた貌を見た。だが、これは"実行時に弾いた"わけじゃない。transition の中に、矛盾を弾く if は、一つも無い。ありえぬ貌へ向かう道を、最初から引かなかっただけだ。——そして、もう一段、深いところで、型が守っている」
カイナは、新しい板にこう書いた。
| |
「@ts-expect-error は、“次の行は型エラーになるはずだ"という指示だ」とカイナは言った。「待機の貌に標的を持たせる——{ kind: "standby", target: "ally" }。これは型として書けないから、コンパイラが弾く。その弾きを、@ts-expect-error が受け止める」
ここで、術式の検めには二つの層があることを、はっきりさせておかねばならない。模擬戦——node --test——は、術式を実際に走らせて、振る舞いを確かめる。だが、走らせるとき、TypeScript の型の検めは行われない。型は剥がされ、ただの JavaScript として動く。だから「待機の貌に標的を持たせると書けない」ことは、node --test では確かめられない。それを守るのは、もう一つの層——コンパイル時の型検査、tsc だ。
「実行時に if で弾くのと、コンパイル時に書けなくするのは、別のことだ」とカイナは言った。「if で弾くなら、その if を通り抜ける道を、誰かがいつか書ける。だが、型に書けないものは、走らせる前に消える。tsc を通した瞬間に、弾かれる。ありえぬ貌は、本番の盤に乗る前に、消えている」
そして、この @ts-expect-error の妙を、カイナは付け加えた。「この指示には、裏の番もある。もし将来、誰かが型をゆるめて——待機の貌にも標的を持てるようにしてしまったら。その行は、もう型エラーにならない。すると @ts-expect-error は"期待した弾きが来ない"と見なされ、今度はそれ自体がコンパイルエラーになる。ありえぬ貌の排除がゆるんだら、この一行が、すぐに気づかせる」
畳んであれば、安全に増やせる
「これから、新しい構えが要るときは?」と僕は訊いた。これが、ずっと怖かったことだ。「また札を——いえ、また貌を足して、矛盾が増えませんか?」
「貌を一つ足すなら、地図に新しい道を引く」とカイナは言った。「引き忘れた道があれば——型が、見張る」
カイナは、仮の話だと断って、こう書いた。
| |
「transition の switch は、最後を exhaustiveGuard(state) で閉じている」とカイナは説明した。「すべての貌を処理し終えた後に残る型は、never——何も残らない、という型のはずだ。exhaustiveGuard は、never だけを受け取る関数。だから、もし貌を一つ足したのに、その貌の道を描き忘れると、残りが never に収まらず、ここで型エラーになる」
switch を never で閉じ、貌の取りこぼしをコンパイル時に弾くこの仕掛けを、**網羅性検査(exhaustiveness checking)**という。never は、すべての型に代入できるが、never に代入できるのは never だけ、という性質を持つ。その性質を使って「全部の貌を処理し終えたら、残るのは never のはず」を、コンパイラに突きつける。
「足すたびに掛け算が膨らんだ札とは、逆だ」とカイナは言った。「畳んであれば、貌を足しても、増えるのは"道を引く手間"だけ。引き忘れれば、型が止める。お前が増やすのを、型が見張る。——畳んで初めて、安全に増やせる」
貌を、確かめる
模擬戦と、型の検め。二つの層を、まとめて走らせた。
まず、実際に走らせる模擬戦。
| |
六本、すべて緑。Before の盤は、札が食い違わない素直な列なら矛盾を起こさず、同じ事故の列に中断を差し込んだ途端、待機と発動を両立させて味方を撃った。After の地図は、そっくり同じ事故の列を流しても、待機の貌のまま発動へは至らず、許されない移ろいは貌を変えなかった。
そして、走らせる前の検め。型が、ありえぬ貌を書けなくしていることを確かめる。
| |
何も言わずに、通った。tsc は、間違いを見つけたときだけ口を開く。黙って通ったのは、ありえぬ貌——待機の貌に標的を持たせる一行——が、ちゃんと型エラーになり、@ts-expect-error がそれを受け止めたからだ。もしその排除がゆるんで——誰かが待機の貌にも標的を持てるようにしてしまったら——@ts-expect-error は受け止める弾きを失って宙に浮き、その途端、この exit は 0 でなくなる。
Before は、走らせて初めて事故が露れた。After は、走らせるより前に、ありえぬ貌が消えている。これが、二つの層の違いだ。
唯一の差分は、状態の数え方だけだった。受け取る合図も、流す事故の列も、外から問う窓口 currentShot も、Before と After で一つも違わない。変えたのは、独立した札の掛け算(直積)を、判別子ひとつの貌(直和)に畳んだこと。それだけが、ありえぬ貌を消した。
盤から、札が消える
防衛術式が、待機の貌のときは、決して撃たなくなった。盤を見ると——もう、並んだ札は無い。灯るのは、ただ一つ。「今の貌」だけだ。
「僕は、増える穴を、後から追いかけていました」と僕は言った。「畳んでしまえば、穴の開く場所そのものが、無い。……これからは、足すんじゃなく、畳んでから、増やせる」
先代の遺産を、増改築で歪めてしまった負い目が、初めて少しだけ、前を向いた。
「次に構えを増やすときは」と僕は続けた。「まず、地図を描きます。どの貌から、どの貌へ、どの合図で移れるのか。引き忘れたら、型が止めてくれる」
カイナはうなずいて、最後に一つだけ、別の鎖の話をした。「貌ごとに"振る舞い"そのものまで分けて持たせたいなら、別の作法もある。状態ごとの処理を、貌ごとの部品に閉じる——そういう設計だ。だが今日の獣の病は、振る舞いの置き場所じゃない。ありえぬ貌が、書けてしまうことだった。だからまず、貌を畳んだ。それで足りた」
カイナは制御室を出る前に、一度だけ、結界の軋む方を見た。
「貌が一つに定まらず、ありえぬ形に崩れていく。——この獣の病は、この術式だけのものか」
僕は、問い返せなかった。ただ、遠くで軋む結界の音と、たった今まで盤を蝕んでいた矛盾とが、どこかで重なる予感だけが、後に残った。
それから、もう一つ、カイナは断っておくと言った。「この地図が消したのは、ありえぬ貌だ。だが、地図に引いた道そのものが、術式として正しいか——たとえば、充填がいくつ満ちたら発動してよいのか——それは、型の守る話じゃない。お前が決めることだ。それに、合図が一つずつ順に届くなら、この地図は寸分たがわず畳む。だが、複数の手元から同時に合図が届くようになったら——それは、また別の獣だ」
その「別の獣」が何なのか、その時の僕は知らなかった。ただ、貌を一つに畳んだこの術式が、もう待機の貌のまま味方を撃つことはない。それだけは、確かだった。
📜 カイナの魔獣契約録(Tamer’s Registry)
- 魔獣名(クラス/パターン名): 千変の変幻獣カメレオン(氷の貌のまま炎を吐く矛盾状態)/ 不正状態の暴走(State Machine+Discriminated Union=貌をひとつに畳む/独立 boolean フラグの直積=ありえぬ状態の組合せ爆発)
- 危険度(難易度/バグの影響度): ★★★★★(データ破壊ではなく安全性の崩壊。盤は「待機」と灯ったまま発動が残り、無害と信じて前を横切った味方を撃つ。矛盾形態のまま魔力が暴走すれば、術式そのものが焼き切れて自壊する)
- 主な生態(アンチパターンの特徴):
- 状態を独立した boolean フラグ(standby/charging/firing)の直積で数えると、n 枚で 2 の n 乗通りの組合せが生まれ、その大半(3枚なら8通り中4通り)がありえぬ無効状態になる。塞ぐために札を足すほど、無効な組合せは倍々に膨れる
- 害の機序は「同じ"状態"を、別々の札が、別々の場所で参照して食い違う」こと。standby は盤の表示ランプと arm 受理ガードに使われ、撃つ判定 currentShot は firing だけを見る。両者が別管理ゆえ standby=true かつ firing=true が成立すると、盤は「待機」と灯るのに標的を返す
- 中断(abort)が発動(firing)の最中に差し込まれた一続きの手順でだけ二枚が灯る。平時・構え中には露れず、再現条件が手順依存ゆえデバッグが難しい
- 契約のポイント(設計の要点):
- Discriminated Union=貌に判別子
kindを一つ付け、その貌でだけ持つ持ち物を束ねた直和。standbyにtargetの枠が無く「待機中なのに標的を持つ」が型として書けない=ありえぬ組合せを型の語彙から排除(直積2^n → 直和の必要な貌だけに畳む) transition(state, event)=今の貌と合図から次の貌を返す純粋・同期関数。許される移ろいだけを刻み、「待機の貌のまま発動」へ至る道を地図に引かない=到達不能。currentShotも札でなく今の貌だけを見る- 二層検証:
node --test(実行時)で事故列が矛盾へ到達しないことを、tsc --noEmit(コンパイル時)でありえぬ貌が書けないことを守る。Node のネイティブ実行は型を剥がすため、型の保証は tsc が担う - never 網羅性検査=
switch (state.kind)をexhaustiveGuard(x: never)で閉じる。新しい貌を足して道を描き忘れると残りが never に収まらずコンパイルエラー=増改築の番人(畳んで初めて安全に増やせる) - 1:1 の単一差分:同一
WardEvent・同一の事故列・同一の外部窓口currentShotを保ち、差分は状態の数え方(独立 boolean の直積 → 判別子ひとつの直和)のみ。無関係なバグ修正を混ぜていない
- Discriminated Union=貌に判別子
- 契約外事項(保証しないこと):
- 合図が一つずつ順に届く前提で畳む。複数の手元から同時に合図が届く並行入力・状態の分散同期は守備範囲外=別の獣(データレース/楽観ロック)の領分
- 型はありえぬ貌を消すが、正しい貌から正しい貌への道そのものが業務的に妥当か(例:充填閾値が適切か)は保証しない。道の妥当性は別
- 過剰符号化への戒め:充填量や冷却残りまで全て型パラメータへ符号化しようとしない。目的は不正状態の排除であって、あらゆる細部の型表現化ではない(
charge: numberは値で持ってよい) - GoF State パターン(状態ごとの振る舞いをオブジェクトにカプセル化)とは焦点が違う。本話は「ありえぬ貌が書けてしまう」ことを型で消す方=FSM+Discriminated Union
- 現在のステータス: 🟢 貌をひとつに畳み、許される移ろいだけを地図に刻んで「待機の貌のまま発動」を型から消した契約成立(実行時の if でなくコンパイル時に存在を消す)/合図の並行到着・状態の分散同期は別の獣として後日。同時の数(門番)・流量(器)に続き、状態空間の妥当性で、暴走を律する物差しがまた一つ揃った
