日本語 · English

Kairos を書く——要件から定義へ(対象: 参照実装 1.0.12)

この手引きは、要件(自然文)を Kairos の定義に写すときの手順です。読み手は人でも AI でもかまいません。 言語の紹介は llms.txt、語ごとの正確な意味は 記述語リファレンス、要件から引く実例は レシピ集 にあります。ここには「どう書くか」だけを書きます。 コード例はすべて参照実装で実行検証されています(# eval: が評価範囲、#=> がその結果)。版が変われば本書も改訂されます。

0. 手順(5 歩)

  1. 要件を「いつ」の列として言い直す。点の列(日・瞬間)か、窓(月・週)かを決める。
  2. 前文(premise)を書く。暦法・時間帯・週の始まり・祝日の表。
  3. 本体を書く。§2 の型から選び、|> で段を繋ぎ、束縛で名前を付ける。
  4. 検算する。validate → list で範囲を切る → 出た点を人が見る(§5)。
  5. 出口を決める。list・next・--json・--tsv・--ics。

1. 形

定義は、前文、@名前、束縛 0 個以上、本体式の順です。

premise JP {
  calendar-system: Gregorian
  tz: "Asia/Tokyo"
  wkst: Mon
  holidays = [2026-01-01, 2026-01-12, 2026-02-11] covering: 2026..2026
  satSun   = everyDay |> filter(d => weekday(d) == Sat or weekday(d) == Sun)
  bizDay   = everyDay \ (satSun | holidays)
}

@JP
payday = everyDay |> filter(d => dayNo(d) == 25)
payday |> roll(Preceding, on: bizDay)

2. 要件の型と式の型

以下の例は、標準の前文(@JP=東京・月曜始まり・2026 年の祝日表 holidays2026・bizDay=土日祝を除く日)で動きます。 評価範囲の先頭で roll が表の始まりより前を見ると「範囲外」の註釈が立つので、例の範囲は表の少し内側から始めています。

2-1. 毎週の曜日

# eval: 2026-01-01..2026-02-01
@JP
everyDay |> filter(d => weekday(d) == Tue)
#=> 2026-01-06 2026-01-13 2026-01-20 2026-01-27

2-2. 固定日。休日なら前営業日(翌営業日なら Following)

# eval: 2026-01-05..2026-05-01
@JP
everyDay |> filter(d => dayNo(d) == 25) |> roll(Preceding, on: bizDay)
#=> 2026-01-23 2026-02-25 2026-03-25 2026-04-24

roll は「軸(on:)に無ければ手前(Preceding)か先(Following)の点へ寄せる」。規約は省略できない。

2-3. 月末の N 営業日前(先に roll、次に shift)

# eval: 2026-01-05..2026-03-01
@JP
monthEnd |> roll(Preceding, on: bizDay) |> shift(-3, unit: bizDay)
#=> 2026-01-27 2026-02-24

shift は軸の目盛りで数える平行移動。軸上に無い点には使えないので、月末のような「休日かもしれない点」は先に roll で軸に乗せる。

2-4. 第 N 曜日・第 N 営業日(within(month) |> nth(N))

# eval: 2026-01-01..2026-04-01
@JP
everyDay |> filter(d => weekday(d) == Wed) |> within(month) |> nth(2)
#=> 2026-01-14 2026-02-11 2026-03-11
# eval: 2026-01-01..2026-04-01
@JP
bizDay |> within(month) |> nth(3)
#=> 2026-01-06 2026-02-04 2026-03-04

無い月(第 5 水曜が無い月、営業日が N 日未満の月)は正当な空で、エラーにならない。

2-5. 月 2 回(和で束ねる)

# eval: 2026-01-01..2026-03-01
@JP
wed = everyDay |> filter(d => weekday(d) == Wed)
(wed |> within(month) |> nth(1)) | (wed |> within(month) |> nth(3))
#=> 2026-01-07 2026-01-21 2026-02-04 2026-02-18
# eval: 2026-01-05..2026-03-01
@JP
d15 = everyDay |> filter(d => dayNo(d) == 15)
(d15 | monthEnd) |> roll(Preceding, on: bizDay)
#=> 2026-01-15 2026-01-30 2026-02-13 2026-02-27

束ねてから丸めるので、roll は 1 回でよい。

2-6. 隔週(stride。位相は from: の実日が持つ)

# eval: 2026-01-01..2026-03-01
@JP
everyDay |> filter(d => weekday(d) == Mon) |> stride(2, from: 2026-01-05)
#=> 2026-01-05 2026-01-19 2026-02-02 2026-02-16

2-7. 毎週・月 4 回・第 5 週は休み

# eval: 2026-03-01..2026-04-01
@JP
tue = everyDay |> filter(d => weekday(d) == Tue)
tue \ (tue |> within(month) |> nth(5))
#=> 2026-03-03 2026-03-10 2026-03-17 2026-03-24

第 5 回目は必ず 29 日以降なので、tue |> filter(d => dayNo(d) <= 28) でも同じ列になる。

2-8. 除く(期間・祝日は表に書き、\ で引く)

# eval: 2026-07-20..2026-09-10
@JP
tue = everyDay |> filter(d => weekday(d) == Tue)
closed = [2026-08-01..2026-08-31, 2026-12-29..2026-12-31] covering: 2026..2026
tue \ (closed | holidays2026)
#=> 2026-07-21 2026-07-28 2026-09-01 2026-09-08

休みの期間は規則ではなく事実なので、表に書く。表には covering: を添える。

2-9. 旬(1 日・11 日・21 日)。月に従う区切りは日番号で書く

# eval: 2026-01-01..2026-03-01
@JP
everyDay |> filter(d => dayNo(d) == 1 or dayNo(d) == 11 or dayNo(d) == 21)
#=> 2026-01-01 2026-01-11 2026-01-21 2026-02-01 2026-02-11 2026-02-21

「10 日ごと」と言い換えて等幅の窓で書いてはいけない(月は等幅ではない)。

2-10. 四半期・年度

# eval: 2026-01-01..2026-07-01
@JP
bizDay |> within(quarter) |> last
#=> 2026-03-31 2026-06-30
# eval: 2026-01-05..2027-01-01
@JP
monthEnd |> filter(d => monthNo(d) == 3) |> roll(Preceding, on: bizDay)
#=> 2026-03-31

2-11. 時刻(at)。複数の時刻は和で

# eval: 2026-01-01..2026-01-20
@JP
mon = everyDay |> filter(d => weekday(d) == Mon)
(mon |> at(T09:00)) | (mon |> at(T15:00))
#=> 2026-01-05T09:00 2026-01-05T15:00 2026-01-12T09:00 2026-01-12T15:00 2026-01-19T09:00 2026-01-19T15:00

2-12. 全 N 回(take。除外してから数えるので、休講が出ても N 回になる)

# eval: 2026-04-01..2026-08-01
@JP
lessons   = everyDay |> filter(d => weekday(d) == Tue)
cancelled = [2026-04-14, 2026-05-05] covering: 2026-04-01..2026-07-01
(lessons \ cancelled) |> take(5, from: 2026-04-01)
#=> 2026-04-07 2026-04-21 2026-04-28 2026-05-12 2026-05-19

2-13. 周期勤務(cycle。ラベルを貼り、述語で読む)

# eval: 2026-01-05..2026-01-21
@JP
roster = day cycle [On, On, On, On, Off, Off, Off, Off] anchor: 2026-01-05
everyDay |> filter(d => roster(d) == On)
#=> 2026-01-05 2026-01-06 2026-01-07 2026-01-08 2026-01-13 2026-01-14 2026-01-15 2026-01-16

anchor: は位相(その日が先頭ラベル)。班を増やすときはリストを回さず anchor: を実際の初日にする。

2-14. 数える(残り営業日など)。列を範囲で切って、出た点を数える

# eval: 2026-10-26..2026-11-04
@JP
bizDay
#=> 2026-10-26 2026-10-27 2026-10-28 2026-10-29 2026-10-30 2026-11-02

Kairos に「残りを返す」語は無い。範囲 [from, to) は右端を含まないので、当日を含めるかは --to を 1 日ずらして決める。

3. 書いてはいけない形と直し方

書きがちな形 何が起きるか 直し方
… |> within(month) |> take(4, …) 静的エラー(take は通し数え) nth(1)〜nth(4) の和、または dayNo(d) <= 28
monthEnd |> shift(-3, unit: bizDay) エラー(点が軸上にない) 先に roll(Preceding, on: bizDay)
… |> roll(on: bizDay) エラー(規約が要る) roll(Preceding, …) か roll(Following, …)
everyDay |> filter(d => dayNo(d) == 31) 30 日の月に出ない monthEnd
chronos grid 30d で月を作る 月の境とずれる month(標準の窓)
「10 日ごと」を stride(10, …) 旬の初日と一致しない dayNo(d) == 1 or dayNo(d) == 11 or dayNo(d) == 21
shift(+19.5, unit: hour) エラー(整数でない) at(T19:30)
束縛を shift や weekday と名付ける core 語の再定義は静的エラー。公開語は意味が変わる 別の名前
祝日を式に直書き(dayNo(d) == 3 and monthNo(d) == 11) 年で腐る。振替休日が出ない 表に書き covering: を添える
「今年の 4 月から」を定義に書く 来年も 4 月から始まる 評価範囲で切る

4. 診断の読み方

診断は「語: 何が悪いか(直し方。根拠)」の形で、括弧の中が直し方です。参照実装はメッセージを出して終了コード 1 で止まり、結果を出しません。 誤った日付が黙って出ることは、言語の設計上ありません。

shift: 点が軸上にない(先に roll で有効点へ寄せる)
roll は規約が必要(I3)——位置引数で Following/Preceding を書くか前文で roll: を宣言
roll: 軸がない(on:/unit: を書くか前文で axis: を宣言。§3.3 は宣言必須)
take: 窓付き入力は取らない——「窓ごとの先頭 N」は within の後の nth(第 N)か ordinalIn の述語で(take は通し数え。ADR-49)
shift: n は整数(19.5 は不可。方向は符号で表す。§5.2)
未解決の名前: wekday(premise 相対解決 §3.4)
core 語 shift の再定義は静的エラー(§4.8)

名前の綴りを間違えると「未解決の名前」で止まる。語を発明せず、§6 の表にある語だけを使う。

警告は結果と一緒に出ます。window-clip は「評価範囲の外へ動いた点は出力に含まれない」。範囲を広げて評価し直します。

5. 検算

書いたら必ず動かして、点を人が見ます。

$ kairos validate payday.kairos
$ kairos list --from 2026-01-01 --to 2026-07-01 payday.kairos
2026-01-23
…
# 被覆サマリ
#   JP.holidays covering 2026-01-01..2026-12-31 残走路 183 日

6. 語彙(短い表)

族 語
生成子 everyDay・everyInstant
窓(標準の前文) day・week・month・quarter・year・monthStart・monthEnd・yearStart・weekStart
窓の区切り within(窓)・segmentBy
選択子 first・last・nth(N)
点変換 roll(規約, on: 軸)・shift(±N, unit: 軸)・at(T時刻)・snapTo・rebase・rephase
フィルタ filter(d => 述語)・filter(on: 列)
結合子 |・&・\
ストライド stride(N, from:)・strideBy(幅, from:)・take(N, from:)・takeLast(N, until:)
値関数 weekday(d)・dayNo(d)・monthNo(d)・yearNo(d)・daysInMonthOf(d)・ordinalIn(u, w, d)・epochOrdinal(u, d)・isOpen(t)
前文 premise・calendar-system・tz・wkst・calendar・nonWorking・covering:・labels:・external(kind:)・cycle・with

語ごとの意味と落とし穴は 記述語リファレンス。

7. 書かないこと