haskell: seq + deepseq via lazy-builtin flag (+9 tests, 368/368)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
This commit is contained in:
@@ -87,7 +87,17 @@
|
||||
hk-mk-builtin
|
||||
(fn
|
||||
(name fn arity)
|
||||
{:type "builtin" :name name :fn fn :arity arity :collected (list)}))
|
||||
{:type "builtin" :name name :fn fn :arity arity :lazy false :collected (list)}))
|
||||
|
||||
;; A lazy built-in receives its collected args as raw thunks (or
|
||||
;; values, if those happened to be eager) — the implementation is
|
||||
;; responsible for forcing exactly what it needs. Used for `seq`
|
||||
;; and `deepseq`, which are non-strict in their second argument.
|
||||
(define
|
||||
hk-mk-lazy-builtin
|
||||
(fn
|
||||
(name fn arity)
|
||||
{:type "builtin" :name name :fn fn :arity arity :lazy true :collected (list)}))
|
||||
|
||||
;; ── Apply a function value to one argument ──────────────────
|
||||
(define
|
||||
@@ -199,11 +209,15 @@
|
||||
((< (len collected) arity)
|
||||
(assoc b "collected" collected))
|
||||
(:else
|
||||
;; Built-ins are strict in all their arguments. Force each
|
||||
;; collected thunk before invoking the underlying SX fn.
|
||||
(apply
|
||||
(get b "fn")
|
||||
(map hk-force collected)))))))
|
||||
;; Strict built-ins force every collected arg before
|
||||
;; calling. Lazy ones (`seq`, `deepseq`) receive the raw
|
||||
;; thunks so they can choose what to force.
|
||||
(cond
|
||||
((get b "lazy") (apply (get b "fn") collected))
|
||||
(:else
|
||||
(apply
|
||||
(get b "fn")
|
||||
(map hk-force collected)))))))))
|
||||
|
||||
;; ── Bool helpers (Bool values are tagged conses) ────────────
|
||||
(define
|
||||
@@ -583,6 +597,23 @@ plus a b = a + b
|
||||
env
|
||||
"id"
|
||||
(hk-mk-builtin "id" (fn (x) x) 1))
|
||||
;; `seq a b` — strict in `a`, lazy in `b`. Forces `a` to WHNF
|
||||
;; and returns `b` unchanged (still a thunk if it was one).
|
||||
(dict-set!
|
||||
env
|
||||
"seq"
|
||||
(hk-mk-lazy-builtin
|
||||
"seq"
|
||||
(fn (a b) (do (hk-force a) b))
|
||||
2))
|
||||
;; `deepseq a b` — like seq but forces `a` to normal form.
|
||||
(dict-set!
|
||||
env
|
||||
"deepseq"
|
||||
(hk-mk-lazy-builtin
|
||||
"deepseq"
|
||||
(fn (a b) (do (hk-deep-force a) b))
|
||||
2))
|
||||
;; Operators as first-class values
|
||||
(dict-set! env "+" (hk-make-binop-builtin "+" "+"))
|
||||
(dict-set! env "-" (hk-make-binop-builtin "-" "-"))
|
||||
|
||||
85
lib/haskell/tests/seq.sx
Normal file
85
lib/haskell/tests/seq.sx
Normal file
@@ -0,0 +1,85 @@
|
||||
;; seq / deepseq tests. seq is strict in its first arg (forces to
|
||||
;; WHNF) and returns the second arg unchanged. deepseq additionally
|
||||
;; forces the first arg to normal form.
|
||||
|
||||
(define
|
||||
hk-prog-val
|
||||
(fn
|
||||
(src name)
|
||||
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
|
||||
|
||||
(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))))
|
||||
|
||||
(define
|
||||
hk-eval-list
|
||||
(fn (src) (hk-as-list (hk-eval-expr-source src))))
|
||||
|
||||
;; ── seq returns its second arg ──
|
||||
(hk-test
|
||||
"seq with primitive first arg"
|
||||
(hk-eval-expr-source "seq 1 99")
|
||||
99)
|
||||
|
||||
(hk-test
|
||||
"seq forces first arg via let"
|
||||
(hk-eval-expr-source "let x = 1 + 2 in seq x x")
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"seq second arg is whatever shape"
|
||||
(hk-eval-expr-source "seq 0 \"hello\"")
|
||||
"hello")
|
||||
|
||||
;; ── seq enables previously-lazy bottom to be forced ──
|
||||
;; Without seq the let-binding `x = error …` is never forced;
|
||||
;; with seq it must be forced because seq is strict in its first
|
||||
;; argument. We don't run that error case here (it would terminate
|
||||
;; the test), but we do verify the negative — that without seq,
|
||||
;; the bottom bound is never demanded.
|
||||
(hk-test
|
||||
"lazy let — bottom never forced when unused"
|
||||
(hk-eval-expr-source "let x = error \"never\" in 42")
|
||||
42)
|
||||
|
||||
;; ── deepseq forces nested structure ──
|
||||
(hk-test
|
||||
"deepseq with finite list"
|
||||
(hk-eval-expr-source "deepseq [1, 2, 3] 7")
|
||||
7)
|
||||
|
||||
(hk-test
|
||||
"deepseq with constructor value"
|
||||
(hk-eval-expr-source "deepseq (Just 5) 11")
|
||||
11)
|
||||
|
||||
(hk-test
|
||||
"deepseq with tuple"
|
||||
(hk-eval-expr-source "deepseq (1, 2) 13")
|
||||
13)
|
||||
|
||||
;; ── seq + arithmetic ──
|
||||
(hk-test
|
||||
"seq used inside arithmetic doesn't poison the result"
|
||||
(hk-eval-expr-source "(seq 1 5) + (seq 2 7)")
|
||||
12)
|
||||
|
||||
;; ── seq in user code ──
|
||||
(hk-test
|
||||
"seq via fun-clause"
|
||||
(hk-prog-val
|
||||
"f x = seq x (x + 1)\nresult = f 10"
|
||||
"result")
|
||||
11)
|
||||
|
||||
(hk-test
|
||||
"seq sequences list construction"
|
||||
(hk-eval-list "[seq 1 10, seq 2 20]")
|
||||
(list 10 20))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
Reference in New Issue
Block a user