diff --git a/lib/haskell/map.sx b/lib/haskell/map.sx new file mode 100644 index 00000000..3bec9ec9 --- /dev/null +++ b/lib/haskell/map.sx @@ -0,0 +1,134 @@ +;; map.sx — Phase 11 Data.Map: weight-balanced BST in pure SX. +;; +;; Algorithm: Adams's weight-balanced tree (the same family as Haskell's +;; Data.Map). Each node tracks its size; rotations maintain the invariant +;; +;; size(small-side) * delta >= size(large-side) (delta = 3) +;; +;; with single or double rotations chosen by the gamma ratio (gamma = 2). +;; The size field is an Int and is included so `size`, `lookup`, etc. are +;; O(log n) on both extremes of the tree. +;; +;; Representation: +;; Empty → ("Map-Empty") +;; Node → ("Map-Node" key val left right size) +;; +;; All operations are pure SX — no mutation of nodes once constructed. +;; The user-facing Haskell layer (Phase 11 next iteration) wraps these +;; for `import Data.Map as Map`. + +;; ── Constructors ──────────────────────────────────────────── +(define hk-map-empty (list "Map-Empty")) + +(define + hk-map-node + (fn + (k v l r) + (list "Map-Node" k v l r (+ 1 (+ (hk-map-size l) (hk-map-size r)))))) + +;; ── Predicates and accessors ──────────────────────────────── +(define hk-map-empty? (fn (m) (and (list? m) (= (first m) "Map-Empty")))) + +(define hk-map-node? (fn (m) (and (list? m) (= (first m) "Map-Node")))) + +(define + hk-map-size + (fn (m) (cond ((hk-map-empty? m) 0) (:else (nth m 5))))) + +(define hk-map-key (fn (m) (nth m 1))) +(define hk-map-val (fn (m) (nth m 2))) +(define hk-map-left (fn (m) (nth m 3))) +(define hk-map-right (fn (m) (nth m 4))) + +;; ── Weight-balanced rotations ─────────────────────────────── +;; delta and gamma per Adams 1992 / Haskell Data.Map. + +(define hk-map-delta 3) +(define hk-map-gamma 2) + +(define + hk-map-single-l + (fn + (k v l r) + (let + ((rk (hk-map-key r)) + (rv (hk-map-val r)) + (rl (hk-map-left r)) + (rr (hk-map-right r))) + (hk-map-node rk rv (hk-map-node k v l rl) rr)))) + +(define + hk-map-single-r + (fn + (k v l r) + (let + ((lk (hk-map-key l)) + (lv (hk-map-val l)) + (ll (hk-map-left l)) + (lr (hk-map-right l))) + (hk-map-node lk lv ll (hk-map-node k v lr r))))) + +(define + hk-map-double-l + (fn + (k v l r) + (let + ((rk (hk-map-key r)) + (rv (hk-map-val r)) + (rl (hk-map-left r)) + (rr (hk-map-right r)) + (rlk (hk-map-key (hk-map-left r))) + (rlv (hk-map-val (hk-map-left r))) + (rll (hk-map-left (hk-map-left r))) + (rlr (hk-map-right (hk-map-left r)))) + (hk-map-node + rlk + rlv + (hk-map-node k v l rll) + (hk-map-node rk rv rlr rr))))) + +(define + hk-map-double-r + (fn + (k v l r) + (let + ((lk (hk-map-key l)) + (lv (hk-map-val l)) + (ll (hk-map-left l)) + (lr (hk-map-right l)) + (lrk (hk-map-key (hk-map-right l))) + (lrv (hk-map-val (hk-map-right l))) + (lrl (hk-map-left (hk-map-right l))) + (lrr (hk-map-right (hk-map-right l)))) + (hk-map-node + lrk + lrv + (hk-map-node lk lv ll lrl) + (hk-map-node k v lrr r))))) + +;; ── Balanced node constructor ────────────────────────────── +;; Use this in place of hk-map-node when one side may have grown +;; or shrunk by one and we need to restore the weight invariant. +(define + hk-map-balance + (fn + (k v l r) + (let + ((sl (hk-map-size l)) (sr (hk-map-size r))) + (cond + ((<= (+ sl sr) 1) (hk-map-node k v l r)) + ((> sr (* hk-map-delta sl)) + (let + ((rl (hk-map-left r)) (rr (hk-map-right r))) + (cond + ((< (hk-map-size rl) (* hk-map-gamma (hk-map-size rr))) + (hk-map-single-l k v l r)) + (:else (hk-map-double-l k v l r))))) + ((> sl (* hk-map-delta sr)) + (let + ((ll (hk-map-left l)) (lr (hk-map-right l))) + (cond + ((< (hk-map-size lr) (* hk-map-gamma (hk-map-size ll))) + (hk-map-single-r k v l r)) + (:else (hk-map-double-r k v l r))))) + (:else (hk-map-node k v l r)))))) diff --git a/plans/haskell-completeness.md b/plans/haskell-completeness.md index 1f3d7213..42238018 100644 --- a/plans/haskell-completeness.md +++ b/plans/haskell-completeness.md @@ -179,7 +179,7 @@ No OCaml changes are needed. The view type is fully representable as an SX dict. ### Phase 11 — Data.Map -- [ ] Implement a weight-balanced BST in pure SX in `lib/haskell/map.sx`. +- [x] Implement a weight-balanced BST in pure SX in `lib/haskell/map.sx`. Internal node representation: `("Map-Node" key val left right size)`. Leaf: `("Map-Empty")`. - [ ] Core operations: `empty`, `singleton`, `insert`, `lookup`, `delete`, @@ -304,6 +304,13 @@ No OCaml changes are needed. The view type is fully representable as an SX dict. _Newest first._ +**2026-05-07** — Phase 11 BST skeleton in `lib/haskell/map.sx`: +- Adams-style weight-balanced tree: node = `("Map-Node" k v l r size)`, + empty = `("Map-Empty")`. delta=3 / gamma=2 ratios. Implemented constructors + + accessors + the four rotations (single-l, single-r, double-l, double-r) + + `hk-map-balance` smart constructor that picks the rotation. Spot-checked + with eval calls; user-facing operations (insert/lookup/etc.) come next. + **2026-05-07** — Phase 10 conformance: statistics.hs (5/5) + newton.hs (5/5) → Phase 10 complete: - `program-statistics.sx`: mean / variance / stdDev on a [Double], exercising `sum`, `map`, `fromIntegral`, `/`, `sqrt`. 5/5.