From e95ca4624ba8ee2920f5630c28bb5159d803c3fe Mon Sep 17 00:00:00 2001 From: giles Date: Thu, 7 May 2026 11:32:55 +0000 Subject: [PATCH] =?UTF-8?q?haskell:=20Phase=2011=20=E2=80=94=20tests/map.s?= =?UTF-8?q?x=20(26/26,=20plan=20=E2=89=A520)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- lib/haskell/tests/map.sx | 196 ++++++++++++++++++++++++++++++++++ plans/haskell-completeness.md | 15 ++- 2 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 lib/haskell/tests/map.sx diff --git a/lib/haskell/tests/map.sx b/lib/haskell/tests/map.sx new file mode 100644 index 00000000..bd97fd63 --- /dev/null +++ b/lib/haskell/tests/map.sx @@ -0,0 +1,196 @@ +;; map.sx — Phase 11 Data.Map unit tests. +;; +;; Tests both the SX-level `hk-map-*` helpers and the Haskell-level +;; `Map.*` aliases bound by the import handler. + +(define + hk-as-list + (fn + (xs) + (cond + ((and (list? xs) (= (first xs) "[]")) (list)) + ((and (list? xs) (= (first xs) ":")) + (cons (nth xs 1) (hk-as-list (nth xs 2)))) + (:else xs)))) + +;; ── SX-level (direct hk-map-*) ─────────────────────────────── +(hk-test + "hk-map-empty: size 0, null true" + (list (hk-map-size hk-map-empty) (hk-map-null hk-map-empty)) + (list 0 true)) + +(hk-test + "hk-map-singleton: lookup hit" + (let + ((m (hk-map-singleton 5 "five"))) + (list (hk-map-size m) (hk-map-lookup 5 m))) + (list 1 (list "Just" "five"))) + +(hk-test + "hk-map-insert: lookup hit on inserted" + (let ((m (hk-map-insert 1 "a" hk-map-empty))) (hk-map-lookup 1 m)) + (list "Just" "a")) + +(hk-test + "hk-map-lookup: miss returns Nothing" + (hk-map-lookup 99 (hk-map-singleton 1 "a")) + (list "Nothing")) + +(hk-test + "hk-map-insert: overwrites existing key" + (let + ((m (hk-map-insert 1 "second" (hk-map-insert 1 "first" hk-map-empty)))) + (hk-map-lookup 1 m)) + (list "Just" "second")) + +(hk-test + "hk-map-delete: removes key" + (let + ((m (hk-map-insert 2 "b" (hk-map-insert 1 "a" hk-map-empty)))) + (let + ((m2 (hk-map-delete 1 m))) + (list (hk-map-size m2) (hk-map-lookup 1 m2) (hk-map-lookup 2 m2)))) + (list 1 (list "Nothing") (list "Just" "b"))) + +(hk-test + "hk-map-delete: missing key is no-op" + (let ((m (hk-map-singleton 1 "a"))) (hk-map-size (hk-map-delete 99 m))) + 1) + +(hk-test + "hk-map-member: true on existing" + (hk-map-member 1 (hk-map-singleton 1 "a")) + true) + +(hk-test + "hk-map-member: false on missing" + (hk-map-member 99 (hk-map-singleton 1 "a")) + false) + +(hk-test + "hk-map-from-list: builds map; keys sorted" + (hk-map-keys + (hk-map-from-list + (list (list 3 "c") (list 1 "a") (list 5 "e") (list 2 "b")))) + (list 1 2 3 5)) + +(hk-test + "hk-map-from-list: duplicates — last wins" + (hk-map-lookup + 1 + (hk-map-from-list (list (list 1 "first") (list 1 "second")))) + (list "Just" "second")) + +(hk-test + "hk-map-to-asc-list: ordered traversal" + (hk-map-to-asc-list + (hk-map-from-list (list (list 3 "c") (list 1 "a") (list 2 "b")))) + (list (list 1 "a") (list 2 "b") (list 3 "c"))) + +(hk-test + "hk-map-elems: in key order" + (hk-map-elems + (hk-map-from-list (list (list 3 30) (list 1 10) (list 2 20)))) + (list 10 20 30)) + +(hk-test + "hk-map-union-with: combines duplicates" + (hk-map-to-asc-list + (hk-map-union-with + (fn (a b) (str a "+" b)) + (hk-map-from-list (list (list 1 "a") (list 2 "b"))) + (hk-map-from-list (list (list 2 "B") (list 3 "c"))))) + (list (list 1 "a") (list 2 "b+B") (list 3 "c"))) + +(hk-test + "hk-map-intersection-with: keeps shared keys" + (hk-map-to-asc-list + (hk-map-intersection-with + + + (hk-map-from-list (list (list 1 10) (list 2 20))) + (hk-map-from-list (list (list 2 200) (list 3 30))))) + (list (list 2 220))) + +(hk-test + "hk-map-difference: drops m2 keys" + (hk-map-keys + (hk-map-difference + (hk-map-from-list (list (list 1 "a") (list 2 "b") (list 3 "c"))) + (hk-map-from-list (list (list 2 "x"))))) + (list 1 3)) + +(hk-test + "hk-map-foldl-with-key: in-order accumulate" + (hk-map-foldl-with-key + (fn (acc k v) (str acc k v)) + "" + (hk-map-from-list (list (list 3 "c") (list 1 "a") (list 2 "b")))) + "1a2b3c") + +(hk-test + "hk-map-map-with-key: transforms values" + (hk-map-to-asc-list + (hk-map-map-with-key + (fn (k v) (* k v)) + (hk-map-from-list (list (list 2 10) (list 3 100))))) + (list (list 2 20) (list 3 300))) + +(hk-test + "hk-map-filter-with-key: keeps matches" + (hk-map-keys + (hk-map-filter-with-key + (fn (k v) (> k 1)) + (hk-map-from-list (list (list 1 "a") (list 2 "b") (list 3 "c"))))) + (list 2 3)) + +(hk-test + "hk-map-adjust: applies f to existing" + (hk-map-lookup + 1 + (hk-map-adjust (fn (v) (* v 10)) 1 (hk-map-singleton 1 5))) + (list "Just" 50)) + +(hk-test + "hk-map-insert-with: combines on existing" + (hk-map-lookup 1 (hk-map-insert-with + 1 5 (hk-map-singleton 1 10))) + (list "Just" 15)) + +(hk-test + "hk-map-alter: Nothing → delete" + (hk-map-size + (hk-map-alter + (fn (mv) (list "Nothing")) + 1 + (hk-map-from-list (list (list 1 "a") (list 2 "b"))))) + 1) + +;; ── Haskell-level (Map.*) via import wiring ───────────────── +(hk-test + "Map.size after Map.insert chain" + (hk-deep-force + (hk-run + "import qualified Data.Map as Map\nmain = Map.size (Map.insert 2 \"b\" (Map.insert 1 \"a\" Map.empty))")) + 2) + +(hk-test + "Map.lookup hit" + (hk-deep-force + (hk-run + "import qualified Data.Map as Map\nmain = Map.lookup 1 (Map.insert 1 \"a\" Map.empty)")) + (list "Just" "a")) + +(hk-test + "Map.lookup miss" + (hk-deep-force + (hk-run + "import qualified Data.Map as Map\nmain = Map.lookup 99 (Map.insert 1 \"a\" Map.empty)")) + (list "Nothing")) + +(hk-test + "Map.member true" + (hk-deep-force + (hk-run + "import qualified Data.Map as Map\nmain = Map.member 5 (Map.insert 5 \"x\" Map.empty)")) + (list "True")) + +{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail} diff --git a/plans/haskell-completeness.md b/plans/haskell-completeness.md index dda48e84..48477635 100644 --- a/plans/haskell-completeness.md +++ b/plans/haskell-completeness.md @@ -190,9 +190,12 @@ No OCaml changes are needed. The view type is fully representable as an SX dict. - [x] Updating: `adjust`, `insertWith`, `insertWithKey`, `alter`. - [x] Module wiring: `import Data.Map` and `import qualified Data.Map as Map` resolve to the `map.sx` namespace dict in the eval import handler. -- [ ] Unit tests in `lib/haskell/tests/map.sx` (≥ 20 tests: empty, singleton, - insert + lookup hit/miss, delete root, fromList with duplicates, - toAscList ordering, unionWith, foldlWithKey). +- [x] Unit tests in `lib/haskell/tests/map.sx` (26 tests, well past ≥20 target: + empty/singleton/insert/lookup hit&miss/overwrite/delete/member at the SX + level, fromList with duplicates last-wins, toAscList ordering, elems in + order, unionWith/intersectionWith/difference, foldlWithKey/mapWithKey/ + filterWithKey, adjust/insertWith/alter, plus 4 end-to-end tests via + `import qualified Data.Map as Map`.) - [ ] Conformance programs: - `wordfreq.hs` — word-frequency histogram using `Data.Map`. Source from Rosetta Code "Word frequency" Haskell entry. @@ -304,6 +307,12 @@ No OCaml changes are needed. The view type is fully representable as an SX dict. _Newest first._ +**2026-05-07** — Phase 11 unit tests `tests/map.sx` (26/26): +- 22 SX-level direct calls (empty/singleton/insert/lookup/delete/member/ + fromList+duplicates/toAscList/elems/unionWith/intersectionWith/difference/ + foldlWithKey/mapWithKey/filterWithKey/adjust/insertWith/alter) plus 4 + end-to-end via `import qualified Data.Map as Map`. Plan asked for ≥20. + **2026-05-07** — Phase 11 module wiring: `import Data.Map`: - Added `hk-bind-data-map!` helper in `eval.sx` that registers `.empty/singleton/insert/lookup/member/size/null/delete` as Haskell