今回やること
前回は「作った」という話だけだった。今回は手を動かす。題材は給与計算の定番、
毎月 25 日。ただしその日が土日または祝日なら、直前の営業日に繰り上げる。
これをゼロから組み立てる。所要時間はコピペ込みで 10 分程度。
準備
リファレンス実装は TypeScript 製・依存ゼロ。Node.js 24 があれば動く。
git clone https://github.com/azathothx/kairos-lang cd kairos-lang/impl
実行はファイルを渡すだけ。
node src/cli.ts <file.kairos> --from 2026-07-01 --to 2026-11-01
--from/--to は評価範囲。Kairos の式は「時点の無限リスト」を定義するので、実体化するときに範囲を切る。
ステップ 1: まず素朴に「毎月 25 日」
payday.kairos というファイルを作る。
premise JP {
calendar-system: Gregorian
tz: "Asia/Tokyo"
wkst: Mon
}
@JP
everyDay |> within(month) |> nth(25)
前半の premise JP { … } は前提の宣言。グレゴリオ暦・東京時間・週は月曜始まり。式の意味はすべて前提の下で決まる、というのが Kairos の設計で、@JP が「以降の式はこの前提で読む」の印。
本体は 1 行。読み下しは「全日を月ごとに束ね、各月の 25 日目を採る」。実行する。
$ node src/cli.ts payday.kairos --from 2026-07-01 --to 2026-11-01 2026-07-25 2026-08-25 2026-09-25 2026-10-25
動いた。そして間違っている。2026-07-25 は土曜、2026-10-25 は日曜。このまま本番に置くと、振込は動かず、月曜の朝に経理から電話が来る。
ステップ 2: 「営業日」を作る
営業日は土日でも祝日でもない日。そのまま書く。
@JP
holidays2026 = [2026-01-01, 2026-01-12, 2026-02-11, 2026-02-23, 2026-03-20,
2026-04-29, 2026-05-03, 2026-05-04, 2026-05-05, 2026-05-06,
2026-07-20, 2026-08-11, 2026-09-21, 2026-09-22, 2026-09-23,
2026-10-12, 2026-11-03, 2026-11-23] covering: 2026..2026
satSun = everyDay |> filter(d => weekday(d) == Sat or weekday(d) == Sun)
bizDay = everyDay \ (satSun | holidays2026)
三行の役割分担。
holidays2026— 祝日は計算で出せないのでデータで持ち込む。日付リテラルを並べるだけ。末尾のcovering: 2026..2026は「このデータは 2026 年の範囲について完全」という宣言(あとで効いてくる)。satSun— 全日から土日だけ濾したもの。bizDay— 全日から「土日と祝日の和」を引いたもの。\が差、|が和。内側から読む。
祝日 18 件のベタ書きが気になるかもしれない。実務では外部供給の仕組みに差し替えられるが、チュートリアルではデータが見えている方が分かりやすいのでこのまま行く。
ステップ 3: 寄せる
素朴版の末尾に 1 段足す。
everyDay |> within(month) |> nth(25) |> roll(Preceding, on: bizDay)
roll(Preceding, on: bizDay) は「点が営業日の上になければ、手前の営業日まで倒す」。すでに営業日ならそのまま。実行。
$ node src/cli.ts payday.kairos --from 2026-07-01 --to 2026-11-01 2026-07-24 2026-08-25 2026-09-25 2026-10-23 # 被覆サマリ # holidays2026 covering 2026-01-01..2026-12-31 残走路 61 日
7/25(土)が金曜の 7/24 へ、10/25(日)が金曜の 10/23 へ。8 月と 9 月の 25 日は平日なのでそのまま。完成。
頼んでいないのに教えてくれる行
出力の下に見慣れない 2 行が付いている。
# 被覆サマリ # holidays2026 covering 2026-01-01..2026-12-31 残走路 61 日
「祝日データは 2026 年末まで。評価範囲の終端からあと 61 日で尽きる」。頼んでいないのに言ってくる。頼んでいないが、ありがたい。
前回書いた「黙って壊れない」の入口がこれ。祝日テーブルをコードに埋めた if 文は、データが尽きたことを教えてくれない。Kairos ではデータの端が運用の数字として常に出力に並走する。この数字を監視に流し込めば「祝日データの更新を忘れて年明けに事故る」が構造的に消える。詳しくは別の回で。
一語で別の業務になる
ところで Preceding を Following に変えると何が起きるか。
everyDay |> within(month) |> nth(25) |> roll(Following, on: bizDay)
2026-07-27 2026-08-25 2026-09-25 2026-10-26
「25 日、休日なら翌営業日」——請求書の支払期日の定番形。給料は前倒し、支払は後ろ倒し。どちらへ倒すかは文法ではなく業務が決める。ちなみに給料日をうっかり Following で書くと、7 月の給料が 27 日に出る。社内の空気は保証しない。
まとめ
- 式は「生成 → 窓 → 選択 → 変換」のパイプで組む:
everyDay |> within(month) |> nth(25) |> roll(…) - 祝日はデータ。
coveringで範囲を宣言すると、残走路が運用の数字として出てくる Preceding/Followingの一語が業務の意味を分ける
完成形はリポジトリの examples/payday.kairos に置いてある。仕様書の代表例 §7.4 も同じ題材。
次回は予定を変えて小ネタを一本。「cron で『13 日の金曜日』を書くと何が起きるか」——知っている人はニヤリとするやつ。

コメント