表現力検証 3: 天文データ依存の暦(旧暦・二十四節気・月相)と計算暦(イースター)
表現力検証の第 3 部。周期規則から生成できない(天文データが要る)暦と、複雑だが純粋に
計算できる暦(イースター)を対にして、「規則とデータの境界」を言語がどう受けるかを試す。
設計上の受け皿は確認済み: ADR-07 は「月相のストリームで区切れば太陰暦」と明言し、segmentBy が
任意マーカーで窓を切る。欠けているのはマーカーデータの持ち込み口である(01 §1.3 の F5 と同根)。
3.1 二十四節気
仕様: 太陽黄経が 15° の倍数を通過する瞬間(立春 315°・春分 0°・夏至 90°・秋分 180°・冬至 270° …)。
不等間隔(ケプラー運動)で、周期規則からは生成できない。暦要項(国立天文台)が権威データ。
期待値(NAOJ 令和8年暦要項・95-reference-data.md): 立春 2026-02-04 05:02、春分 03-20 23:46、
夏至 06-21 17:25、秋分 09-23 09:05、冬至 12-22 05:50。時刻を持つ点であることに注意(F21 の動機)。
premise Sekki24 {
source: "nao.ac.jp/koyomi" # 暦要項
asof: 2025-02-03 # 令和8年暦要項の告示日
# 節気の瞬間列(テーブルリテラル・実データ。時刻は暦要項の値。24 個の全列は 95-reference-data.md)
sekkiInstants = [2026-01-05T17:23, 2026-01-20T10:45, 2026-02-04T05:02, 2026-02-19T00:52,
2026-03-05T22:59, 2026-03-20T23:46, 2026-04-05T03:40, 2026-04-20T10:39,
2026-05-05T20:49, 2026-05-21T09:37, 2026-06-06T00:48, 2026-06-21T17:25,
2026-07-07T10:57, 2026-07-23T04:13, 2026-08-07T20:43, 2026-08-23T11:19,
2026-09-07T23:41, 2026-09-23T09:05, 2026-10-08T15:29, 2026-10-23T18:38,
2026-11-07T18:52, 2026-11-22T16:23, 2026-12-07T11:53, 2026-12-22T05:50]
# 日への落とし込み: 瞬間を含む市民日(暫定 snapTo。F21)
sekkiDays = sekkiInstants |> snapTo(day)
}
# 利用例: 節気で区切った窓と、その第 1 日。labels: が節気名を窓に結ぶ(ADR-39——リスト長=窓数を検査)
@Sekki24
sekkiW = everyDay |> segmentBy(sekkiDays, edges: drop, empties: error,
labels: ["小寒", "大寒", "立春", "雨水", "啓蟄", "春分", "清明", "穀雨",
"立夏", "小満", "芒種", "夏至", "小暑", "大暑", "立秋", "処暑",
"白露", "秋分", "寒露", "霜降", "立冬", "小雪", "大雪", "冬至"])
sekkiW |> first
判定: 要外部データ(テーブルリテラル F5)。窓化の受け皿 segmentBy は仕様通り機能する。
実データ(時刻つき 24 点)を流し込んで期待値が再現できることを確認済み(95-reference-data.md)。
綻び:
- (F21) 点→窓先頭への射影(snap/floor)が無い。節気の「瞬間」(05:02 など)を「その日」に落とすには
「点の属する day 窓の先頭点」への写像が要る。既存語彙では
within(day) |> firstが近いが、これは 「ストリームを日で束ねて各窓の第 1 要素」であり、要素そのもの(05:02 の点)が返る——窓の境界点 (00:00)は返らない。暫定語snapTo(w)を置いて先へ進む(§1.17 の値射影一族と同根: 窓の先頭点は 窓の属性)。 - (F22)
segmentByのマーカーに瞬間(日より細かい点)を渡すと、日ストリームの各要素(日)が どちらの区間に属すかは日の代表点(先頭 00:00)で判定される。節気当日は境界の瞬間より代表点が 前なので前の節月に落ちる——旧暦の朔日規約(瞬間を含む日から新しい月)と逆。マーカーをsnapTo(day)してから渡す(上の書き方)ことで回避したが、粗い単位列を細かいマーカーで切るときの 所属規約は segmentBy の意味論として明文化が要る。
3.2 旧暦(天保暦様式の太陰太陽暦)
仕様: 朔(新月)の瞬間を含む日が月の第 1 日(朔日)。朔から次の朔の前日までが一月(29 日=小の月/
30 日=大の月)。月名は「中気」(二十四節気の偶数番目。雨水・春分・穀雨…)を含むかで決まり、中気を
含まない月は閏月(前月の名を継いで「閏 N 月」)。
期待値: 2026-02-17 が旧暦 1 月 1 日(旧正月)。実データで裏づけ済み——朔 2026-02-17 21:01 が
その根拠(95-reference-data.md。朔の瞬間を含む日=2/17 が朔日)。
premise Lunisolar {
source: "nao.ac.jp/koyomi"
asof: 2025-02-03
# 朔の瞬間列(実データ・12 回。NAOJ 令和8年暦要項)
newMoons = [2026-01-19T04:52, 2026-02-17T21:01, 2026-03-19T10:23, 2026-04-17T20:52,
2026-05-17T05:01, 2026-06-15T11:54, 2026-07-14T18:44, 2026-08-13T02:37,
2026-09-11T12:27, 2026-10-11T00:50, 2026-11-09T16:02, 2026-12-09T09:52]
# 中気=黄経が 30 の倍数(雨水330・春分0・穀雨30…)。暦年順の先頭は小寒(節気)なので
# stride(2) は起点をずらさないと節気を拾う(実データ投入で判明。F32)。黄経ラベルで filter するのが厳密。
chukiDays = (Sekki24.sekkiInstants |> stride(2, from: <大寒>) |> snapTo(day))
# 月番号: 「含む中気」で決まる(雨水を含む月=1 月)。中気を含まなければ閏月(前月名を継ぐ)——
# 導出はデータ側(95-reference-data.md)に置き、窓には labels: で番号列を結ぶ
# (F23 → ADR-34〈label: ラムダ〉+ADR-39〈labels: リスト〉で解消。リスト長=窓数の同長検査つき)
monthNos = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
# 月窓: 朔日で day 列を区切る。読みは束縛名射影 lunarMonth(d) = 月番号
lunarMonth = day |> segmentBy(newMoons |> snapTo(day), edges: drop, empties: error, labels: monthNos)
}
判定: 要外部データ+要補完。骨格(朔で切る)は ADR-07 の想定通り segmentBy 一発で書けた。
大小の月は窓幅の帰結として自動で出る(数える必要すらない)。閏月の判定(中気を含まない)も
窓とストリームの交差なので既存概念で表せる。書けないのは月名の付与である。
綻び:
- (F23) 窓へのラベル付与機構が無い(→ 後に解消: 計算ラベルは
label:付与式=ADR-34・データラベルはlabels:並行リスト=ADR-39〈同長検査つき〉。上のコードは確定形に更新済み)。パーティション窓の各 インスタンスに「この窓は 1 月」「これは 閏 5 月」という値(番号・ラベル)を計算して貼る手段が無い。cycleは固定巡回列のラベルしか 貼れず(閏月で 12 巡回が破れる)、六曜(02 §2.4 F18)で要った「窓ラベルの値射影」の書き込み側が これ。読み書き揃って「窓→値の射影」一族=補完機構の本命 2。 - (F24) 「前の月の名を継ぐ」は窓列に沿った再帰(前の窓の値への参照)。値式は現状、点ごと・窓ごとに 独立で、隣接窓を参照する手段が無い。閏月名はこれが無いと書けない(データとして月名列を持ち込めば 回避可能——実務上は暦要項が月名まで確定させるので、テーブルリテラル側に倒すのが現実解)。
- (F25)
premise 内で本体層の演算子(segmentBy・stride・snapTo)を使った。ADR-25 は「語彙は本体層と 共有し統廃合」なので方向は正しいはずだが、premise 層の窓生成語(grid/span/split)と本体層の窓演算子 (within/segmentBy)の層またぎ規則(premise の窓束縛の右辺に何を書いてよいか)は未明文。 旧暦が書けるかはこの一点に掛かっている——明文化必須。
3.3 雑節(八十八夜・二百十日・彼岸)
仕様: 立春から数えて 88 日目の夜=八十八夜(立春+87 日)、210 日目=二百十日(立春+209 日)。 彼岸は春分・秋分を中日とする前後 3 日ずつの 7 日間。 期待値: 2026 年の八十八夜 05-02、二百十日 09-01。春彼岸 2026-03-17〜03-23。
@Sekki24
# 立春=黄経 315°。だが暦年の最初の節気は小寒(1/5)なので nth(1, of: year) は小寒を返す(実データで判明・F33)。
# 節気を名前で一意に選ぶには窓ラベル(§3.1 の sekkiW=labels: 付き segmentBy)の束縛名射影を使う:
risshun = sekkiDays |> filter(s => sekkiW(s) == "立春") # 束縛名射影で一意(2026 は 2/4。F48 で字面統一)
hachijuhachiya = risshun |> shift(+87, unit: day) # 立春 2/4 → 5/2(実データの八十八夜 5/2 と一致)
nihyakutoka = risshun |> shift(+209, unit: day) # 立春 2/4 → 9/1(実データの二百十日 9/1 と一致)
# 彼岸(中日±3 日の 7 日間): 固定幅の和で列挙
higan(mid) = mid | (mid |> shift(-1, unit: day)) | (mid |> shift(-2, unit: day)) | (mid |> shift(-3, unit: day))
| (mid |> shift(+1, unit: day)) | (mid |> shift(+2, unit: day)) | (mid |> shift(+3, unit: day))
判定: 節気データがあれば書ける。shift の素直な適用で、雑節は「節気からの派生値」だと確かめられた。
綻び:
- (F26) 「±3 日の範囲」の列挙が鈍い(01 §1.9 F12 の日付範囲と同根)。
shiftの範囲版 (spread(-3..+3, unit: day)相当)は糖衣で書けるが固定幅の列挙になる。可変幅は fold(可変長の和)が 無いので書けない。 - (F27)
sekkiDaysはwithin(year)を通っていない裸のストリームで、選択子がof:だけで窓文脈を 持てるか(それともwithin(year)を先に通すべきか)は選択子の窓解決規約(I4・既定は最内窓)の際どい端で、 要明文化。 - (F33) 実データで判明: 「立春=各年最初の節気」は誤り。暦年の最初の節気は小寒(2026 は 1/5)で、
nth(1, of: year)は小寒を返す。特定の節気(立春=黄経 315°)を選ぶには、点から節気の名前/黄経を読む 射影(labelOf・射影一族 ADR-27)と、時点だけでなくラベルを持つテーブル(テーブルリテラルの拡張)が 要る。時点のみのテーブルリテラルでは序数でしか選べず、暦の意味的な位置(立春)で選べない——射影一族の 必要性を実データが具体的に裏づけた。
3.4 月齢・月相
仕様: 満月・上弦・下弦・新月(月相イベント)と、月齢 n の日(朔からの経過日数)。
期待値: 2026 年最初の満月は 01-03(望 2026-01-03 19:03。実データ照合済み・95-reference-data.md)。
premise Moon {
source: "nao.ac.jp/koyomi"
asof: 2025-02-03
# 望(満月)の瞬間列(実データ・13 回。NAOJ 令和8年暦要項)
fullMoons = [2026-01-03T19:03, 2026-02-02T07:09, 2026-03-03T20:38, 2026-04-02T11:12,
2026-05-02T02:23, 2026-05-31T17:45, 2026-06-30T08:57, 2026-07-29T23:36,
2026-08-28T13:19, 2026-09-27T01:49, 2026-10-26T13:12, 2026-11-24T23:54,
2026-12-24T10:28]
}
@Moon
fullMoonDays = fullMoons |> snapTo(day)
# 月齢 n の日: 朔日から n 日後(Lunisolar の朔データを再利用)
moonAge(n) = (Lunisolar.newMoons |> snapTo(day)) |> shift(+n, unit: day)
判定: 要外部データ。「月齢」という連続量そのものは点列言語の外(射影値)で、スケジュールとして 意味を持つのはイベント化した列(満月の日・月齢 15 の日)——これは既存語彙で書ける。連続量→ イベント列への還元は利用者ではなくデータ供給側(暦要項が満月の瞬間を出す)の仕事、と整理できた。
綻び: 新規なし(F5・F21 に依存するのみ。「連続量はイベント化してから持ち込む」を テーブルリテラルの設計原則として記録)。
「計算できないから外部データ」ではない——できるが、あえて取らない(2026-07-11 追記)
外部データ供給は能力の欠如ではなく真正性の統治(ADR-15)の帰結である。「月相の計算」は三段に 割れ、切り分けるとこの判断の論拠が立つ:
(1)平均朔望月の算術近似は、今日の確定語彙でそのまま書ける(規則 premise・外部データゼロ・
暦法純粋 I8)。朔望月の平均 29.530589 日=経過時間幅 708h44m3s:
# eval: 2026-01-01..2027-01-01
premise MeanMoon {
calendar-system: Gregorian
tz: "Asia/Tokyo"
# 平均朔望月による朔の近似。錨は既知の朔(2000-01-06T18:14 UTC = JST 翌 03:14)
meanNewMoons = everyInstant |> strideBy(708h44m3s, from: 2000-01-07T03:14)
}
premise J { calendar-system: Gregorian; tz: "Asia/Tokyo"; wkst: Mon }
@J
MeanMoon.meanNewMoons |> snapTo(day)
#=> 2026-01-18 2026-02-17 2026-03-19 2026-04-17 2026-05-17 2026-06-15
#=> 2026-07-15 2026-08-13 2026-09-12 2026-10-11 2026-11-10 2026-12-09
NAOJ の朔日(95-reference-data)との照合は 8/12 一致・4 か月が ±1 日ずれ(真の朔は楕円軌道の 摂動で平均から最大 ±14 時間揺れる)——「書ける」と「官暦に合う」は別物であることを実行が示す。
(2)真の朔弦望(Meeus 級の天文アルゴリズム)は原理的には決定的計算だが、現在の値式では書けず、
この欠けは意図的。周期項の総和には三角関数が要り、値式は sin/cos を持たない(必然基準)。
足せば書けるが、60 項超の係数列を premise に並べる姿は実質「データの持ち込み」と同型であり、
さらに ΔT(地球自転の不規則減速)は観測値で予測不能——天文アルゴリズム自体が観測フィットの
近似模型で、遠未来の「純粋計算」は原理的に崩れる。
(3)官暦との一致は、どれだけ精密に計算しても買えない。旧暦の月切り・六曜・春節は「NAOJ が計算し 官報で告示した値」が正で、独自計算が 1 分ずれて日境界を跨げば別の暦になる(実例は記録済み—— 2025-08-23 の朔と処暑の同日競合〈95-reference-data〉・2027 春節の JST/CST 割れ)。
まとめ: 算術近似は書ける(上の実行例)・精密計算は sin/cos を足せば書けるが導入の必然が無い・ 公式暦への一致は計算では買えない——だから権威データを差し込む。スタンスは「できないから外部」 ではなく「できるが、真正性のために取らない」。
3.5 イースター(復活祭)— 計算可能な移動祝日
仕様: 春分後の最初の満月の次の日曜(教会暦の算法=Computus。天文データでなく純粋な算術で 確定する)。Anonymous Gregorian algorithm を値式で書けるかのストレステスト。 期待値: 2025-04-20、2026-04-05。
# 値レイヤー(§1.10)だけで書く Computus。y は暦年
a = y => y mod 19
b = y => y div 100
c = y => y mod 100
h = y => (19*a(y) + b(y) - b(y) div 4 - (b(y) - (b(y)+8) div 25 + 1) div 3 + 15) mod 30
l = y => (32 + 2*(b(y) mod 4) + 2*((y mod 100) div 4) - h(y) - (y mod 100) mod 4) mod 7
m = y => (a(y) + 11*h(y) + 22*l(y)) div 451
easterMonth = y => (h(y) + l(y) - 7*m(y) + 114) div 31 # 3 または 4
easterDay = y => ((h(y) + l(y) - 7*m(y) + 114) mod 31) + 1
# 時点列への持ち上げ: 年ごとの (month, day) 値を日付に変える手段が無い(F28)
# 暫定案 1(値→時点の持ち上げ dateOf があれば):
# easter = everyYear |> mapToDate(y => dateOf(y, easterMonth(y), easterDay(y)))
# 暫定案 2(射影値との突き合わせ。F18 のラベル射影があれば):
@JPX
easter = everyDay |> filter(d => monthNo(d) == easterMonth(yearNo(d)) and dayNo(d) == easterDay(yearNo(d)))
判定: 値計算は書ける・時点化で要補完。mod・div・四則・入れ子の関数適用だけで Computus は
完全に書けた(値レイヤーの表現力は十分)。書けないのは最後の一歩=値 (y, m, d) から基底上の点への
持ち上げである。
綻び:
- (F28) 値→時点の持ち上げ(
dateOf(y, m, d)相当)が無い。逆向き(点→値の射影:yearNo・monthNo・dayNo)も無い(F17/F18 と同族)。どちらか一方があれば書ける(暫定案 2 は射影だけで 済む)。射影一族(§1.17)に「点→序数」を入れれば、持ち上げ無しでもイースター・固定日祝日・六曜が 全部 filter 形で書ける——射影を先に、持ち上げは保留が筋がよい。 - (F29) 値関数定義で
a = y => …と束縛の連鎖を書いたが、premise ブロック外の「その場の値束縛」を どこに書くか(本体式の前文か、束か)が未明文。糖衣定義(§1.13)は変換の束縛であり、値関数の束縛の 置き場は §1.10 が premise 層内としか言っていない。
3.6 ISO 週番号(参考・サンプルのみ)
仕様: ISO 8601 の週番号(月曜始まり・その年の第 1 木曜を含む週が W01)。「2026-W01-1」のような 週ラベルの体系。 期待値: 2026-W01 は 2025-12-29(月)〜2026-01-04(日)。
# week 窓(wkst: Mon)自体は §1.14 暫定で立つ。問題は「どの週が W01 か」=週への年・番号ラベル付与
# = F23(窓ラベル付与)+年度ラベルの射影(90-open-questions の既存宿題)と同一問題。機構設計は宿題送り。
判定: 要補完(F23 と年度ラベル射影に依存)。週窓そのものは書ける。番号付けは旧暦の月名付け (3.2)・年度ラベルと同じ「窓→値」問題の一族だと確認した——三例が同じ機構に還元されるのは 補完機構(§1.17)の設計妥当性の傍証。
小括
| 例 | 判定 | 依存する暫定/欠落 |
|---|---|---|
| 二十四節気 | 要外部データ | テーブルリテラル(F5)・snapTo(F21)・segmentBy 所属規約(F22) |
| 旧暦 | 要外部データ+要補完 | 窓ラベル付与(F23)・隣接窓参照(F24)・層またぎ規則(F25) |
| 雑節 | 節気があれば書ける | 範囲 shift(F26)・裸ストリームへの of:(F27) |
| 月齢・月相 | 要外部データ | 「連続量はイベント化して持ち込む」原則で整理済み |
| イースター | 値は書ける・時点化で要補完 | 値→時点の持ち上げ or 点→値の射影(F28)・値束縛の置き場(F29) |
| ISO 週番号 | 要補完 | F23+年度ラベル射影(既存宿題と同一) |
天文暦の壁は予想通りデータの持ち込み口(テーブルリテラル)に集約され、窓化の受け皿 (segmentBy)は仕様の想定通り機能した。予想外の収穫は三つ——(1) snap/所属規約という「粒度の 継ぎ目」の未定義(F21/F22)、(2) 旧暦の月名・ISO 週番号・年度ラベルが同一の「窓→値の射影」問題に 還元されること(F23)、(3) 値レイヤーは Computus 級の計算に耐えるが基底との出入口(射影・ 持ち上げ)が無いこと(F28)。