diff --git a/lib/haskell/eval.sx b/lib/haskell/eval.sx index a7cc2c86..a7fa0eff 100644 --- a/lib/haskell/eval.sx +++ b/lib/haskell/eval.sx @@ -496,7 +496,7 @@ ;; the recursive list-building functions. (define hk-prelude-src - "head (x:_) = x\ntail (_:xs) = xs\nfst (a, _) = a\nsnd (_, b) = b\ntake 0 _ = []\ntake _ [] = []\ntake n (x:xs) = x : take (n - 1) xs\ndrop 0 xs = xs\ndrop _ [] = []\ndrop n (_:xs) = drop (n - 1) xs\nrepeat x = x : repeat x\niterate f x = x : iterate f (f x)\nlength [] = 0\nlength (_:xs) = 1 + length xs\nmap _ [] = []\nmap f (x:xs) = f x : map f xs\nfilter _ [] = []\nfilter p (x:xs) = if p x then x : filter p xs else filter p xs\nzipWith _ [] _ = []\nzipWith _ _ [] = []\nzipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys\nfibs = 0 : 1 : zipWith plus fibs (tail fibs)\nplus a b = a + b\nconcat [] = []\nconcat (xs:xss) = xs ++ concat xss\nconcatMap f [] = []\nconcatMap f (x:xs) = f x ++ concatMap f xs\nabs x = if x < 0 then 0 - x else x\nnegate x = 0 - x\nnull [] = True\nnull _ = False\nflip f x y = f y x\nconst x _ = x\nid x = x\ncurry f x y = f (x, y)\nuncurry f p = f (fst p) (snd p)\nfoldr f z [] = z\nfoldr f z (x:xs) = f x (foldr f z xs)\nfoldl f z [] = z\nfoldl f z (x:xs) = foldl f (f z x) xs\nfoldl1 f (x:xs) = foldl f x xs\nfoldr1 f [x] = x\nfoldr1 f (x:xs) = f x (foldr1 f xs)\nzip [] _ = []\nzip _ [] = []\nzip (x:xs) (y:ys) = (x, y) : zip xs ys\nreverse [] = []\nreverse (x:xs) = reverse xs ++ [x]\nelem _ [] = False\nelem x (y:ys) = if x == y then True else elem x ys\nnotElem x xs = not (elem x xs)\nany _ [] = False\nany f (x:xs) = if f x then True else any f xs\nall _ [] = True\nall f (x:xs) = if f x then all f xs else False\nand [] = True\nand (x:xs) = if x then and xs else False\nor [] = False\nor (x:xs) = if x then True else or xs\nsum [] = 0\nsum (x:xs) = x + sum xs\nproduct [] = 1\nproduct (x:xs) = x * product xs\nmaximum [x] = x\nmaximum (x:xs) = let m = maximum xs in if x >= m then x else m\nminimum [x] = x\nminimum (x:xs) = let m = minimum xs in if x <= m then x else m\ncompare x y = if x < y then LT else if x == y then EQ else GT\nmin x y = if x <= y then x else y\nmax x y = if x >= y then x else y\nsignum x = if x < 0 then negate 1 else if x == 0 then 0 else 1\nfromIntegral x = x\nfromInteger x = x\ntoInteger x = x\nceiling x = x\nfloor x = x\nround x = x\ntruncate x = x\nlookup _ [] = Nothing\nlookup k ((k2,v):rest) = if k == k2 then Just v else lookup k rest\nmaybe d _ Nothing = d\nmaybe _ f (Just x) = f x\neither f _ (Left x) = f x\neither _ g (Right y) = g y\nmapMaybe _ [] = []\nmapMaybe f (x:xs) = case f x of { Nothing -> mapMaybe f xs; Just y -> y : mapMaybe f xs }\nfmap = map\npure = return\nwhen b m = if b then m else return ()\nunless b m = if b then return () else m\nmapM_ _ [] = return ()\nmapM_ f (x:xs) = f x >> mapM_ f xs\nsequence_ [] = return ()\nsequence_ (m:ms) = m >> sequence_ ms\ninteractApply f s = putStr (f s)\ninteract f = getContents >>= interactApply f\nnub [] = []\nnub (x:xs) = x : nub (filter notEqX xs)\n where notEqX y = y /= x\nsort [] = []\nsort (x:xs) = sort (filter ltX xs) ++ [x] ++ sort (filter geX xs)\n where ltX y = y < x\n geX y = y >= x\nsortBy _ [] = []\nsortBy cmp (x:xs) = sortBy cmp smaller ++ [x] ++ sortBy cmp bigger\n where smaller = filter ltCmp xs\n bigger = filter geCmp xs\n ltCmp y = cmp y x /= GT\n geCmp y = cmp y x == GT\nsortOnCmpFst p1 p2 = compare (fst p1) (fst p2)\nsortOn f xs = map snd (sortBy sortOnCmpFst (zip (map f xs) xs))\nsplitAt 0 xs = ([], xs)\nsplitAt _ [] = ([], [])\nsplitAt n (x:xs) = (x : a, b) where (a, b) = splitAt (n - 1) xs\nspan _ [] = ([], [])\nspan p (x:xs) = if p x then (x : a, b) else ([], x : xs) where (a, b) = span p xs\nbreak p xs = span notP xs\n where notP y = not (p y)\npartition _ [] = ([], [])\npartition p (x:xs) = if p x then (x : a, b) else (a, x : b) where (a, b) = partition p xs\nunzip [] = ([], [])\nunzip ((a, b) : rest) = (a : as, b : bs) where (as, bs) = unzip rest\ntails [] = [[]]\ntails (x:xs) = (x:xs) : tails xs\ninits [] = [[]]\ninits (x:xs) = [] : map (x:) (inits xs)\nisPrefixOf [] _ = True\nisPrefixOf _ [] = False\nisPrefixOf (x:xs) (y:ys) = if x == y then isPrefixOf xs ys else False\nisSuffixOf xs ys = isPrefixOf (reverse xs) (reverse ys)\nisInfixOf [] _ = True\nisInfixOf _ [] = False\nisInfixOf xs ys = if isPrefixOf xs ys then True else isInfixOf xs (tail ys)\nintercalate _ [] = []\nintercalate _ [x] = x\nintercalate sep (x:xs) = x ++ sep ++ intercalate sep xs\nintersperse _ [] = []\nintersperse _ [x] = [x]\nintersperse sep (x:xs) = x : sep : intersperse sep xs\nunwords [] = \"\"\nunwords [w] = w\nunwords (w:ws) = w ++ \" \" ++ unwords ws\nunlines [] = \"\"\nunlines (l:ls) = l ++ \"\\n\" ++ unlines ls\nprint x = putStrLn (show x)\nshows x s = show x ++ s\nshowString prefix rest = prefix ++ rest\nshowParen True p s = \"(\" ++ p (\")\" ++ s)\nshowParen False p s = p s\nshowsPrec _ x s = show x ++ s\nreads s = []\nreadsPrec _ s = reads s\nread s = fst (head (reads s))\nundefined = error \"Prelude.undefined\"\n") + "head (x:_) = x\nhead [] = error \"Prelude.head: empty list\"\ntail (_:xs) = xs\ntail [] = error \"Prelude.tail: empty list\"\nfst (a, _) = a\nsnd (_, b) = b\ntake 0 _ = []\ntake _ [] = []\ntake n (x:xs) = x : take (n - 1) xs\ndrop 0 xs = xs\ndrop _ [] = []\ndrop n (_:xs) = drop (n - 1) xs\nrepeat x = x : repeat x\niterate f x = x : iterate f (f x)\nlength [] = 0\nlength (_:xs) = 1 + length xs\nmap _ [] = []\nmap f (x:xs) = f x : map f xs\nfilter _ [] = []\nfilter p (x:xs) = if p x then x : filter p xs else filter p xs\nzipWith _ [] _ = []\nzipWith _ _ [] = []\nzipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys\nfibs = 0 : 1 : zipWith plus fibs (tail fibs)\nplus a b = a + b\nconcat [] = []\nconcat (xs:xss) = xs ++ concat xss\nconcatMap f [] = []\nconcatMap f (x:xs) = f x ++ concatMap f xs\nabs x = if x < 0 then 0 - x else x\nnegate x = 0 - x\nnull [] = True\nnull _ = False\nflip f x y = f y x\nconst x _ = x\nid x = x\ncurry f x y = f (x, y)\nuncurry f p = f (fst p) (snd p)\nfoldr f z [] = z\nfoldr f z (x:xs) = f x (foldr f z xs)\nfoldl f z [] = z\nfoldl f z (x:xs) = foldl f (f z x) xs\nfoldl1 f (x:xs) = foldl f x xs\nfoldr1 f [x] = x\nfoldr1 f (x:xs) = f x (foldr1 f xs)\nzip [] _ = []\nzip _ [] = []\nzip (x:xs) (y:ys) = (x, y) : zip xs ys\nreverse [] = []\nreverse (x:xs) = reverse xs ++ [x]\nelem _ [] = False\nelem x (y:ys) = if x == y then True else elem x ys\nnotElem x xs = not (elem x xs)\nany _ [] = False\nany f (x:xs) = if f x then True else any f xs\nall _ [] = True\nall f (x:xs) = if f x then all f xs else False\nand [] = True\nand (x:xs) = if x then and xs else False\nor [] = False\nor (x:xs) = if x then True else or xs\nsum [] = 0\nsum (x:xs) = x + sum xs\nproduct [] = 1\nproduct (x:xs) = x * product xs\nmaximum [x] = x\nmaximum (x:xs) = let m = maximum xs in if x >= m then x else m\nminimum [x] = x\nminimum (x:xs) = let m = minimum xs in if x <= m then x else m\ncompare x y = if x < y then LT else if x == y then EQ else GT\nmin x y = if x <= y then x else y\nmax x y = if x >= y then x else y\nsignum x = if x < 0 then negate 1 else if x == 0 then 0 else 1\nfromIntegral x = x\nfromInteger x = x\ntoInteger x = x\nceiling x = x\nfloor x = x\nround x = x\ntruncate x = x\nlookup _ [] = Nothing\nlookup k ((k2,v):rest) = if k == k2 then Just v else lookup k rest\nmaybe d _ Nothing = d\nmaybe _ f (Just x) = f x\neither f _ (Left x) = f x\neither _ g (Right y) = g y\nmapMaybe _ [] = []\nmapMaybe f (x:xs) = case f x of { Nothing -> mapMaybe f xs; Just y -> y : mapMaybe f xs }\nfromJust (Just x) = x\nfromJust Nothing = error \"Maybe.fromJust: Nothing\"\nfromMaybe d Nothing = d\nfromMaybe _ (Just x) = x\nisJust (Just _) = True\nisJust Nothing = False\nisNothing Nothing = True\nisNothing (Just _) = False\nfmap = map\npure = return\nwhen b m = if b then m else return ()\nunless b m = if b then return () else m\nmapM_ _ [] = return ()\nmapM_ f (x:xs) = f x >> mapM_ f xs\nsequence_ [] = return ()\nsequence_ (m:ms) = m >> sequence_ ms\ninteractApply f s = putStr (f s)\ninteract f = getContents >>= interactApply f\nnub [] = []\nnub (x:xs) = x : nub (filter notEqX xs)\n where notEqX y = y /= x\nsort [] = []\nsort (x:xs) = sort (filter ltX xs) ++ [x] ++ sort (filter geX xs)\n where ltX y = y < x\n geX y = y >= x\nsortBy _ [] = []\nsortBy cmp (x:xs) = sortBy cmp smaller ++ [x] ++ sortBy cmp bigger\n where smaller = filter ltCmp xs\n bigger = filter geCmp xs\n ltCmp y = cmp y x /= GT\n geCmp y = cmp y x == GT\nsortOnCmpFst p1 p2 = compare (fst p1) (fst p2)\nsortOn f xs = map snd (sortBy sortOnCmpFst (zip (map f xs) xs))\nsplitAt 0 xs = ([], xs)\nsplitAt _ [] = ([], [])\nsplitAt n (x:xs) = (x : a, b) where (a, b) = splitAt (n - 1) xs\nspan _ [] = ([], [])\nspan p (x:xs) = if p x then (x : a, b) else ([], x : xs) where (a, b) = span p xs\nbreak p xs = span notP xs\n where notP y = not (p y)\npartition _ [] = ([], [])\npartition p (x:xs) = if p x then (x : a, b) else (a, x : b) where (a, b) = partition p xs\nunzip [] = ([], [])\nunzip ((a, b) : rest) = (a : as, b : bs) where (as, bs) = unzip rest\ntails [] = [[]]\ntails (x:xs) = (x:xs) : tails xs\ninits [] = [[]]\ninits (x:xs) = [] : map (x:) (inits xs)\nisPrefixOf [] _ = True\nisPrefixOf _ [] = False\nisPrefixOf (x:xs) (y:ys) = if x == y then isPrefixOf xs ys else False\nisSuffixOf xs ys = isPrefixOf (reverse xs) (reverse ys)\nisInfixOf [] _ = True\nisInfixOf _ [] = False\nisInfixOf xs ys = if isPrefixOf xs ys then True else isInfixOf xs (tail ys)\nintercalate _ [] = []\nintercalate _ [x] = x\nintercalate sep (x:xs) = x ++ sep ++ intercalate sep xs\nintersperse _ [] = []\nintersperse _ [x] = [x]\nintersperse sep (x:xs) = x : sep : intersperse sep xs\nunwords [] = \"\"\nunwords [w] = w\nunwords (w:ws) = w ++ \" \" ++ unwords ws\nunlines [] = \"\"\nunlines (l:ls) = l ++ \"\\n\" ++ unlines ls\nprint x = putStrLn (show x)\nshows x s = show x ++ s\nshowString prefix rest = prefix ++ rest\nshowParen True p s = \"(\" ++ p (\")\" ++ s)\nshowParen False p s = p s\nshowsPrec _ x s = show x ++ s\nreads s = []\nreadsPrec _ s = reads s\nread s = fst (head (reads s))\nundefined = error \"Prelude.undefined\"\n") (define hk-load-into! diff --git a/lib/haskell/tests/eval.sx b/lib/haskell/tests/eval.sx index 2c05fd64..7bbeec29 100644 --- a/lib/haskell/tests/eval.sx +++ b/lib/haskell/tests/eval.sx @@ -259,6 +259,36 @@ (hk-deep-force (hk-run "main = if True then 42 else undefined")) 42) +(hk-test + "head []: raises Prelude.head: empty list" + (guard + (e (true (>= (index-of e "Prelude.head: empty list") 0))) + (begin (hk-deep-force (hk-run "main = head []")) false)) + true) + +(hk-test + "tail []: raises Prelude.tail: empty list" + (guard + (e (true (>= (index-of e "Prelude.tail: empty list") 0))) + (begin (hk-deep-force (hk-run "main = tail []")) false)) + true) + +;; ── not / id built-ins ── +(hk-test + "fromJust Nothing: raises Maybe.fromJust: Nothing" + (guard + (e (true (>= (index-of e "Maybe.fromJust: Nothing") 0))) + (begin (hk-deep-force (hk-run "main = fromJust Nothing")) false)) + true) +(hk-test + "fromJust (Just 5) = 5" + (hk-deep-force (hk-run "main = fromJust (Just 5)")) + 5) +(hk-test + "head [42] = 42 (still works for non-empty)" + (hk-deep-force (hk-run "main = head [42]")) + 42) + (hk-test "second arg never forced" (hk-eval-expr-source "(\\x y -> x) 1 (error \"never\")") @@ -269,16 +299,17 @@ (hk-eval-expr-source "(\\x y -> y) (error \"never\") 99") 99) -;; ── not / id built-ins ── (hk-test "constructor argument is lazy under wildcard pattern" (hk-eval-expr-source "case Just (error \"deeply\") of Just _ -> 7 ; Nothing -> 0") 7) + (hk-test "lazy: const drops its second argument" (hk-prog-val "const x y = x\nresult = const 5 (error \"boom\")" "result") 5) + (hk-test "lazy: head ignores tail" (hk-prog-val diff --git a/plans/haskell-completeness.md b/plans/haskell-completeness.md index bed867a6..789fb9d7 100644 --- a/plans/haskell-completeness.md +++ b/plans/haskell-completeness.md @@ -128,7 +128,7 @@ No OCaml changes are needed. The view type is fully representable as an SX dict. the tag has to live in a string prefix rather than as the head of a list. Catchers use `(index-of e "hk-error: ")` to detect. - [x] `undefined :: a` = `error "Prelude.undefined"`. -- [ ] Partial functions emit proper error messages: `head []` → +- [x] Partial functions emit proper error messages: `head []` → `"Prelude.head: empty list"`, `tail []` → `"Prelude.tail: empty list"`, `fromJust Nothing` → `"Maybe.fromJust: Nothing"`. - [ ] Top-level `hk-run-io` catches `hk-error` tag and returns it as a tagged @@ -293,6 +293,15 @@ No OCaml changes are needed. The view type is fully representable as an SX dict. _Newest first._ +**2026-05-07** — Phase 9 partial functions emit proper error messages: +- Added empty-list catch clauses to `head`, `tail` in the prelude. Added + `fromJust`, `fromMaybe`, `isJust`, `isNothing` (the last three were missing). + `fromJust Nothing` raises `"Maybe.fromJust: Nothing"`. Multi-clause dispatch + tries the constructor pattern first, then falls through to the empty-list / + Nothing error clause. +- 5 new tests in `tests/eval.sx`. Suite is 64/64. Verified no regressions in + match, stdlib, fib, quicksort, program-maybe. + **2026-05-07** — Phase 9 `undefined = error "Prelude.undefined"` + lazy CAFs: - Added `undefined = error "Prelude.undefined"` to `hk-prelude-src`. Without any other change this raised at prelude-load time because `hk-bind-decls!`