diff --git a/lib/haskell/conformance.sh b/lib/haskell/conformance.sh index 97d449ec..0c34a35c 100755 --- a/lib/haskell/conformance.sh +++ b/lib/haskell/conformance.sh @@ -20,7 +20,7 @@ if [ ! -x "$SX_SERVER" ]; then fi fi -PROGRAMS=(fib sieve quicksort nqueens calculator collatz palindrome maybe fizzbuzz anagram roman binary either primes zipwith matrix wordcount powers caesar runlength-str showadt showio partial statistics newton) +PROGRAMS=(fib sieve quicksort nqueens calculator collatz palindrome maybe fizzbuzz anagram roman binary either primes zipwith matrix wordcount powers caesar runlength-str showadt showio partial statistics newton wordfreq mapgraph) PASS_COUNTS=() FAIL_COUNTS=() diff --git a/lib/haskell/eval.sx b/lib/haskell/eval.sx index 5fc53f0e..bcf9087f 100644 --- a/lib/haskell/eval.sx +++ b/lib/haskell/eval.sx @@ -1083,7 +1083,45 @@ (hk-mk-lazy-builtin "Map.delete" (fn (k m) (hk-map-delete (hk-force k) (hk-force m))) - 2)))))) + 2)) + (dict-set! + env + (str p "insertWith") + (hk-mk-lazy-builtin + "Map.insertWith" + (fn + (f k v m) + (hk-map-insert-with + (fn (a b) (hk-force (hk-apply (hk-apply f a) b))) + (hk-force k) + (hk-force v) + (hk-force m))) + 4)) + (dict-set! + env + (str p "adjust") + (hk-mk-lazy-builtin + "Map.adjust" + (fn + (f k m) + (hk-map-adjust + (fn (v) (hk-force (hk-apply f v))) + (hk-force k) + (hk-force m))) + 3)) + (dict-set! + env + (str p "findWithDefault") + (hk-mk-lazy-builtin + "Map.findWithDefault" + (fn + (d k m) + (let + ((res (hk-map-lookup (hk-force k) (hk-force m)))) + (cond + ((= (first res) "Just") (nth res 1)) + (:else (hk-force d))))) + 3)))))) (define hk-bind-decls! diff --git a/lib/haskell/tests/program-mapgraph.sx b/lib/haskell/tests/program-mapgraph.sx new file mode 100644 index 00000000..dfec90aa --- /dev/null +++ b/lib/haskell/tests/program-mapgraph.sx @@ -0,0 +1,46 @@ +;; mapgraph.hs — adjacency-list using Data.Map (BFS-style traversal). +;; +;; Exercises Phase 11: `import qualified Data.Map as Map`, `Map.empty`, +;; `Map.insert`, `Map.lookup`, `Map.findWithDefault`. Adjacency lists are +;; stored as `Map Int [Int]`; `neighbors` does a default-empty lookup. + +(define + hk-mapgraph-source + "import qualified Data.Map as Map\n\nemptyG = Map.empty\n\naddEdge u v g = Map.insertWith add u [v] g\n where add new old = new ++ old\n\nbuild = addEdge 1 2 (addEdge 1 3 (addEdge 2 4 (addEdge 3 4 (addEdge 4 5 emptyG))))\n\nneighbors n g = Map.findWithDefault [] n g\n") + +(hk-test + "mapgraph.hs — neighbors of 1" + (hk-deep-force + (hk-run (str hk-mapgraph-source "main = neighbors 1 build\n"))) + (list ":" 2 (list ":" 3 (list "[]")))) + +(hk-test + "mapgraph.hs — neighbors of 4" + (hk-deep-force + (hk-run (str hk-mapgraph-source "main = neighbors 4 build\n"))) + (list ":" 5 (list "[]"))) + +(hk-test + "mapgraph.hs — neighbors of 5 (leaf, no entry) defaults to []" + (hk-deep-force + (hk-run (str hk-mapgraph-source "main = neighbors 5 build\n"))) + (list "[]")) + +(hk-test + "mapgraph.hs — neighbors of 99 (absent) defaults to []" + (hk-deep-force + (hk-run (str hk-mapgraph-source "main = neighbors 99 build\n"))) + (list "[]")) + +(hk-test + "mapgraph.hs — Map.member 1" + (hk-deep-force + (hk-run (str hk-mapgraph-source "main = Map.member 1 build\n"))) + (list "True")) + +(hk-test + "mapgraph.hs — Map.size = 4 source nodes" + (hk-deep-force (hk-run (str hk-mapgraph-source "main = Map.size build\n"))) + 4) + +{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail} diff --git a/lib/haskell/tests/program-wordfreq.sx b/lib/haskell/tests/program-wordfreq.sx new file mode 100644 index 00000000..36bb589a --- /dev/null +++ b/lib/haskell/tests/program-wordfreq.sx @@ -0,0 +1,54 @@ +;; wordfreq.hs — word-frequency histogram using Data.Map. +;; Source: Rosetta Code "Word frequency" (Haskell entry, simplified). +;; +;; Exercises Phase 11: `import qualified Data.Map as Map`, `Map.empty`, +;; `Map.insertWith`, `Map.lookup`, `Map.findWithDefault`, `foldl`. + +(define + hk-wordfreq-source + "import qualified Data.Map as Map\n\ncountWord m w = Map.insertWith (+) w 1 m\n\nwordFreq xs = foldl countWord Map.empty xs\n\nresult = wordFreq [\"the\", \"cat\", \"the\", \"dog\", \"the\", \"cat\"]\n") + +(hk-test + "wordfreq.hs — \"the\" counted 3 times" + (hk-deep-force + (hk-run (str hk-wordfreq-source "main = Map.lookup \"the\" result\n"))) + (list "Just" 3)) + +(hk-test + "wordfreq.hs — \"cat\" counted 2 times" + (hk-deep-force + (hk-run (str hk-wordfreq-source "main = Map.lookup \"cat\" result\n"))) + (list "Just" 2)) + +(hk-test + "wordfreq.hs — \"dog\" counted 1 time" + (hk-deep-force + (hk-run (str hk-wordfreq-source "main = Map.lookup \"dog\" result\n"))) + (list "Just" 1)) + +(hk-test + "wordfreq.hs — \"missing\" not present" + (hk-deep-force + (hk-run (str hk-wordfreq-source "main = Map.lookup \"missing\" result\n"))) + (list "Nothing")) + +(hk-test + "wordfreq.hs — Map.size = 3 unique words" + (hk-deep-force (hk-run (str hk-wordfreq-source "main = Map.size result\n"))) + 3) + +(hk-test + "wordfreq.hs — findWithDefault for missing returns 0" + (hk-deep-force + (hk-run + (str hk-wordfreq-source "main = Map.findWithDefault 0 \"absent\" result\n"))) + 0) + +(hk-test + "wordfreq.hs — findWithDefault for present returns count" + (hk-deep-force + (hk-run + (str hk-wordfreq-source "main = Map.findWithDefault 0 \"the\" result\n"))) + 3) + +{: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 48477635..391886ec 100644 --- a/plans/haskell-completeness.md +++ b/plans/haskell-completeness.md @@ -196,7 +196,7 @@ No OCaml changes are needed. The view type is fully representable as an SX dict. 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: +- [x] Conformance programs: - `wordfreq.hs` — word-frequency histogram using `Data.Map`. Source from Rosetta Code "Word frequency" Haskell entry. - `mapgraph.hs` — adjacency-list BFS using `Data.Map`. @@ -307,6 +307,14 @@ No OCaml changes are needed. The view type is fully representable as an SX dict. _Newest first._ +**2026-05-07** — Phase 11 conformance: wordfreq.hs (7/7) + mapgraph.hs (6/6) → Phase 11 complete: +- Extended `hk-bind-data-map!` with `Map.insertWith`, `Map.adjust`, and + `Map.findWithDefault` so the conformance programs have what they need. +- `program-wordfreq.sx`: word-frequency histogram, `foldl Map.insertWith Map.empty`. +- `program-mapgraph.sx`: adjacency list, `Map.findWithDefault [] n g` for + default-empty neighbors. +- Both added to `PROGRAMS` in `conformance.sh`. Phase 11 fully complete. + **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/