表現力検証 4: 射影一族の綻び出し
ADR-27 で導入した窓→値の射影一族(ordinalIn/labelOf/epochOrdinal/snapTo+窓ラベル付与
label:)は、01〜03 の「要補完」判定をまとめて救うはずの機構だが、名も含めて綻び出し前の候補段階
だった。本書はその候補を 01〜03 の該当例に実際に適用して書き直し、機構の穴を炙り出す。綻びは
90-findings.md(F34〜)へ。方法論は他章と同じ「書いて綻びを見る」。
4.0 現行案(ADR-27・spec §4.9)の要約
| 語(仮称) | 型 | 意味 |
|---|---|---|
ordinalIn(w, d) |
点 → 数値 | 点 d が属する w 窓の中で第何要素か(1 起点・窓ごとリセット) |
epochOrdinal(w, d) |
点 → 数値 | 属する w 窓の紀元からの通し序数 |
labelOf(w, d) |
点 → ラベル/値 | 点 d の属する w 窓のラベル(cycle ラベル読みの一般形) |
snapTo(w) |
ストリーム変換 | 各点を属する w 窓の先頭点へ写す(floor) |
label:(付与) |
窓生成の引数 | 窓インスタンスにラベル式を貼る(未確定) |
以下、各サンプルを射影で書き、綻びに F 番号を振る。
4.1 暦座標(yearNo・monthNo・dayNo)は射影で導けるか
固定日祝日・イースター時点化・給料日などが要求する「点の暦座標」を、まず射影一族から導出してみる。
標準 premise(stdlib/gregorian.md)は既に月通し番号 m からの値関数 yearOf(m)・monthOf(m) を持つ。
点から m を出せれば、暦座標はその合成になる。
# 点 → その点が属する月の通し番号(紀元起点)
m(d) = epochOrdinal(month, d)
yearNo(d) = yearOf(m(d)) # 2026(既存値関数の再利用)
monthNo(d) = monthOf(m(d)) + 1 # 1〜12
dayNo(d) = ordinalIn(month, d) # 月内日序数 1〜31
判定: 導ける。暦座標は射影一族の新規語ではなく糖衣(epochOrdinal+ordinalIn+既存値関数)だと
確かめられた。固定日祝日(毎年 2/11)は filter(d => monthNo(d) == 2 and dayNo(d) == 11)、イースターは
filter(d => monthNo(d) == easterMonth(yearNo(d)) and dayNo(d) == easterDay(yearNo(d))) で書ける。
綻び:
- (F34) 射影一族に二つの「番号」がある——
epochOrdinal(紀元通し・単調増加)とordinalIn(窓内・ リセット)。両者は「どの上位窓で数えるか」で決まる同じ演算の別パラメータに見える。ordinalIn(month, d)= 日を月内で数える、epochOrdinal(month, d)=月を紀元から数える——数える対象と数える枠が引数で 絡む。ordinalIn(w, d)の「d は w の要素」だがepochOrdinal(w, d)の「d は w に属する」で d の意味が ずれている(前者は d が w の中身、後者は d が指す上位窓 w のインデックス)。一本化するならordinalIn(数える窓, 枠窓, d)の二窓引数か。名と引数の設計を要再考。
4.2 固定日祝日 — ordinalIn の「月内序数」と「日」の食い違い
@Gregorian
foundationDay = everyDay |> filter(d => monthNo(d) == 2 and dayNo(d) == 11) # 建国記念の日
dayNo(d) = ordinalIn(month, d) は「その月の第何日」。2/11 なら 11。期待通り(01 §1.1 の onMonthDay
糖衣を射影で厳密化できた。cycle ラベル述語に頼らずに済む)。
判定: 書ける。01 の「要補完」が射影で解ける。
綻び:
- (F35)
everyDayは既に日で間引かれた列なのでordinalIn(month, d)は「月の第何日」になるが、もし 上流が時刻列(everyInstant)ならordinalIn(month, d)は「月の第何瞬間」になり得る。ordinalInの 値は入力ストリームの粒度に依存する(何を 1 要素と数えるか)。「月の 11 日目」を意図するなら入力が 日粒度である前提が要る。この前提(射影は入力粒度に依存)を明文化しないとサイレント誤結果。 →ordinalInは「窓 w の中の入力要素の序数」であって「窓 w の下位窓 u の序数」ではない。後者 (月の中の第 11 日、入力粒度非依存)が欲しいならordinalIn(month, day, d)=二窓引数(F34 と同根)。
4.3 六曜 — labelOf のラベルの「源」が定まらない
@Lunisolar
rokuyoIndex = d => (lunarMonthNo(d) + ordinalIn(lunarMonth, d)) mod 6
lunarMonthNo(d) = labelOf(lunarMonth, d) # 旧暦の月番号(1〜12、閏月は前月番号)
ordinalIn(lunarMonth, d)(旧暦月窓内の日序数)は素直。問題は labelOf(lunarMonth, d)。旧暦の月番号は
中気を含むかで決まり(雨水を含む月=1 月)、閏月は番号を持たない(前月名を継ぐ)。この番号は窓の
自然な序数でも cycle の固定巡回でもない——計算して貼ったラベルである。つまり labelOf が読むには、
先に lunarMonth 窓へラベルを付与(label:)しておく必要がある。
判定: 要補完(label: 付与が未確定のまま)。読み(labelOf)だけ導入しても、貼る側が無ければ
読むものが無い。
綻び:
- (F36)
labelOfが読むラベルの源が三種類あり、現行案はこれを区別していない:- 源 1: cycle 由来(
weekday・干支)——cycleが固定巡回列で貼るラベル。 - 源 2:
label:付与(旧暦月番号・ISO 週番号・年度ラベル)——窓生成時に計算式で貼るラベル。 - 源 3: 暦座標(
monthNo・yearNo)——既存値関数からの糖衣(4.1)。labelOf(w, d)の一語で三源を統一的に読めるのか、源ごとに別語(cycleLabel/windowLabel/座標糖衣) なのかが未決。統一するなら「窓はラベルを一つ持ち、その源は生成時に決まる」というモデルが要る。
- 源 1: cycle 由来(
- (F37) 閏月番号は「前月名を継ぐ」=隣接窓参照(F24・射程外)。
label:の付与式が前の窓の値を 読めないと閏月名が貼れない。射程外(データに倒す)と決めた F24 がここで再燃する——旧暦月名はlabel:の計算式ではなく、テーブルリテラル(月名列)で持ち込むのが現実解、と改めて確認。
4.4 立春の選択(F33 の解決を射影で試す)
03 §3.3 で露見した「立春を序数で選べない」(F33)を射影で解こうとする。
# 案 A: 節気点に名前ラベルを付与(label:)して labelOf で読む
sekkiLabeled = sekkiInstants |> label: cycle [小寒, 大寒, 立春, 雨水, …24個…] # 時点列に巡回ラベル
risshun = sekkiLabeled |> filter(s => labelOf(sekki, s) == 立春)
# 案 B: 黄経で選ぶ(節気=黄経 15°刻み。立春=315°)
risshun = sekkiInstants |> filter(s => solarLongitude(s) == 315)
判定: 案 A は書けるが、label: を区間列型でない裸の点列(sekkiInstants)に貼る形になり、cycle が
「点に巡回ラベル」を貼る本来(day 窓など単位窓へ)とずれる。案 B の solarLongitude(s) は連続天文量で
射影一族の外(データ)。
綻び:
- (F38) ラベル付きテーブルリテラルが要る(F33 の帰結)。節気は「時点+名前+黄経」の三つ組データ。
現行テーブルリテラル(ADR-26)は時点のみ。
label:を後付けする案 A は「24 点に 24 ラベルを対応づける」 =実質ラベル列を別に持つことで、テーブルリテラルを列を持つ表(時点列+ラベル列)に拡張するのが素直。 ADR-26 の拡張点。 - (F39) 案 A の
label: cycle […]は「巡回」だが節気 24 名は 1 年で 1 巡(巡回だが周期=データ長)で、 cycle の「暦と独立に回り続ける律動」(weekday)とは性格が違う。データ列への 1 対 1 ラベル付けは cycle(律動)でなく表の列(源 2 の付与)で捉えるべき——cycle と label: の役割境界が要整理。
4.5 ISO 週番号 — ordinalIn では規約が出ない
@ISO # wkst: Mon
isoWeekNo(d) = ordinalIn(year, weekOf(d)) # 年内で週が第何番目?
ISO 8601 の週番号は「その年の第 1 木曜を含む週=W01」。単純な「年内の第何週」ではない(年初の数日が
前年の最終週 W52/W53 になる、年末が翌年 W01 になる)。素の ordinalIn(year, week) では出ない。
判定: 要補完。ordinalIn は素の序数で、ISO の木曜規約を表現できない。
綻び:
- (F40) ISO 週番号は「週窓に規約つきの番号を貼る」=
label:の付与式に規約(第 1 木曜基準)を書く 問題。しかも週の所属年(W01 が前年か当年か)は週窓の属する上位窓が動く(年境界をまたぐ週)。labelOf(year, weekStartDay)の year が「木曜の属する年」——ラベル計算が別の点(週の木曜)の窓を 参照する。射影が「d の属する窓」だけでなく「d に関係する別の点の窓」を要求する初のケース。 年度ラベル(開始年/終了年)と同型で、label:付与式の表現力(どの点の窓を見てよいか)の設計が要る。
4.6 snapTo と公開境界語の重複
snapTo(month) # 各点 → その月の初日(floor)
month |> first # 各月窓 → その先頭点(選択子)
Gregorian.monthStart # 公開境界語(= month |> first)
snapTo(month) は「点→窓先頭点」、monthStart は「窓→先頭点」。入口(点かストリームか窓か)が違うが
同じ『月の初日』を三経路で出せる。節気の瞬間を日に丸める(03 F21)には snapTo(day) が要ったが、
これは「窓の先頭点」という窓属性の読み——labelOf の値がラベルでなく境界点の版とも言える。
判定: 書けるが機構が重複ぎみ。
綻び:
- (F41)
snapTo(w)・w |> first・公開境界語(monthStart)・labelOf(境界点版)が同じ「窓の属性 (先頭点)」を別経路で読む。snapTo は点変換、first は選択子、公開境界語は生成子、と層が違うだけで 射影の実体は一つ(窓→その先頭点)。射影一族に「窓の境界点を読む」を含めるなら選択子・公開境界語との 役割整理が要る(重複は綻びの兆候)。逆に snapTo を「点→属する窓→その first」の糖衣と見れば新規語は 不要かもしれない(snapTo(w) = d => (d が属する w 窓) |> first)。
4.7 「n 個ごと窓リセット」ストライドの還元(ADR-27 の主張の検算)
ADR-27 は「ストライドの窓ごとリセット版は filter(d => (ordinalIn(w, d) - 1) mod n == 0) に還元され
専用記法を持たない」とした。検算する。
# 各月の 1・4・7・…日目(3 日ごと・月頭でリセット)
@Gregorian
everyDay |> filter(d => (ordinalIn(month, d) - 1) mod 3 == 0)
判定: 書ける。月頭で位相がリセットされ、ordinalIn が月内序数なので mod 3 が月ごとに 1 から数え
直す。ADR-27 の主張は成立。
綻び:
- (F42) ただし「3 営業日ごと・月リセット」だと、入力が営業日列(間引き済み)になり
ordinalIn(month, d)は「月内の第何営業日」を返す(F35 と同根=入力粒度依存)。これは意図通り(営業日で数えたい)だが、 「3 暦日ごと・月リセット」を営業日列の上でやりたいときはordinalInが暦日を数えないので書けない。 ストライド(走査間引き)と射影(窓内序数)の使い分けの境界=「何を 1 と数えるか」がやはり入力粒度に 縛られる(F35/F42 は射影一族全体に効く横断的制約)。
小括 — 射影一族に必要な設計判断
01〜03 の「要補完」は射影一族で大半が書けた(固定日祝日・イースター・六曜の骨格・窓リセット)。 だが綻び出しで、候補のままでは決められない設計判断が六つ見えた。
| 判断 | 綻び | 方向の当たり | |
|---|---|---|---|
ordinalIn/epochOrdinal の引数設計(数える対象と枠) |
F34 | 二窓引数 ordinalIn(数える窓, 枠窓, d) に一本化を検討 |
|
| 射影値は入力ストリームの粒度に依存する | F35・F42 | 「窓の下位窓を数える」版(粒度非依存)と「入力要素を数える」版の分離 | |
labelOf が読むラベルの源(cycle/label:/暦座標) |
F36 | 「窓はラベルを一つ持ち源は生成時に決まる」モデルの明文化 | |
窓ラベル付与 label: の表現力(隣接窓・別点の窓の参照) |
F37・F40 | 隣接窓は射程外(データに倒す)、別点の窓参照(ISO 週・年度)は要設計 | |
| ラベル付きテーブルリテラル(時点+ラベル列) | F38・F39 | ADR-26 を「列を持つ表」へ拡張。cycle(律動)と表の列(1 対 1)を分ける | |
snapTo と選択子・公開境界語の重複 |
F41 | snapTo(w) を「点→属する w 窓 |
> first」の糖衣として new 語を減らす |
総括: 射影一族は「暦座標は糖衣で導ける(新規語は epochOrdinal・ordinalIn の二つで足りる)」ことが
確認できた一方、labelOf はラベルの源の三分類と付与 label: の設計が本体で、そこが未決のまま
だと六曜の月名・ISO 週番号・年度ラベル・節気名が書けない。次段は (a) ordinalIn/epochOrdinal の二窓
引数への一本化、(b) labelOf のラベル源モデル、(c) label: 付与+ラベル付きテーブルの設計——この三点を
詰める(draft §1.17 の改稿と ADR-27 の改訂)。snapTo は糖衣化で新規語から外せる見込み。