表現力検証 2: 周期系(十干・十二支・干支・六曜・選日)
表現力検証の第 2 部。並列サイクル(cycle)の一般化耐性を試す。weekday(7 要素・day 窓)しか
実例が無かった cycle を、周期長 10・12・60、適用先 day/year、複数サイクルの合成、窓ごとリセット
(六曜)へ広げる。前提・記法は README.md と 01-jp-holidays.md に同じ。
2.1 十干・十二支(日)
仕様: 十干(甲乙丙丁戊己庚辛壬癸)は 10 日周期、十二支(子丑寅卯辰巳午未申酉戌亥)は 12 日周期の
並列ラベル。暦月・暦年とは独立に、途切れず回り続ける——weekday と完全に同族。
期待値: 日の干支の位相(どの日が甲子か)は暦書・暦要項の権威データで確定する(下記綻び F13)。
暦要項には日の干支が載らないことを実際に確認した(95-reference-data.md。F13 の裏付け)。
参考値(民間暦・複数サイト一致、権威データではない): 2026 年の甲子日は 2/19・4/20・6/19・8/18・
10/17・12/16。下の anchor はこの参考値 2026-02-19 を仮に用いる。
premise JPSexagenary = Gregorian with {
dayStem = day cycle [甲, 乙, 丙, 丁, 戊, 己, 庚, 辛, 壬, 癸] anchor: 2026-02-19 # 甲子(参考値)
dayBranch = day cycle [子, 丑, 寅, 卯, 辰, 巳, 午, 未, 申, 酉, 戌, 亥] anchor: 2026-02-19 # 同上(甲子)
}
# 利用例: 庚申の日(庚 ∧ 申。60 日ごと)
@JPSexagenary
kōshinDay = everyDay |> filter(d => dayStem(d) == 庚 and dayBranch(d) == 申)
cycle の周期長は仕様上 7 に限定されていない(§1.11 の定義は「巡回列+anchor 位相」のみ)ので、
10 要素・12 要素はそのまま通る。二つの並列サイクルの合成(干支)は、ラベル述語の and で
値式側に畳める——cycle 同士を合成する専用機構は要らなかった。
判定: 書ける(暫定語彙: cycle ラベル述語)。
綻び:
- (F13)
anchor:に置く「甲子の実日付」は計算では出ない権威データ(暦要項・暦書)。つまり anchor 引数はテーブルリテラル(F5)と同じくsource:の適用対象である。規則(巡回列)は言語内、 位相(データ)は出所つき——premise の中に規則とデータの境界が走る、という整理が要る。 - (F14) ラベルに漢字(甲・子…)を使った。列挙値(
Mon等)の字句として非 ASCII 識別子を許すかは 未定義(字句規則の宿題に追加)。
2.2 十干・十二支(年)— 年窓へのサイクル
仕様: 年の十二支(2020=子 … 2026=午)と十干(2024=甲、2026=丙)。丙午(ひのえうま)年の検出。 期待値: 2024=甲辰、2025=乙巳、2026=丙午。次の丙午は 2086。
premise JPEto = Gregorian with {
yearStem = year cycle [甲, 乙, 丙, 丁, 戊, 己, 庚, 辛, 壬, 癸] anchor: 2024-01-01 # 2024=甲
yearBranch = year cycle [子, 丑, 寅, 卯, 辰, 巳, 午, 未, 申, 酉, 戌, 亥] anchor: 2020-01-01 # 2020=子
}
# 利用例: 丙午年の元日(1966, 2026, 2086, …)
@JPEto
hinoeumaNewYear = everyDay |> within(year) |> first |> filter(d => yearStem(d) == 丙 and yearBranch(d) == 午)
cycle の適用先を day から year 窓に替えた初の例(§1.14 暫定の「単位窓以外への cycle」)。
anchor: 2024-01-01 は「anchor の属する窓(2024 年窓)が先頭ラベル」と読む——この解釈で
day の場合(属する day 窓=その日)とも一貫する。
判定: 書ける(暫定語彙: 年窓への cycle・anchor の窓解釈)。
綻び:
- (F15) ラベルは窓(年)に付くが、述語で読むのは点(日)である。
yearStem(d)は「点 d の属する year 窓のラベル」と二段で解決している。この「点→属する窓→ラベル」の合成規則は暫定解釈の中に 暗黙に入っており、明文化が要る(会計暦派生で year 窓が動くと、同じ点のラベルが変わる——それで 正しいのか? 干支は暦年に貼り付くべきでは? → ラベルの定義 premise にピンするGregorian.yearBranchの修飾(ADR-17 機構 A)が答えになる。派生とラベルの相互作用は要注意と記録)。
2.3 干支 60(六十干支の直接定義)
仕様: 甲子・乙丑・丙寅…癸亥の 60 周期を一本のサイクルとして。
期待値: 2.1 の合成(dayStem == 甲 and dayBranch == 子)と同じ列になること(60 = lcm(10, 12))。
premise JPSexagenary60 = Gregorian with {
dayKanshi = day cycle [甲子, 乙丑, 丙寅, …(60 要素の列挙)…, 壬戌, 癸亥] anchor: ⟨甲子の実日付⟩
}
判定: 書けるが鈍い。60 要素の直書きは可能で意味論も明確だが、「十干×十二支の 60 は二つの
サイクルの積」という構造情報が失われる。2.1 の合成(述語の and)が使えるので実用上は困らない。
綻び:
- (F16) cycle 同士の積(zip)を premise 層で書けると 60 要素の列挙が消える
(
dayKanshi = zip(dayStem, dayBranch)相当)。ただし F15 の通り述語合成で足りるため、 優先度は低い。「書けるが意味が欠落する」の記録として残す。
2.4 六曜 — 窓ごとリセットの本丸
仕様: 先勝・友引・先負・仏滅・大安・赤口の 6 周期。ただし単純な巡回ではなく、 旧暦の月初(朔日)ごとに位相がリセットされる: 六曜番号 = (旧暦月数 + 旧暦日数) mod 6 (旧暦 1 月 1 日=先勝、2 月 1 日=友引、…)。 期待値: 2026-02-17(旧暦 1 月 1 日=旧正月)は先勝。(1+1) mod 6 = 2 → 先勝。
# 前提: 旧暦 premise(03-astronomical.md §3.2)が lunarMonth 窓と月番号を供給しているとする
@Lunisolar
rokuyoIndex = d => (lunarMonthNo(d) + lunarDayNo(d)) mod 6
taian = everyDay |> filter(d => rokuyoIndex(d) == 0) # 大安のみ抽出
# 対応表: 2=先勝, 3=友引, 4=先負, 5=仏滅, 0=大安, 1=赤口
判定: 要補完+要外部データ。二段の欠落がある。
- 土台の旧暦(
lunarMonth窓)が要外部データ(朔の列。03 §3.2)。 lunarMonthNo(d)(点の属する旧暦月の月番号)とlunarDayNo(d)(旧暦月窓内の日序数)が 書けない。後者は「窓内序数の値射影」——選択子nthの逆向き(点→序数)で、既知の宿題 「ストライドの窓ごとリセット版」と同族。前者は「窓のラベルの値射影」で、年度ラベル・ISO 週番号の 宿題とも同族。
注目すべきは、六曜に cycle は不要だったこと。「月ごとにリセットされるサイクル」を cycle の変種
(cycle … resetBy: w)として機構化するより、窓内序数+剰余の値計算に還元するほうが仕様
((月+日) mod 6)に直接対応する。リセット付きサイクルという新機構でなく「窓→値の射影」一族
(ordinalIn・ラベル射影)を導入すれば、六曜・年度ラベル・ISO 週番号・ストライドのリセット版が
同じ根から出る。→ 補完機構の本命 2(§1.17 候補)。
綻び:
- (F17) 窓内序数の値射影
ordinalIn(w, d)(点 d が窓 w の第何要素か)が無い。 - (F18) 窓ラベル(番号)の値射影(点 d の属する w 窓の番号・ラベル)が無い。旧暦の月番号は 閏月(閏五月は「五」のまま)を持つため単純な通し番号でなく、窓生成時にラベルを与える機構が 素直(03 §3.2 の綻びと連動)。
2.5 一粒万倍日(選日)— 節気月 × 日干支
仕様: 節切り月(立春起算の十二節で切った月。03 §3.3)ごとに定められた十二支の日が一粒万倍日。 例: 正月(立春〜啓蟄前)は丑・午の日、二月(啓蟄〜清明前)は酉・寅の日(以下、月ごとの対応表が続く)。 期待値: 対応表自体が暦書データ(下記 F19)。構造の検証が主眼。
@JPSexagenary # +節気窓(03 §3.3 の sekki premise)
ichiryumanbai =
(everyDay & sekkiMonth(寅) |> filter(d => dayBranch(d) == 丑 or dayBranch(d) == 午))
| (everyDay & sekkiMonth(卯) |> filter(d => dayBranch(d) == 酉 or dayBranch(d) == 寅))
| … # 十二月分の列挙
節気窓(区間)との積で月を絞り、日干支ラベルで選ぶ。二つの独立な暦構造(節気の区切りと 干支の巡回)の合成は、結合子とラベル述語の直交性がそのまま効く。
判定: 構造は書ける(節気データに依存)。月→支の対応表は値リスト(12 要素)に畳めるが、
「窓(節気月)から値(対応する支のリスト)を引いて述語にする」には F18 のラベル射影が要る——
あれば filter(d => dayBranch(d) in branchesFor(sekkiMonthNo(d))) の一行になる(in も未定義。F20)。
綻び:
- (F19) 選日の対応表も権威データ(暦書間で異同あり)。source: の適用対象。
- (F20) リスト所属述語
in(x in [a, b])が値式に無い。orの列挙で書けるが、対応表を データ(リスト)として持つならinがないと繋がらない。
小括
| 例 | 判定 | 依存する暫定/欠落 |
|---|---|---|
| 十干・十二支(日) | 書ける | anchor が権威データ(F13)・漢字ラベル(F14) |
| 十干・十二支(年) | 書ける | 年窓 cycle・点→窓→ラベルの二段解決(F15) |
| 干支 60 | 書けるが鈍い | cycle の積は不在でも述語合成で足りる(F16) |
| 六曜 | 要補完+要外部データ | 窓内序数 ordinalIn(F17)・窓ラベル射影(F18)・旧暦 |
| 一粒万倍日 | 構造は書ける | 節気データ・ラベル射影・in 述語(F19/F20) |
cycle は周期長・適用先の一般化に耐えた(F15 の明文化は必要)。リセット付きサイクル(六曜)は
cycle の変種でなく「窓→値の射影」への還元が正解というのが本部の最大の発見。これは年度ラベル・
ISO 週番号・ストライドのリセット版と同じ根であり、補完機構の設計(§1.17)に直結する。