Kairos チュートリアル——給料日ルールをゼロから書く

Kairos

今回やること

前回は「作った」という話だけだった。今回は手を動かす。題材は給与計算の定番、

毎月 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 で範囲を宣言すると、残走路が運用の数字として出てくる
  • PrecedingFollowing の一語が業務の意味を分ける

完成形はリポジトリの examples/payday.kairos に置いてある。仕様書の代表例 §7.4 も同じ題材。

次回は予定を変えて小ネタを一本。「cron で『13 日の金曜日』を書くと何が起きるか」——知っている人はニヤリとするやつ。

コメント

タイトルとURLをコピーしました