recurrence.bend source
recurrence.bend on the hub · documented module
# Pure recurrence on arbitrary-precision Nat ticks: no wrapping or U32 conversion.import Basetype Missed is Data: Skip{} CatchUp{bound: Nat}type Cadence is Data: FixedDelay{} FixedRate{policy: Missed}type Plan is Data: Planned{replay: List<&2, Nat>, next: Nat}def replay(p: Plan) -> List<&2, Nat>: match p: case Planned{ticks, next}: ticksdef next(p: Plan) -> Nat: match p: case Planned{ticks, next}: next# Generate only the bounded replay prefix, never the skipped remainder.def ticks(count: Nat, +previous: Nat, +period: Nat) -> List<&2, Nat>: match count: case 0n: Nil{} case 1n+n: +deadline = Nat.add(previous, period) Con{deadline, ticks(n, deadline, period)}def limit(policy: Missed, overdue: Nat) -> Nat: match policy: case Skip{}: 0n case CatchUp{bound}: Nat.min(bound, overdue)# A tick equal to completion is overdue. The already-completed previous tick# is excluded. A positive period keeps all arithmetic exact, without overflow.def rate(+previous: Nat, +completion: Nat, +period: Nat, policy: Missed) -> Plan: +overdue = Nat.div(Nat.sub(completion, previous), period) Planned{ticks(limit(policy, overdue), previous, period), Nat.add(previous, Nat.mul(1n+overdue, period))}def positive(cadence: Cadence, +previous: Nat, +completion: Nat, +period: Nat) -> Plan: match cadence: case FixedDelay{}: Planned{Nil{}, Nat.add(completion, period)} case FixedRate{policy}: rate(previous, completion, period, policy)# None is an explicit invalid-zero-period result, not a zero cadence.def plan(previous: Nat, completion: Nat, period: Nat, cadence: Cadence) -> Maybe<&2, Plan>: match period: case 0n: None{} case 1n+p: Some{positive(cadence, previous, completion, 1n+p)}