Lazy seqs for map, filter, range and the rest of the seq library
map/filter/remove/range/take/drop/take-while/drop-while/concat/ map-indexed now return lazy seqs, and iterate/repeat/repeatedly/cycle/ doall/dorun join them. An element is computed on first demand and memoized, so infinite seqs are ordinary values and a consumer that stops early never pays for the rest: (take 5 (filter even? (range))) => (0 2 4 6 8) (first (filter odd? (map inc (range 2000000)))) 671ms -> 0ms Two new Value kinds: kCons (head + tail) and kLazy (a memoizing thunk). Producers all share one shape — capture a Cursor, return a thunk that advances a copy of it and yields one cons — so no producer recurses per element and nothing is computed at construction time. Cursor is the single way core walks a collection: it follows a cons/lazy chain link by link and indexes concrete collections directly, so no builtin materializes more of a seq than it was asked for. nth, first, rest, seq, empty? and `& rest` destructuring stop at the element they need; count, reduce and printing realize the whole seq. cons is now O(1) rather than a copy. Teardown needed a hand-written =destroy. ARC frees a linked structure by recursing into it, so dropping a million-element seq meant a million destructor frames and a segfault. Value now hands a cons or lazy tail to a worklist and drains it in a loop; nothing shared is mutated, so a tail another seq still holds survives untouched. Verified with a 1M-element teardown and a 40-round churn of lazy seqs, vectors, maps, sets, strings and closures: flat RSS, clean exit. --mm:refc would also have avoided the recursion but cost 4.5x on map/set writes. The trade is the usual one: a fully realized lazy seq allocates a cons and a thunk per element where the eager path filled one flat seq, so realizing all of (map inc (range 1000000)) went 290ms -> 570ms. Clojure buys that back with 32-element chunking; not chunking is why take 3 computes exactly three elements here. fib(30) is unchanged. Also fixes `& rest` destructuring, which realized the whole collection and so hung on an infinite seq. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4de7343 parent: 86373e5 modified
README.md +39 -4 | @@ -66,6 +66,37 @@ Clojure's small array-maps are, without giving up hashed lookup. | ||
| 66 | 66 | The set column is the honest shape of the old representation: `conj` rebuilt the |
| 67 | 67 | whole set and re-scanned it for duplicates, so building one was O(n³). |
| 68 | 68 | |
| 69 | +**Seqs are lazy.** `map`, `filter`, `remove`, `range`, `take`, `drop`, | |
| 70 | +`take-while`, `drop-while`, `concat`, `map-indexed`, `iterate`, `repeat`, | |
| 71 | +`repeatedly` and `cycle` return a chain of thunks: each element is computed on | |
| 72 | +first demand and memoized, so infinite seqs are ordinary values and a consumer | |
| 73 | +that stops early never pays for the rest. | |
| 74 | + | |
| 75 | +```clojure | |
| 76 | +(take 5 (filter even? (range))) ;=> (0 2 4 6 8) | |
| 77 | +(first (filter odd? (map inc (range 2000000)))) ; 671 ms eager -> 0 ms lazy | |
| 78 | +``` | |
| 79 | + | |
| 80 | +Everything in `core` walks collections through one `Cursor`, which follows a | |
| 81 | +cons/lazy chain link by link and indexes concrete collections directly, so a | |
| 82 | +builtin never materializes more of a seq than it was asked for. `nth`, `first`, | |
| 83 | +`rest`, `seq`, `empty?` and `& rest` destructuring all stop at the element they | |
| 84 | +need; `count`, `reduce` and printing realize the whole seq, which is what those | |
| 85 | +mean. | |
| 86 | + | |
| 87 | +The cost is the usual one: a fully realized lazy seq allocates a cons cell and | |
| 88 | +a thunk per element, where the eager version filled one flat `seq`. Realizing | |
| 89 | +all of `(map inc (range 1000000))` went from 290 ms to 570 ms. Clojure buys most | |
| 90 | +of that back by realizing in 32-element chunks; clonim does not chunk yet, which | |
| 91 | +is why its laziness is exact — `take 3` computes exactly three elements, not | |
| 92 | +thirty-two. | |
| 93 | + | |
| 94 | +Long chains need one piece of care. ARC frees a linked structure by recursing | |
| 95 | +into it, so dropping a million-element seq means a million destructor frames and | |
| 96 | +a segfault. `Value` therefore has a hand-written `=destroy` that hands a cons or | |
| 97 | +lazy tail to a worklist and drains it in a loop. Nothing shared is mutated, so a | |
| 98 | +tail another seq still holds simply survives. | |
| 99 | + | |
| 69 | 100 | ## What works |
| 70 | 101 | |
| 71 | 102 | `def` `defn` (multi-arity, varargs, docstrings) `fn` (named, self-recursive) |
| @@ -78,15 +109,19 @@ Data: nil, bool, int, float, string, keyword, symbol, list, vector, map, set — | ||
| 78 | 109 | persistent, with structural equality, hashing, and Clojure-shaped printing. Atoms, closures, `comp`, |
| 79 | 110 | `partial`, `juxt`, the usual seq library, `clojure.string/*`. |
| 80 | 111 | |
| 112 | +Lazy seqs: `iterate` `repeat` `repeatedly` `cycle` `doall` `dorun`, and the seq | |
| 113 | +library above returns them where Clojure does. | |
| 114 | + | |
| 81 | 115 | ## What doesn't (yet) |
| 82 | 116 | |
| 83 | 117 | - **`defmacro`.** The macro set is fixed and expanded by the compiler. User |
| 84 | 118 | macros need the compiler to be able to *evaluate* code at compile time — |
| 85 | 119 | the honest fix is to bootstrap clonim in itself, or embed an interpreter. |
| 86 | -- **Laziness.** `map`/`filter`/`range` are eager. Infinite seqs will hang. | |
| 87 | -- **Laziness for the seq library.** Most of `core` still materialises a | |
| 88 | - `seq[Value]` on the way in and out, so even with persistent vectors, `map` | |
| 89 | - over a big collection allocates twice. | |
| 120 | +- **Chunked seqs.** Lazy seqs are unchunked, so full realization allocates two | |
| 121 | + cells per element and runs ~2× slower than the old eager path. 32-element | |
| 122 | + chunking is the fix, at the cost of exact demand. | |
| 123 | +- **Destructuring in parameter lists.** `(let [[a b] xs] …)` works; `(defn f | |
| 124 | + [[a b]] …)` does not. | |
| 90 | 125 | - Protocols/records, namespaces (`ns` is parsed and ignored), refs/agents, |
| 91 | 126 | `#()` literals, syntax-quote, transducers, Nim interop. |
| 92 | 127 | |
| @@ -66,6 +66,37 @@ Clojure's small array-maps are, without giving up hashed lookup. | |||
| 66 | The set column is the honest shape of the old representation: `conj` rebuilt the | 66 | The set column is the honest shape of the old representation: `conj` rebuilt the |
| 67 | whole set and re-scanned it for duplicates, so building one was O(n³). | 67 | whole set and re-scanned it for duplicates, so building one was O(n³). |
| 68 | 68 | ||
| 69 | +**Seqs are lazy.** `map`, `filter`, `remove`, `range`, `take`, `drop`, | ||
| 70 | +`take-while`, `drop-while`, `concat`, `map-indexed`, `iterate`, `repeat`, | ||
| 71 | +`repeatedly` and `cycle` return a chain of thunks: each element is computed on | ||
| 72 | +first demand and memoized, so infinite seqs are ordinary values and a consumer | ||
| 73 | +that stops early never pays for the rest. | ||
| 74 | + | ||
| 75 | +```clojure | ||
| 76 | +(take 5 (filter even? (range))) ;=> (0 2 4 6 8) | ||
| 77 | +(first (filter odd? (map inc (range 2000000)))) ; 671 ms eager -> 0 ms lazy | ||
| 78 | +``` | ||
| 79 | + | ||
| 80 | +Everything in `core` walks collections through one `Cursor`, which follows a | ||
| 81 | +cons/lazy chain link by link and indexes concrete collections directly, so a | ||
| 82 | +builtin never materializes more of a seq than it was asked for. `nth`, `first`, | ||
| 83 | +`rest`, `seq`, `empty?` and `& rest` destructuring all stop at the element they | ||
| 84 | +need; `count`, `reduce` and printing realize the whole seq, which is what those | ||
| 85 | +mean. | ||
| 86 | + | ||
| 87 | +The cost is the usual one: a fully realized lazy seq allocates a cons cell and | ||
| 88 | +a thunk per element, where the eager version filled one flat `seq`. Realizing | ||
| 89 | +all of `(map inc (range 1000000))` went from 290 ms to 570 ms. Clojure buys most | ||
| 90 | +of that back by realizing in 32-element chunks; clonim does not chunk yet, which | ||
| 91 | +is why its laziness is exact — `take 3` computes exactly three elements, not | ||
| 92 | +thirty-two. | ||
| 93 | + | ||
| 94 | +Long chains need one piece of care. ARC frees a linked structure by recursing | ||
| 95 | +into it, so dropping a million-element seq means a million destructor frames and | ||
| 96 | +a segfault. `Value` therefore has a hand-written `=destroy` that hands a cons or | ||
| 97 | +lazy tail to a worklist and drains it in a loop. Nothing shared is mutated, so a | ||
| 98 | +tail another seq still holds simply survives. | ||
| 99 | + | ||
| 69 | ## What works | 100 | ## What works |
| 70 | 101 | ||
| 71 | `def` `defn` (multi-arity, varargs, docstrings) `fn` (named, self-recursive) | 102 | `def` `defn` (multi-arity, varargs, docstrings) `fn` (named, self-recursive) |
| @@ -78,15 +109,19 @@ Data: nil, bool, int, float, string, keyword, symbol, list, vector, map, set — | |||
| 78 | persistent, with structural equality, hashing, and Clojure-shaped printing. Atoms, closures, `comp`, | 109 | persistent, with structural equality, hashing, and Clojure-shaped printing. Atoms, closures, `comp`, |
| 79 | `partial`, `juxt`, the usual seq library, `clojure.string/*`. | 110 | `partial`, `juxt`, the usual seq library, `clojure.string/*`. |
| 80 | 111 | ||
| 112 | +Lazy seqs: `iterate` `repeat` `repeatedly` `cycle` `doall` `dorun`, and the seq | ||
| 113 | +library above returns them where Clojure does. | ||
| 114 | + | ||
| 81 | ## What doesn't (yet) | 115 | ## What doesn't (yet) |
| 82 | 116 | ||
| 83 | - **`defmacro`.** The macro set is fixed and expanded by the compiler. User | 117 | - **`defmacro`.** The macro set is fixed and expanded by the compiler. User |
| 84 | macros need the compiler to be able to *evaluate* code at compile time — | 118 | macros need the compiler to be able to *evaluate* code at compile time — |
| 85 | the honest fix is to bootstrap clonim in itself, or embed an interpreter. | 119 | the honest fix is to bootstrap clonim in itself, or embed an interpreter. |
| 86 | -- **Laziness.** `map`/`filter`/`range` are eager. Infinite seqs will hang. | 120 | +- **Chunked seqs.** Lazy seqs are unchunked, so full realization allocates two |
| 87 | -- **Laziness for the seq library.** Most of `core` still materialises a | 121 | + cells per element and runs ~2× slower than the old eager path. 32-element |
| 88 | - `seq[Value]` on the way in and out, so even with persistent vectors, `map` | 122 | + chunking is the fix, at the cost of exact demand. |
| 89 | - over a big collection allocates twice. | 123 | +- **Destructuring in parameter lists.** `(let [[a b] xs] …)` works; `(defn f |
| 124 | + [[a b]] …)` does not. | ||
| 90 | - Protocols/records, namespaces (`ns` is parsed and ignored), refs/agents, | 125 | - Protocols/records, namespaces (`ns` is parsed and ignored), refs/agents, |
| 91 | `#()` literals, syntax-quote, transducers, Nim interop. | 126 | `#()` literals, syntax-quote, transducers, Nim interop. |
| 92 | 127 | ||
added
examples/lazy-bench.clj +8 -0 | new file mode 100644 | ||
| @@ -0,0 +1,8 @@ | ||
| 1 | +(def n 2000000) | |
| 2 | +(let [t0 (now-ms) | |
| 3 | + a (first (filter odd? (map inc (range n)))) | |
| 4 | + t1 (now-ms) | |
| 5 | + b (reduce + 0 (take 10 (map (fn [x] (* x x)) (range n)))) | |
| 6 | + t2 (now-ms)] | |
| 7 | + (println "first of filter/map over" n ":" (- t1 t0) "ms =" a) | |
| 8 | + (println "sum of take 10 over " n ":" (- t2 t1) "ms =" b)) | |
| new file mode 100644 | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | +(def n 2000000) | ||
| 2 | +(let [t0 (now-ms) | ||
| 3 | + a (first (filter odd? (map inc (range n)))) | ||
| 4 | + t1 (now-ms) | ||
| 5 | + b (reduce + 0 (take 10 (map (fn [x] (* x x)) (range n)))) | ||
| 6 | + t2 (now-ms)] | ||
| 7 | + (println "first of filter/map over" n ":" (- t1 t0) "ms =" a) | ||
| 8 | + (println "sum of take 10 over " n ":" (- t2 t1) "ms =" b)) | ||
added
examples/lazy.clj +42 -0 | new file mode 100644 | ||
| @@ -0,0 +1,42 @@ | ||
| 1 | +;; Lazy seqs: map/filter/range and friends compute only what is asked for. | |
| 2 | + | |
| 3 | +;; infinite sources are ordinary values | |
| 4 | +(println (take 5 (range))) | |
| 5 | +(println (take 5 (map inc (range)))) | |
| 6 | +(println (take 5 (filter even? (range)))) | |
| 7 | +(println (take 5 (iterate (fn [x] (* 2 x)) 1))) | |
| 8 | +(println (take 4 (repeat :x)) (repeat 3 :y)) | |
| 9 | +(println (take 7 (cycle [1 2 3]))) | |
| 10 | +(println (take 3 (drop 100 (range)))) | |
| 11 | +(println (take-while (fn [x] (< x 5)) (range))) | |
| 12 | +(println (take 3 (drop-while (fn [x] (< x 10)) (range)))) | |
| 13 | +(println (take 5 (concat [1 2] (range)))) | |
| 14 | +(println (first (map inc (range))) (second (range)) (nth (range) 1000)) | |
| 15 | + | |
| 16 | +;; nothing beyond the demand is computed, and each cell is computed once | |
| 17 | +(def calls (atom 0)) | |
| 18 | +(def xs (map (fn [x] (reset! calls (inc (deref calls))) x) (range 1000))) | |
| 19 | +(println "built, calls so far:" (deref calls)) | |
| 20 | +(def three (doall (take 3 xs))) | |
| 21 | +(println "took" three "- calls:" (deref calls)) | |
| 22 | +(println "took" (doall (take 3 xs)) "- calls:" (deref calls) "(memoized)") | |
| 23 | + | |
| 24 | +;; composing lazily builds no intermediate collections | |
| 25 | +(println (reduce + 0 (take 10 (filter odd? (map (fn [x] (* x x)) (range)))))) | |
| 26 | + | |
| 27 | +;; destructuring walks only as far as the pattern needs | |
| 28 | +(let [[a b & more] (range)] | |
| 29 | + (println a b (take 3 more))) | |
| 30 | + | |
| 31 | +;; finite collections behave exactly as before | |
| 32 | +(println (map inc [1 2 3]) (filter even? [1 2 3 4])) | |
| 33 | +(println (range 5) (range 2 8 2) (range 5 0 -1)) | |
| 34 | +(println (count (range 100)) (empty? (range 0)) (seq (range 0)) (seq (range 2))) | |
| 35 | +(println (= (range 3) [0 1 2]) (= (map inc [0 1]) (list 1 2))) | |
| 36 | +(println (vec (take 3 (range))) (sort (take 4 (map (fn [x] (- 9 x)) (range))))) | |
| 37 | +(println (cons 0 (range 3)) (rest (range 3)) (next (range 1)) (last (take 4 (range)))) | |
| 38 | +(doseq [x (take 3 (map inc (range)))] (print x "")) | |
| 39 | +(println) | |
| 40 | + | |
| 41 | +;; realizing a long seq is iterative: no stack growth, no deep teardown | |
| 42 | +(println (count (take 200000 (range))) (nth (iterate inc 0) 200000)) | |
| new file mode 100644 | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | +;; Lazy seqs: map/filter/range and friends compute only what is asked for. | ||
| 2 | + | ||
| 3 | +;; infinite sources are ordinary values | ||
| 4 | +(println (take 5 (range))) | ||
| 5 | +(println (take 5 (map inc (range)))) | ||
| 6 | +(println (take 5 (filter even? (range)))) | ||
| 7 | +(println (take 5 (iterate (fn [x] (* 2 x)) 1))) | ||
| 8 | +(println (take 4 (repeat :x)) (repeat 3 :y)) | ||
| 9 | +(println (take 7 (cycle [1 2 3]))) | ||
| 10 | +(println (take 3 (drop 100 (range)))) | ||
| 11 | +(println (take-while (fn [x] (< x 5)) (range))) | ||
| 12 | +(println (take 3 (drop-while (fn [x] (< x 10)) (range)))) | ||
| 13 | +(println (take 5 (concat [1 2] (range)))) | ||
| 14 | +(println (first (map inc (range))) (second (range)) (nth (range) 1000)) | ||
| 15 | + | ||
| 16 | +;; nothing beyond the demand is computed, and each cell is computed once | ||
| 17 | +(def calls (atom 0)) | ||
| 18 | +(def xs (map (fn [x] (reset! calls (inc (deref calls))) x) (range 1000))) | ||
| 19 | +(println "built, calls so far:" (deref calls)) | ||
| 20 | +(def three (doall (take 3 xs))) | ||
| 21 | +(println "took" three "- calls:" (deref calls)) | ||
| 22 | +(println "took" (doall (take 3 xs)) "- calls:" (deref calls) "(memoized)") | ||
| 23 | + | ||
| 24 | +;; composing lazily builds no intermediate collections | ||
| 25 | +(println (reduce + 0 (take 10 (filter odd? (map (fn [x] (* x x)) (range)))))) | ||
| 26 | + | ||
| 27 | +;; destructuring walks only as far as the pattern needs | ||
| 28 | +(let [[a b & more] (range)] | ||
| 29 | + (println a b (take 3 more))) | ||
| 30 | + | ||
| 31 | +;; finite collections behave exactly as before | ||
| 32 | +(println (map inc [1 2 3]) (filter even? [1 2 3 4])) | ||
| 33 | +(println (range 5) (range 2 8 2) (range 5 0 -1)) | ||
| 34 | +(println (count (range 100)) (empty? (range 0)) (seq (range 0)) (seq (range 2))) | ||
| 35 | +(println (= (range 3) [0 1 2]) (= (map inc [0 1]) (list 1 2))) | ||
| 36 | +(println (vec (take 3 (range))) (sort (take 4 (map (fn [x] (- 9 x)) (range))))) | ||
| 37 | +(println (cons 0 (range 3)) (rest (range 3)) (next (range 1)) (last (take 4 (range)))) | ||
| 38 | +(doseq [x (take 3 (map inc (range)))] (print x "")) | ||
| 39 | +(println) | ||
| 40 | + | ||
| 41 | +;; realizing a long seq is iterative: no stack growth, no deep teardown | ||
| 42 | +(println (count (take 200000 (range))) (nth (iterate inc 0) 200000)) | ||
modified
justfile +2 -1 | @@ -26,9 +26,10 @@ emit file: build | ||
| 26 | 26 | compile file: build |
| 27 | 27 | ./{{bin}} build {{file}} |
| 28 | 28 | |
| 29 | -# Persistent-collection benchmark: conj/assoc/get at scale | |
| 29 | +# Benchmarks: persistent-collection writes, and lazy early exit | |
| 30 | 30 | bench: release |
| 31 | 31 | ./{{bin}} run examples/persistent-bench.clj -d |
| 32 | + ./{{bin}} run examples/lazy-bench.clj -d | |
| 32 | 33 | |
| 33 | 34 | # Re-record tests/<name>.expected from current output |
| 34 | 35 | accept: build |
| @@ -26,9 +26,10 @@ emit file: build | |||
| 26 | compile file: build | 26 | compile file: build |
| 27 | ./{{bin}} build {{file}} | 27 | ./{{bin}} build {{file}} |
| 28 | 28 | ||
| 29 | -# Persistent-collection benchmark: conj/assoc/get at scale | 29 | +# Benchmarks: persistent-collection writes, and lazy early exit |
| 30 | bench: release | 30 | bench: release |
| 31 | ./{{bin}} run examples/persistent-bench.clj -d | 31 | ./{{bin}} run examples/persistent-bench.clj -d |
| 32 | + ./{{bin}} run examples/lazy-bench.clj -d | ||
| 32 | 33 | ||
| 33 | # Re-record tests/<name>.expected from current output | 34 | # Re-record tests/<name>.expected from current output |
| 34 | accept: build | 35 | accept: build |
modified
src/compiler.nim +5 -3 | @@ -101,6 +101,7 @@ proc quoteLit(v: Value): string = | ||
| 101 | 101 | var parts: seq[string] = @[] |
| 102 | 102 | for (k, val) in v.pairs: parts.add "(" & quoteLit(k) & ", " & quoteLit(val) & ")" |
| 103 | 103 | "mkMap(@[" & parts.join(", ") & "])" |
| 104 | + of kCons, kLazy: err("Can't quote a lazy seq") | |
| 104 | 105 | of kFn: err("Can't quote a function") |
| 105 | 106 | |
| 106 | 107 | proc emptySeqFix(s: string, elemType: string): string = |
| @@ -240,8 +241,7 @@ proc genLet(bindings: Value, body: seq[Value], dst: string, env: Env, c: Ctx) = | ||
| 240 | 241 | if isSym(p, "&"): |
| 241 | 242 | let restSym = symName(target.items[j + 1]) |
| 242 | 243 | let id = c.gensym("l" & mangle(restSym)) |
| 243 | - c.line("var " & id & ": Value = mkList(toSeq(" & v & ")[min(" & $idx & | |
| 244 | - ", toSeq(" & v & ").len) .. ^1])") | |
| 244 | + c.line("var " & id & ": Value = seqDrop(" & v & ", " & $idx & ")") | |
| 245 | 245 | lenv.locals[restSym] = id |
| 246 | 246 | break |
| 247 | 247 | let id = c.gensym("l" & mangle(symName(p))) |
| @@ -331,6 +331,8 @@ proc genInto(f: Value, dst: string, env: Env, c: Ctx) = | ||
| 331 | 331 | (if parts.len == 0: "newSeq[(Value, Value)]()" else: "@[" & parts.join(", ") & "]") & ")") |
| 332 | 332 | of kFn: |
| 333 | 333 | err("Can't emit a function literal") |
| 334 | + of kCons, kLazy: | |
| 335 | + err("Can't emit a lazy seq literal") | |
| 334 | 336 | of kList: |
| 335 | 337 | if f.items.len == 0: |
| 336 | 338 | c.line(dst & " = mkList(newSeq[Value]())"); return |
| @@ -514,7 +516,7 @@ proc genInto(f: Value, dst: string, env: Env, c: Ctx) = | ||
| 514 | 516 | let nm = symName(b.items[0]) |
| 515 | 517 | let cv = genExpr(b.items[1], env, c) |
| 516 | 518 | let it = c.gensym("it") |
| 517 | - c.line("for " & it & " in toSeq(" & cv & "):") | |
| 519 | + c.line("for " & it & " in elems(" & cv & "):") | |
| 518 | 520 | c.push |
| 519 | 521 | let benv = newEnv(env) |
| 520 | 522 | let id = c.gensym("l" & mangle(nm)) |
| @@ -101,6 +101,7 @@ proc quoteLit(v: Value): string = | |||
| 101 | var parts: seq[string] = @[] | 101 | var parts: seq[string] = @[] |
| 102 | for (k, val) in v.pairs: parts.add "(" & quoteLit(k) & ", " & quoteLit(val) & ")" | 102 | for (k, val) in v.pairs: parts.add "(" & quoteLit(k) & ", " & quoteLit(val) & ")" |
| 103 | "mkMap(@[" & parts.join(", ") & "])" | 103 | "mkMap(@[" & parts.join(", ") & "])" |
| 104 | + of kCons, kLazy: err("Can't quote a lazy seq") | ||
| 104 | of kFn: err("Can't quote a function") | 105 | of kFn: err("Can't quote a function") |
| 105 | 106 | ||
| 106 | proc emptySeqFix(s: string, elemType: string): string = | 107 | proc emptySeqFix(s: string, elemType: string): string = |
| @@ -240,8 +241,7 @@ proc genLet(bindings: Value, body: seq[Value], dst: string, env: Env, c: Ctx) = | |||
| 240 | if isSym(p, "&"): | 241 | if isSym(p, "&"): |
| 241 | let restSym = symName(target.items[j + 1]) | 242 | let restSym = symName(target.items[j + 1]) |
| 242 | let id = c.gensym("l" & mangle(restSym)) | 243 | let id = c.gensym("l" & mangle(restSym)) |
| 243 | - c.line("var " & id & ": Value = mkList(toSeq(" & v & ")[min(" & $idx & | 244 | + c.line("var " & id & ": Value = seqDrop(" & v & ", " & $idx & ")") |
| 244 | - ", toSeq(" & v & ").len) .. ^1])") | ||
| 245 | lenv.locals[restSym] = id | 245 | lenv.locals[restSym] = id |
| 246 | break | 246 | break |
| 247 | let id = c.gensym("l" & mangle(symName(p))) | 247 | let id = c.gensym("l" & mangle(symName(p))) |
| @@ -331,6 +331,8 @@ proc genInto(f: Value, dst: string, env: Env, c: Ctx) = | |||
| 331 | (if parts.len == 0: "newSeq[(Value, Value)]()" else: "@[" & parts.join(", ") & "]") & ")") | 331 | (if parts.len == 0: "newSeq[(Value, Value)]()" else: "@[" & parts.join(", ") & "]") & ")") |
| 332 | of kFn: | 332 | of kFn: |
| 333 | err("Can't emit a function literal") | 333 | err("Can't emit a function literal") |
| 334 | + of kCons, kLazy: | ||
| 335 | + err("Can't emit a lazy seq literal") | ||
| 334 | of kList: | 336 | of kList: |
| 335 | if f.items.len == 0: | 337 | if f.items.len == 0: |
| 336 | c.line(dst & " = mkList(newSeq[Value]())"); return | 338 | c.line(dst & " = mkList(newSeq[Value]())"); return |
| @@ -514,7 +516,7 @@ proc genInto(f: Value, dst: string, env: Env, c: Ctx) = | |||
| 514 | let nm = symName(b.items[0]) | 516 | let nm = symName(b.items[0]) |
| 515 | let cv = genExpr(b.items[1], env, c) | 517 | let cv = genExpr(b.items[1], env, c) |
| 516 | let it = c.gensym("it") | 518 | let it = c.gensym("it") |
| 517 | - c.line("for " & it & " in toSeq(" & cv & "):") | 519 | + c.line("for " & it & " in elems(" & cv & "):") |
| 518 | c.push | 520 | c.push |
| 519 | let benv = newEnv(env) | 521 | let benv = newEnv(env) |
| 520 | let id = c.gensym("l" & mangle(nm)) | 522 | let id = c.gensym("l" & mangle(nm)) |
modified
src/core.nim +183 -86 | @@ -87,6 +87,133 @@ proc conjOne(coll, x: Value): Value = | ||
| 87 | 87 | else: err("conj on map needs a pair") |
| 88 | 88 | else: err("conj not supported on " & prStr(coll)) |
| 89 | 89 | |
| 90 | +# ------------------------------------------------------------- lazy seqs | |
| 91 | +## Producers share one shape: capture a Cursor, and return a thunk that | |
| 92 | +## advances a *copy* of it, yields one cons cell, and hands the advanced copy | |
| 93 | +## to the next thunk. Because each thunk only ever takes one step, an infinite | |
| 94 | +## source costs exactly as much as the consumer asks for. | |
| 95 | + | |
| 96 | +proc lazyOf(c: Cursor): Value = | |
| 97 | + ## The remainder of a cursor, as a lazy seq. | |
| 98 | + let cur = c | |
| 99 | + mkLazy(proc (): Value = | |
| 100 | + var cc = cur | |
| 101 | + if not hasNext(cc): return NilV | |
| 102 | + let x = next(cc) | |
| 103 | + mkCons(x, lazyOf(cc))) | |
| 104 | + | |
| 105 | +proc lazyMap(f: Value, c: Cursor): Value = | |
| 106 | + let cur = c | |
| 107 | + mkLazy(proc (): Value = | |
| 108 | + var cc = cur | |
| 109 | + if not hasNext(cc): return NilV | |
| 110 | + let x = next(cc) | |
| 111 | + mkCons(call(f, @[x]), lazyMap(f, cc))) | |
| 112 | + | |
| 113 | +proc lazyMapN(f: Value, cs: seq[Cursor]): Value = | |
| 114 | + let curs = cs | |
| 115 | + mkLazy(proc (): Value = | |
| 116 | + var ccs = curs | |
| 117 | + var args: seq[Value] = @[] | |
| 118 | + for i in 0 ..< ccs.len: | |
| 119 | + if not hasNext(ccs[i]): return NilV # stop at the shortest | |
| 120 | + args.add next(ccs[i]) | |
| 121 | + mkCons(call(f, args), lazyMapN(f, ccs))) | |
| 122 | + | |
| 123 | +proc lazyMapIndexed(f: Value, i: int64, c: Cursor): Value = | |
| 124 | + let cur = c | |
| 125 | + mkLazy(proc (): Value = | |
| 126 | + var cc = cur | |
| 127 | + if not hasNext(cc): return NilV | |
| 128 | + let x = next(cc) | |
| 129 | + mkCons(call(f, @[mkInt(i), x]), lazyMapIndexed(f, i + 1, cc))) | |
| 130 | + | |
| 131 | +proc lazyFilter(pred: Value, c: Cursor, keep: bool): Value = | |
| 132 | + let cur = c | |
| 133 | + mkLazy(proc (): Value = | |
| 134 | + var cc = cur | |
| 135 | + while hasNext(cc): | |
| 136 | + let x = next(cc) | |
| 137 | + if truthy(call(pred, @[x])) == keep: | |
| 138 | + return mkCons(x, lazyFilter(pred, cc, keep)) | |
| 139 | + NilV) | |
| 140 | + | |
| 141 | +proc lazyTake(n: int, c: Cursor): Value = | |
| 142 | + if n <= 0: return mkList(@[]) | |
| 143 | + let cur = c | |
| 144 | + mkLazy(proc (): Value = | |
| 145 | + var cc = cur | |
| 146 | + if not hasNext(cc): return NilV | |
| 147 | + let x = next(cc) | |
| 148 | + mkCons(x, lazyTake(n - 1, cc))) | |
| 149 | + | |
| 150 | +proc lazyDrop(n: int, c: Cursor): Value = | |
| 151 | + let cur = c | |
| 152 | + mkLazy(proc (): Value = | |
| 153 | + var cc = cur | |
| 154 | + var k = n | |
| 155 | + while k > 0 and hasNext(cc): discard next(cc); dec k | |
| 156 | + force(lazyOf(cc))) | |
| 157 | + | |
| 158 | +proc lazyTakeWhile(pred: Value, c: Cursor): Value = | |
| 159 | + let cur = c | |
| 160 | + mkLazy(proc (): Value = | |
| 161 | + var cc = cur | |
| 162 | + if not hasNext(cc): return NilV | |
| 163 | + let x = next(cc) | |
| 164 | + if not truthy(call(pred, @[x])): return NilV | |
| 165 | + mkCons(x, lazyTakeWhile(pred, cc))) | |
| 166 | + | |
| 167 | +proc lazyDropWhile(pred: Value, c: Cursor): Value = | |
| 168 | + let cur = c | |
| 169 | + mkLazy(proc (): Value = | |
| 170 | + var cc = cur | |
| 171 | + while true: | |
| 172 | + var peek = cc | |
| 173 | + if not hasNext(peek): return NilV | |
| 174 | + let x = next(peek) | |
| 175 | + if not truthy(call(pred, @[x])): return mkCons(x, lazyOf(peek)) | |
| 176 | + cc = peek) | |
| 177 | + | |
| 178 | +proc lazyRange(i, hi, step: int64, bounded: bool): Value = | |
| 179 | + mkLazy(proc (): Value = | |
| 180 | + if bounded and ((step > 0 and i >= hi) or (step < 0 and i <= hi)): return NilV | |
| 181 | + mkCons(mkInt(i), lazyRange(i + step, hi, step, bounded))) | |
| 182 | + | |
| 183 | +proc lazyIterate(f, x: Value): Value = | |
| 184 | + mkLazy(proc (): Value = mkCons(x, lazyIterate(f, call(f, @[x])))) | |
| 185 | + | |
| 186 | +proc lazyRepeat(x: Value, n: int64, bounded: bool): Value = | |
| 187 | + mkLazy(proc (): Value = | |
| 188 | + if bounded and n <= 0: return NilV | |
| 189 | + mkCons(x, lazyRepeat(x, n - 1, bounded))) | |
| 190 | + | |
| 191 | +proc lazyRepeatedly(f: Value, n: int64, bounded: bool): Value = | |
| 192 | + mkLazy(proc (): Value = | |
| 193 | + if bounded and n <= 0: return NilV | |
| 194 | + mkCons(call(f, @[]), lazyRepeatedly(f, n - 1, bounded))) | |
| 195 | + | |
| 196 | +proc lazyCycle(orig: Value, c: Cursor): Value = | |
| 197 | + let cur = c | |
| 198 | + mkLazy(proc (): Value = | |
| 199 | + var cc = cur | |
| 200 | + if not hasNext(cc): | |
| 201 | + cc = cursor(orig) # wrap around | |
| 202 | + if not hasNext(cc): return NilV # empty source: empty cycle | |
| 203 | + let x = next(cc) | |
| 204 | + mkCons(x, lazyCycle(orig, cc))) | |
| 205 | + | |
| 206 | +proc lazyConcat(colls: seq[Value], i: int, c: Cursor): Value = | |
| 207 | + let cur = c | |
| 208 | + mkLazy(proc (): Value = | |
| 209 | + var cc = cur | |
| 210 | + var k = i | |
| 211 | + while not hasNext(cc): | |
| 212 | + if k >= colls.len: return NilV | |
| 213 | + cc = cursor(colls[k]); inc k | |
| 214 | + let x = next(cc) | |
| 215 | + mkCons(x, lazyConcat(colls, k, cc))) | |
| 216 | + | |
| 90 | 217 | proc def(name: string, f: proc (args: seq[Value]): Value {.closure.}) = |
| 91 | 218 | setVar(name, mkFn(name, f)) |
| 92 | 219 | |
| @@ -165,7 +292,7 @@ proc registerCore*() = | ||
| 165 | 292 | def "coll?", proc (a: seq[Value]): Value = |
| 166 | 293 | mkBool(a[0].kind in {kList, kVector, kMap, kSet}) |
| 167 | 294 | def "fn?", proc (a: seq[Value]): Value = mkBool(a[0].kind == kFn) |
| 168 | - def "empty?", proc (a: seq[Value]): Value = mkBool(count(a[0]) == 0) | |
| 295 | + def "empty?", proc (a: seq[Value]): Value = mkBool(seqIsEmpty(a[0])) | |
| 169 | 296 | def "contains?", proc (a: seq[Value]): Value = |
| 170 | 297 | let c = a[0] |
| 171 | 298 | if c.isNil or c.kind == kNil: return FalseV |
| @@ -221,7 +348,7 @@ proc registerCore*() = | ||
| 221 | 348 | let sep = (if a.len > 1: str(a[0]) else: "") |
| 222 | 349 | let coll = (if a.len > 1: a[1] else: a[0]) |
| 223 | 350 | var parts: seq[string] = @[] |
| 224 | - for x in toSeq(coll): parts.add str(x) | |
| 351 | + for x in elems(coll): parts.add str(x) | |
| 225 | 352 | mkStr(parts.join(sep)) |
| 226 | 353 | def "read-line", proc (a: seq[Value]): Value = |
| 227 | 354 | try: mkStr(stdin.readLine()) except CatchableError: NilV |
| @@ -243,34 +370,29 @@ proc registerCore*() = | ||
| 243 | 370 | def "set", proc (a: seq[Value]): Value = mkSet(toSeq(a[0])) |
| 244 | 371 | def "vec", proc (a: seq[Value]): Value = mkVector(toSeq(a[0])) |
| 245 | 372 | def "seq", proc (a: seq[Value]): Value = |
| 246 | - let s = toSeq(a[0]) | |
| 247 | - (if s.len == 0: NilV else: mkList(s)) | |
| 373 | + # does not realize a lazy seq — just asks whether it has a first element | |
| 374 | + (if seqIsEmpty(a[0]): NilV else: a[0]) | |
| 248 | 375 | def "count", proc (a: seq[Value]): Value = |
| 249 | 376 | if a[0].isNil or a[0].kind == kNil: return mkInt(0) |
| 250 | 377 | mkInt(count(a[0])) |
| 251 | 378 | def "conj", proc (a: seq[Value]): Value = |
| 252 | 379 | result = a[0] |
| 253 | 380 | for i in 1 ..< a.len: result = conjOne(result, a[i]) |
| 254 | - def "cons", proc (a: seq[Value]): Value = mkList(@[a[0]] & toSeq(a[1])) | |
| 381 | + def "cons", proc (a: seq[Value]): Value = mkCons(a[0], a[1]) | |
| 255 | 382 | def "first", proc (a: seq[Value]): Value = |
| 256 | 383 | if a[0].kind == kVector: |
| 257 | 384 | return (if a[0].vec.cnt == 0: NilV else: vecNth(a[0].vec, 0)) |
| 258 | - let s = toSeq(a[0]) | |
| 259 | - (if s.len == 0: NilV else: s[0]) | |
| 260 | - def "second", proc (a: seq[Value]): Value = | |
| 261 | - let s = toSeq(a[0]) | |
| 262 | - (if s.len < 2: NilV else: s[1]) | |
| 385 | + seqFirst(a[0]) | |
| 386 | + def "second", proc (a: seq[Value]): Value = seqFirst(seqRest(a[0])) | |
| 263 | 387 | def "last", proc (a: seq[Value]): Value = |
| 264 | 388 | if a[0].kind == kVector: |
| 265 | 389 | return (if a[0].vec.cnt == 0: NilV else: vecNth(a[0].vec, a[0].vec.cnt - 1)) |
| 266 | - let s = toSeq(a[0]) | |
| 267 | - (if s.len == 0: NilV else: s[^1]) | |
| 268 | - def "rest", proc (a: seq[Value]): Value = | |
| 269 | - let s = toSeq(a[0]) | |
| 270 | - (if s.len <= 1: mkList(@[]) else: mkList(s[1 .. ^1])) | |
| 390 | + result = NilV | |
| 391 | + for x in elems(a[0]): result = x | |
| 392 | + def "rest", proc (a: seq[Value]): Value = seqRest(a[0]) | |
| 271 | 393 | def "next", proc (a: seq[Value]): Value = |
| 272 | - let s = toSeq(a[0]) | |
| 273 | - (if s.len <= 1: NilV else: mkList(s[1 .. ^1])) | |
| 394 | + let r = seqRest(a[0]) | |
| 395 | + (if seqIsEmpty(r): NilV else: r) | |
| 274 | 396 | def "nth", proc (a: seq[Value]): Value = |
| 275 | 397 | let i = int(intOf(a[1])) |
| 276 | 398 | if a[0].kind == kVector: |
| @@ -278,9 +400,15 @@ proc registerCore*() = | ||
| 278 | 400 | if i >= 0 and i < a[0].vec.cnt: return vecNth(a[0].vec, i) |
| 279 | 401 | if a.len > 2: return a[2] |
| 280 | 402 | err("Index out of bounds: " & $i) |
| 281 | - let s = toSeq(a[0]) | |
| 282 | - if i >= 0 and i < s.len: s[i] | |
| 283 | - elif a.len > 2: a[2] | |
| 403 | + if i >= 0: | |
| 404 | + # walks the seq, realizing no more of it than the index demands | |
| 405 | + var k = i | |
| 406 | + var c = cursor(a[0]) | |
| 407 | + while hasNext(c): | |
| 408 | + let x = next(c) | |
| 409 | + if k == 0: return x | |
| 410 | + dec k | |
| 411 | + if a.len > 2: a[2] | |
| 284 | 412 | else: err("Index out of bounds: " & $i) |
| 285 | 413 | def "get", proc (a: seq[Value]): Value = |
| 286 | 414 | getIn(a[0], a[1], (if a.len > 2: a[2] else: NilV)) |
| @@ -310,7 +438,7 @@ proc registerCore*() = | ||
| 310 | 438 | for e in mapEntries(a[0].m): r.add e.val |
| 311 | 439 | (if r.len == 0: NilV else: mkList(r)) |
| 312 | 440 | def "reverse", proc (a: seq[Value]): Value = |
| 313 | - var s = toSeq(a[0]) | |
| 441 | + let s = toSeq(a[0]) | |
| 314 | 442 | var r: seq[Value] = @[] |
| 315 | 443 | for i in countdown(s.len - 1, 0): r.add s[i] |
| 316 | 444 | mkList(r) |
| @@ -322,26 +450,26 @@ proc registerCore*() = | ||
| 322 | 450 | elif a.len >= 2: |
| 323 | 451 | lo = intOf(a[0]); hi = intOf(a[1]) |
| 324 | 452 | if a.len > 2: step = intOf(a[2]) |
| 325 | - var r: seq[Value] = @[] | |
| 326 | - if step > 0: | |
| 327 | - var i = lo | |
| 328 | - while i < hi: r.add mkInt(i); i += step | |
| 329 | - elif step < 0: | |
| 330 | - var i = lo | |
| 331 | - while i > hi: r.add mkInt(i); i += step | |
| 332 | - mkList(r) | |
| 453 | + # (range) with no bound is infinite; everything else stops at hi | |
| 454 | + lazyRange(lo, hi, step, bounded = a.len > 0) | |
| 333 | 455 | def "take", proc (a: seq[Value]): Value = |
| 334 | - let n = int(intOf(a[0])) | |
| 335 | - let s = toSeq(a[1]) | |
| 336 | - mkList(s[0 ..< min(n, s.len)]) | |
| 456 | + lazyTake(int(intOf(a[0])), cursor(a[1])) | |
| 337 | 457 | def "drop", proc (a: seq[Value]): Value = |
| 338 | - let n = int(intOf(a[0])) | |
| 339 | - let s = toSeq(a[1]) | |
| 340 | - (if n >= s.len: mkList(@[]) else: mkList(s[n .. ^1])) | |
| 458 | + lazyDrop(int(intOf(a[0])), cursor(a[1])) | |
| 341 | 459 | def "concat", proc (a: seq[Value]): Value = |
| 342 | - var r: seq[Value] = @[] | |
| 343 | - for x in a: r.add toSeq(x) | |
| 344 | - mkList(r) | |
| 460 | + lazyConcat(a, 0, cursor(NilV)) | |
| 461 | + def "iterate", proc (a: seq[Value]): Value = lazyIterate(a[0], a[1]) | |
| 462 | + def "repeat", proc (a: seq[Value]): Value = | |
| 463 | + (if a.len == 1: lazyRepeat(a[0], 0, bounded = false) | |
| 464 | + else: lazyRepeat(a[1], intOf(a[0]), bounded = true)) | |
| 465 | + def "repeatedly", proc (a: seq[Value]): Value = | |
| 466 | + (if a.len == 1: lazyRepeatedly(a[0], 0, bounded = false) | |
| 467 | + else: lazyRepeatedly(a[1], intOf(a[0]), bounded = true)) | |
| 468 | + def "cycle", proc (a: seq[Value]): Value = lazyCycle(a[0], cursor(a[0])) | |
| 469 | + def "doall", proc (a: seq[Value]): Value = mkList(toSeq(a[0])) | |
| 470 | + def "dorun", proc (a: seq[Value]): Value = | |
| 471 | + for x in elems(a[0]): discard | |
| 472 | + NilV | |
| 345 | 473 | def "sort", proc (a: seq[Value]): Value = |
| 346 | 474 | var s = toSeq(a[^1]) |
| 347 | 475 | let cmpFn = (if a.len > 1: a[0] else: NilV) |
| @@ -370,7 +498,7 @@ proc registerCore*() = | ||
| 370 | 498 | mkList(s) |
| 371 | 499 | def "distinct", proc (a: seq[Value]): Value = |
| 372 | 500 | var r: seq[Value] = @[] |
| 373 | - for x in toSeq(a[0]): | |
| 501 | + for x in elems(a[0]): | |
| 374 | 502 | var dup = false |
| 375 | 503 | for y in r: |
| 376 | 504 | if equals(x, y): dup = true; break |
| @@ -378,7 +506,7 @@ proc registerCore*() = | ||
| 378 | 506 | mkList(r) |
| 379 | 507 | def "interpose", proc (a: seq[Value]): Value = |
| 380 | 508 | var r: seq[Value] = @[] |
| 381 | - for x in toSeq(a[1]): | |
| 509 | + for x in elems(a[1]): | |
| 382 | 510 | if r.len > 0: r.add a[0] |
| 383 | 511 | r.add x |
| 384 | 512 | mkList(r) |
| @@ -398,41 +526,20 @@ proc registerCore*() = | ||
| 398 | 526 | callArgs.add toSeq(a[^1]) |
| 399 | 527 | call(a[0], callArgs) |
| 400 | 528 | def "map", proc (a: seq[Value]): Value = |
| 401 | - let f = a[0] | |
| 402 | - if a.len == 2: | |
| 403 | - var r: seq[Value] = @[] | |
| 404 | - for x in toSeq(a[1]): r.add call(f, @[x]) | |
| 405 | - return mkList(r) | |
| 406 | - var colls: seq[seq[Value]] = @[] | |
| 407 | - for i in 1 ..< a.len: colls.add toSeq(a[i]) | |
| 408 | - var n = colls[0].len | |
| 409 | - for c in colls: n = min(n, c.len) | |
| 410 | - var r: seq[Value] = @[] | |
| 411 | - for i in 0 ..< n: | |
| 412 | - var args: seq[Value] = @[] | |
| 413 | - for c in colls: args.add c[i] | |
| 414 | - r.add call(f, args) | |
| 415 | - mkList(r) | |
| 529 | + if a.len == 2: return lazyMap(a[0], cursor(a[1])) | |
| 530 | + var cs: seq[Cursor] = @[] | |
| 531 | + for i in 1 ..< a.len: cs.add cursor(a[i]) | |
| 532 | + lazyMapN(a[0], cs) | |
| 416 | 533 | def "mapv", proc (a: seq[Value]): Value = |
| 417 | 534 | var r: seq[Value] = @[] |
| 418 | - for x in toSeq(a[1]): r.add call(a[0], @[x]) | |
| 535 | + for x in elems(a[1]): r.add call(a[0], @[x]) | |
| 419 | 536 | mkVector(r) |
| 420 | 537 | def "map-indexed", proc (a: seq[Value]): Value = |
| 421 | - var r: seq[Value] = @[] | |
| 422 | - var i = 0 | |
| 423 | - for x in toSeq(a[1]): | |
| 424 | - r.add call(a[0], @[mkInt(i), x]); inc i | |
| 425 | - mkList(r) | |
| 538 | + lazyMapIndexed(a[0], 0, cursor(a[1])) | |
| 426 | 539 | def "filter", proc (a: seq[Value]): Value = |
| 427 | - var r: seq[Value] = @[] | |
| 428 | - for x in toSeq(a[1]): | |
| 429 | - if truthy(call(a[0], @[x])): r.add x | |
| 430 | - mkList(r) | |
| 540 | + lazyFilter(a[0], cursor(a[1]), keep = true) | |
| 431 | 541 | def "remove", proc (a: seq[Value]): Value = |
| 432 | - var r: seq[Value] = @[] | |
| 433 | - for x in toSeq(a[1]): | |
| 434 | - if not truthy(call(a[0], @[x])): r.add x | |
| 435 | - mkList(r) | |
| 542 | + lazyFilter(a[0], cursor(a[1]), keep = false) | |
| 436 | 543 | def "reduce", proc (a: seq[Value]): Value = |
| 437 | 544 | let f = a[0] |
| 438 | 545 | if a.len == 2: |
| @@ -442,40 +549,30 @@ proc registerCore*() = | ||
| 442 | 549 | for i in 1 ..< s.len: acc = call(f, @[acc, s[i]]) |
| 443 | 550 | return acc |
| 444 | 551 | var acc = a[1] |
| 445 | - for x in toSeq(a[2]): acc = call(f, @[acc, x]) | |
| 552 | + for x in elems(a[2]): acc = call(f, @[acc, x]) | |
| 446 | 553 | acc |
| 447 | 554 | def "some", proc (a: seq[Value]): Value = |
| 448 | - for x in toSeq(a[1]): | |
| 555 | + for x in elems(a[1]): | |
| 449 | 556 | let r = call(a[0], @[x]) |
| 450 | 557 | if truthy(r): return r |
| 451 | 558 | NilV |
| 452 | 559 | def "every?", proc (a: seq[Value]): Value = |
| 453 | - for x in toSeq(a[1]): | |
| 560 | + for x in elems(a[1]): | |
| 454 | 561 | if not truthy(call(a[0], @[x])): return FalseV |
| 455 | 562 | TrueV |
| 456 | 563 | def "take-while", proc (a: seq[Value]): Value = |
| 457 | - var r: seq[Value] = @[] | |
| 458 | - for x in toSeq(a[1]): | |
| 459 | - if not truthy(call(a[0], @[x])): break | |
| 460 | - r.add x | |
| 461 | - mkList(r) | |
| 564 | + lazyTakeWhile(a[0], cursor(a[1])) | |
| 462 | 565 | def "drop-while", proc (a: seq[Value]): Value = |
| 463 | - var r: seq[Value] = @[] | |
| 464 | - var dropping = true | |
| 465 | - for x in toSeq(a[1]): | |
| 466 | - if dropping and truthy(call(a[0], @[x])): continue | |
| 467 | - dropping = false | |
| 468 | - r.add x | |
| 469 | - mkList(r) | |
| 566 | + lazyDropWhile(a[0], cursor(a[1])) | |
| 470 | 567 | def "group-by", proc (a: seq[Value]): Value = |
| 471 | 568 | var m = emptyPMap() |
| 472 | - for x in toSeq(a[1]): | |
| 569 | + for x in elems(a[1]): | |
| 473 | 570 | let k = call(a[0], @[x]) |
| 474 | 571 | m = mapAssoc(m, k, conjOne(mapGet(m, k, mkVector(@[])), x)) |
| 475 | 572 | mkMapOf(m) |
| 476 | 573 | def "frequencies", proc (a: seq[Value]): Value = |
| 477 | 574 | var m = emptyPMap() |
| 478 | - for x in toSeq(a[0]): | |
| 575 | + for x in elems(a[0]): | |
| 479 | 576 | m = mapAssoc(m, x, mkInt(mapGet(m, x, mkInt(0)).i + 1)) |
| 480 | 577 | mkMapOf(m) |
| 481 | 578 | def "identity", proc (a: seq[Value]): Value = a[0] |
| @@ -87,6 +87,133 @@ proc conjOne(coll, x: Value): Value = | |||
| 87 | else: err("conj on map needs a pair") | 87 | else: err("conj on map needs a pair") |
| 88 | else: err("conj not supported on " & prStr(coll)) | 88 | else: err("conj not supported on " & prStr(coll)) |
| 89 | 89 | ||
| 90 | +# ------------------------------------------------------------- lazy seqs | ||
| 91 | +## Producers share one shape: capture a Cursor, and return a thunk that | ||
| 92 | +## advances a *copy* of it, yields one cons cell, and hands the advanced copy | ||
| 93 | +## to the next thunk. Because each thunk only ever takes one step, an infinite | ||
| 94 | +## source costs exactly as much as the consumer asks for. | ||
| 95 | + | ||
| 96 | +proc lazyOf(c: Cursor): Value = | ||
| 97 | + ## The remainder of a cursor, as a lazy seq. | ||
| 98 | + let cur = c | ||
| 99 | + mkLazy(proc (): Value = | ||
| 100 | + var cc = cur | ||
| 101 | + if not hasNext(cc): return NilV | ||
| 102 | + let x = next(cc) | ||
| 103 | + mkCons(x, lazyOf(cc))) | ||
| 104 | + | ||
| 105 | +proc lazyMap(f: Value, c: Cursor): Value = | ||
| 106 | + let cur = c | ||
| 107 | + mkLazy(proc (): Value = | ||
| 108 | + var cc = cur | ||
| 109 | + if not hasNext(cc): return NilV | ||
| 110 | + let x = next(cc) | ||
| 111 | + mkCons(call(f, @[x]), lazyMap(f, cc))) | ||
| 112 | + | ||
| 113 | +proc lazyMapN(f: Value, cs: seq[Cursor]): Value = | ||
| 114 | + let curs = cs | ||
| 115 | + mkLazy(proc (): Value = | ||
| 116 | + var ccs = curs | ||
| 117 | + var args: seq[Value] = @[] | ||
| 118 | + for i in 0 ..< ccs.len: | ||
| 119 | + if not hasNext(ccs[i]): return NilV # stop at the shortest | ||
| 120 | + args.add next(ccs[i]) | ||
| 121 | + mkCons(call(f, args), lazyMapN(f, ccs))) | ||
| 122 | + | ||
| 123 | +proc lazyMapIndexed(f: Value, i: int64, c: Cursor): Value = | ||
| 124 | + let cur = c | ||
| 125 | + mkLazy(proc (): Value = | ||
| 126 | + var cc = cur | ||
| 127 | + if not hasNext(cc): return NilV | ||
| 128 | + let x = next(cc) | ||
| 129 | + mkCons(call(f, @[mkInt(i), x]), lazyMapIndexed(f, i + 1, cc))) | ||
| 130 | + | ||
| 131 | +proc lazyFilter(pred: Value, c: Cursor, keep: bool): Value = | ||
| 132 | + let cur = c | ||
| 133 | + mkLazy(proc (): Value = | ||
| 134 | + var cc = cur | ||
| 135 | + while hasNext(cc): | ||
| 136 | + let x = next(cc) | ||
| 137 | + if truthy(call(pred, @[x])) == keep: | ||
| 138 | + return mkCons(x, lazyFilter(pred, cc, keep)) | ||
| 139 | + NilV) | ||
| 140 | + | ||
| 141 | +proc lazyTake(n: int, c: Cursor): Value = | ||
| 142 | + if n <= 0: return mkList(@[]) | ||
| 143 | + let cur = c | ||
| 144 | + mkLazy(proc (): Value = | ||
| 145 | + var cc = cur | ||
| 146 | + if not hasNext(cc): return NilV | ||
| 147 | + let x = next(cc) | ||
| 148 | + mkCons(x, lazyTake(n - 1, cc))) | ||
| 149 | + | ||
| 150 | +proc lazyDrop(n: int, c: Cursor): Value = | ||
| 151 | + let cur = c | ||
| 152 | + mkLazy(proc (): Value = | ||
| 153 | + var cc = cur | ||
| 154 | + var k = n | ||
| 155 | + while k > 0 and hasNext(cc): discard next(cc); dec k | ||
| 156 | + force(lazyOf(cc))) | ||
| 157 | + | ||
| 158 | +proc lazyTakeWhile(pred: Value, c: Cursor): Value = | ||
| 159 | + let cur = c | ||
| 160 | + mkLazy(proc (): Value = | ||
| 161 | + var cc = cur | ||
| 162 | + if not hasNext(cc): return NilV | ||
| 163 | + let x = next(cc) | ||
| 164 | + if not truthy(call(pred, @[x])): return NilV | ||
| 165 | + mkCons(x, lazyTakeWhile(pred, cc))) | ||
| 166 | + | ||
| 167 | +proc lazyDropWhile(pred: Value, c: Cursor): Value = | ||
| 168 | + let cur = c | ||
| 169 | + mkLazy(proc (): Value = | ||
| 170 | + var cc = cur | ||
| 171 | + while true: | ||
| 172 | + var peek = cc | ||
| 173 | + if not hasNext(peek): return NilV | ||
| 174 | + let x = next(peek) | ||
| 175 | + if not truthy(call(pred, @[x])): return mkCons(x, lazyOf(peek)) | ||
| 176 | + cc = peek) | ||
| 177 | + | ||
| 178 | +proc lazyRange(i, hi, step: int64, bounded: bool): Value = | ||
| 179 | + mkLazy(proc (): Value = | ||
| 180 | + if bounded and ((step > 0 and i >= hi) or (step < 0 and i <= hi)): return NilV | ||
| 181 | + mkCons(mkInt(i), lazyRange(i + step, hi, step, bounded))) | ||
| 182 | + | ||
| 183 | +proc lazyIterate(f, x: Value): Value = | ||
| 184 | + mkLazy(proc (): Value = mkCons(x, lazyIterate(f, call(f, @[x])))) | ||
| 185 | + | ||
| 186 | +proc lazyRepeat(x: Value, n: int64, bounded: bool): Value = | ||
| 187 | + mkLazy(proc (): Value = | ||
| 188 | + if bounded and n <= 0: return NilV | ||
| 189 | + mkCons(x, lazyRepeat(x, n - 1, bounded))) | ||
| 190 | + | ||
| 191 | +proc lazyRepeatedly(f: Value, n: int64, bounded: bool): Value = | ||
| 192 | + mkLazy(proc (): Value = | ||
| 193 | + if bounded and n <= 0: return NilV | ||
| 194 | + mkCons(call(f, @[]), lazyRepeatedly(f, n - 1, bounded))) | ||
| 195 | + | ||
| 196 | +proc lazyCycle(orig: Value, c: Cursor): Value = | ||
| 197 | + let cur = c | ||
| 198 | + mkLazy(proc (): Value = | ||
| 199 | + var cc = cur | ||
| 200 | + if not hasNext(cc): | ||
| 201 | + cc = cursor(orig) # wrap around | ||
| 202 | + if not hasNext(cc): return NilV # empty source: empty cycle | ||
| 203 | + let x = next(cc) | ||
| 204 | + mkCons(x, lazyCycle(orig, cc))) | ||
| 205 | + | ||
| 206 | +proc lazyConcat(colls: seq[Value], i: int, c: Cursor): Value = | ||
| 207 | + let cur = c | ||
| 208 | + mkLazy(proc (): Value = | ||
| 209 | + var cc = cur | ||
| 210 | + var k = i | ||
| 211 | + while not hasNext(cc): | ||
| 212 | + if k >= colls.len: return NilV | ||
| 213 | + cc = cursor(colls[k]); inc k | ||
| 214 | + let x = next(cc) | ||
| 215 | + mkCons(x, lazyConcat(colls, k, cc))) | ||
| 216 | + | ||
| 90 | proc def(name: string, f: proc (args: seq[Value]): Value {.closure.}) = | 217 | proc def(name: string, f: proc (args: seq[Value]): Value {.closure.}) = |
| 91 | setVar(name, mkFn(name, f)) | 218 | setVar(name, mkFn(name, f)) |
| 92 | 219 | ||
| @@ -165,7 +292,7 @@ proc registerCore*() = | |||
| 165 | def "coll?", proc (a: seq[Value]): Value = | 292 | def "coll?", proc (a: seq[Value]): Value = |
| 166 | mkBool(a[0].kind in {kList, kVector, kMap, kSet}) | 293 | mkBool(a[0].kind in {kList, kVector, kMap, kSet}) |
| 167 | def "fn?", proc (a: seq[Value]): Value = mkBool(a[0].kind == kFn) | 294 | def "fn?", proc (a: seq[Value]): Value = mkBool(a[0].kind == kFn) |
| 168 | - def "empty?", proc (a: seq[Value]): Value = mkBool(count(a[0]) == 0) | 295 | + def "empty?", proc (a: seq[Value]): Value = mkBool(seqIsEmpty(a[0])) |
| 169 | def "contains?", proc (a: seq[Value]): Value = | 296 | def "contains?", proc (a: seq[Value]): Value = |
| 170 | let c = a[0] | 297 | let c = a[0] |
| 171 | if c.isNil or c.kind == kNil: return FalseV | 298 | if c.isNil or c.kind == kNil: return FalseV |
| @@ -221,7 +348,7 @@ proc registerCore*() = | |||
| 221 | let sep = (if a.len > 1: str(a[0]) else: "") | 348 | let sep = (if a.len > 1: str(a[0]) else: "") |
| 222 | let coll = (if a.len > 1: a[1] else: a[0]) | 349 | let coll = (if a.len > 1: a[1] else: a[0]) |
| 223 | var parts: seq[string] = @[] | 350 | var parts: seq[string] = @[] |
| 224 | - for x in toSeq(coll): parts.add str(x) | 351 | + for x in elems(coll): parts.add str(x) |
| 225 | mkStr(parts.join(sep)) | 352 | mkStr(parts.join(sep)) |
| 226 | def "read-line", proc (a: seq[Value]): Value = | 353 | def "read-line", proc (a: seq[Value]): Value = |
| 227 | try: mkStr(stdin.readLine()) except CatchableError: NilV | 354 | try: mkStr(stdin.readLine()) except CatchableError: NilV |
| @@ -243,34 +370,29 @@ proc registerCore*() = | |||
| 243 | def "set", proc (a: seq[Value]): Value = mkSet(toSeq(a[0])) | 370 | def "set", proc (a: seq[Value]): Value = mkSet(toSeq(a[0])) |
| 244 | def "vec", proc (a: seq[Value]): Value = mkVector(toSeq(a[0])) | 371 | def "vec", proc (a: seq[Value]): Value = mkVector(toSeq(a[0])) |
| 245 | def "seq", proc (a: seq[Value]): Value = | 372 | def "seq", proc (a: seq[Value]): Value = |
| 246 | - let s = toSeq(a[0]) | 373 | + # does not realize a lazy seq — just asks whether it has a first element |
| 247 | - (if s.len == 0: NilV else: mkList(s)) | 374 | + (if seqIsEmpty(a[0]): NilV else: a[0]) |
| 248 | def "count", proc (a: seq[Value]): Value = | 375 | def "count", proc (a: seq[Value]): Value = |
| 249 | if a[0].isNil or a[0].kind == kNil: return mkInt(0) | 376 | if a[0].isNil or a[0].kind == kNil: return mkInt(0) |
| 250 | mkInt(count(a[0])) | 377 | mkInt(count(a[0])) |
| 251 | def "conj", proc (a: seq[Value]): Value = | 378 | def "conj", proc (a: seq[Value]): Value = |
| 252 | result = a[0] | 379 | result = a[0] |
| 253 | for i in 1 ..< a.len: result = conjOne(result, a[i]) | 380 | for i in 1 ..< a.len: result = conjOne(result, a[i]) |
| 254 | - def "cons", proc (a: seq[Value]): Value = mkList(@[a[0]] & toSeq(a[1])) | 381 | + def "cons", proc (a: seq[Value]): Value = mkCons(a[0], a[1]) |
| 255 | def "first", proc (a: seq[Value]): Value = | 382 | def "first", proc (a: seq[Value]): Value = |
| 256 | if a[0].kind == kVector: | 383 | if a[0].kind == kVector: |
| 257 | return (if a[0].vec.cnt == 0: NilV else: vecNth(a[0].vec, 0)) | 384 | return (if a[0].vec.cnt == 0: NilV else: vecNth(a[0].vec, 0)) |
| 258 | - let s = toSeq(a[0]) | 385 | + seqFirst(a[0]) |
| 259 | - (if s.len == 0: NilV else: s[0]) | 386 | + def "second", proc (a: seq[Value]): Value = seqFirst(seqRest(a[0])) |
| 260 | - def "second", proc (a: seq[Value]): Value = | ||
| 261 | - let s = toSeq(a[0]) | ||
| 262 | - (if s.len < 2: NilV else: s[1]) | ||
| 263 | def "last", proc (a: seq[Value]): Value = | 387 | def "last", proc (a: seq[Value]): Value = |
| 264 | if a[0].kind == kVector: | 388 | if a[0].kind == kVector: |
| 265 | return (if a[0].vec.cnt == 0: NilV else: vecNth(a[0].vec, a[0].vec.cnt - 1)) | 389 | return (if a[0].vec.cnt == 0: NilV else: vecNth(a[0].vec, a[0].vec.cnt - 1)) |
| 266 | - let s = toSeq(a[0]) | 390 | + result = NilV |
| 267 | - (if s.len == 0: NilV else: s[^1]) | 391 | + for x in elems(a[0]): result = x |
| 268 | - def "rest", proc (a: seq[Value]): Value = | 392 | + def "rest", proc (a: seq[Value]): Value = seqRest(a[0]) |
| 269 | - let s = toSeq(a[0]) | ||
| 270 | - (if s.len <= 1: mkList(@[]) else: mkList(s[1 .. ^1])) | ||
| 271 | def "next", proc (a: seq[Value]): Value = | 393 | def "next", proc (a: seq[Value]): Value = |
| 272 | - let s = toSeq(a[0]) | 394 | + let r = seqRest(a[0]) |
| 273 | - (if s.len <= 1: NilV else: mkList(s[1 .. ^1])) | 395 | + (if seqIsEmpty(r): NilV else: r) |
| 274 | def "nth", proc (a: seq[Value]): Value = | 396 | def "nth", proc (a: seq[Value]): Value = |
| 275 | let i = int(intOf(a[1])) | 397 | let i = int(intOf(a[1])) |
| 276 | if a[0].kind == kVector: | 398 | if a[0].kind == kVector: |
| @@ -278,9 +400,15 @@ proc registerCore*() = | |||
| 278 | if i >= 0 and i < a[0].vec.cnt: return vecNth(a[0].vec, i) | 400 | if i >= 0 and i < a[0].vec.cnt: return vecNth(a[0].vec, i) |
| 279 | if a.len > 2: return a[2] | 401 | if a.len > 2: return a[2] |
| 280 | err("Index out of bounds: " & $i) | 402 | err("Index out of bounds: " & $i) |
| 281 | - let s = toSeq(a[0]) | 403 | + if i >= 0: |
| 282 | - if i >= 0 and i < s.len: s[i] | 404 | + # walks the seq, realizing no more of it than the index demands |
| 283 | - elif a.len > 2: a[2] | 405 | + var k = i |
| 406 | + var c = cursor(a[0]) | ||
| 407 | + while hasNext(c): | ||
| 408 | + let x = next(c) | ||
| 409 | + if k == 0: return x | ||
| 410 | + dec k | ||
| 411 | + if a.len > 2: a[2] | ||
| 284 | else: err("Index out of bounds: " & $i) | 412 | else: err("Index out of bounds: " & $i) |
| 285 | def "get", proc (a: seq[Value]): Value = | 413 | def "get", proc (a: seq[Value]): Value = |
| 286 | getIn(a[0], a[1], (if a.len > 2: a[2] else: NilV)) | 414 | getIn(a[0], a[1], (if a.len > 2: a[2] else: NilV)) |
| @@ -310,7 +438,7 @@ proc registerCore*() = | |||
| 310 | for e in mapEntries(a[0].m): r.add e.val | 438 | for e in mapEntries(a[0].m): r.add e.val |
| 311 | (if r.len == 0: NilV else: mkList(r)) | 439 | (if r.len == 0: NilV else: mkList(r)) |
| 312 | def "reverse", proc (a: seq[Value]): Value = | 440 | def "reverse", proc (a: seq[Value]): Value = |
| 313 | - var s = toSeq(a[0]) | 441 | + let s = toSeq(a[0]) |
| 314 | var r: seq[Value] = @[] | 442 | var r: seq[Value] = @[] |
| 315 | for i in countdown(s.len - 1, 0): r.add s[i] | 443 | for i in countdown(s.len - 1, 0): r.add s[i] |
| 316 | mkList(r) | 444 | mkList(r) |
| @@ -322,26 +450,26 @@ proc registerCore*() = | |||
| 322 | elif a.len >= 2: | 450 | elif a.len >= 2: |
| 323 | lo = intOf(a[0]); hi = intOf(a[1]) | 451 | lo = intOf(a[0]); hi = intOf(a[1]) |
| 324 | if a.len > 2: step = intOf(a[2]) | 452 | if a.len > 2: step = intOf(a[2]) |
| 325 | - var r: seq[Value] = @[] | 453 | + # (range) with no bound is infinite; everything else stops at hi |
| 326 | - if step > 0: | 454 | + lazyRange(lo, hi, step, bounded = a.len > 0) |
| 327 | - var i = lo | ||
| 328 | - while i < hi: r.add mkInt(i); i += step | ||
| 329 | - elif step < 0: | ||
| 330 | - var i = lo | ||
| 331 | - while i > hi: r.add mkInt(i); i += step | ||
| 332 | - mkList(r) | ||
| 333 | def "take", proc (a: seq[Value]): Value = | 455 | def "take", proc (a: seq[Value]): Value = |
| 334 | - let n = int(intOf(a[0])) | 456 | + lazyTake(int(intOf(a[0])), cursor(a[1])) |
| 335 | - let s = toSeq(a[1]) | ||
| 336 | - mkList(s[0 ..< min(n, s.len)]) | ||
| 337 | def "drop", proc (a: seq[Value]): Value = | 457 | def "drop", proc (a: seq[Value]): Value = |
| 338 | - let n = int(intOf(a[0])) | 458 | + lazyDrop(int(intOf(a[0])), cursor(a[1])) |
| 339 | - let s = toSeq(a[1]) | ||
| 340 | - (if n >= s.len: mkList(@[]) else: mkList(s[n .. ^1])) | ||
| 341 | def "concat", proc (a: seq[Value]): Value = | 459 | def "concat", proc (a: seq[Value]): Value = |
| 342 | - var r: seq[Value] = @[] | 460 | + lazyConcat(a, 0, cursor(NilV)) |
| 343 | - for x in a: r.add toSeq(x) | 461 | + def "iterate", proc (a: seq[Value]): Value = lazyIterate(a[0], a[1]) |
| 344 | - mkList(r) | 462 | + def "repeat", proc (a: seq[Value]): Value = |
| 463 | + (if a.len == 1: lazyRepeat(a[0], 0, bounded = false) | ||
| 464 | + else: lazyRepeat(a[1], intOf(a[0]), bounded = true)) | ||
| 465 | + def "repeatedly", proc (a: seq[Value]): Value = | ||
| 466 | + (if a.len == 1: lazyRepeatedly(a[0], 0, bounded = false) | ||
| 467 | + else: lazyRepeatedly(a[1], intOf(a[0]), bounded = true)) | ||
| 468 | + def "cycle", proc (a: seq[Value]): Value = lazyCycle(a[0], cursor(a[0])) | ||
| 469 | + def "doall", proc (a: seq[Value]): Value = mkList(toSeq(a[0])) | ||
| 470 | + def "dorun", proc (a: seq[Value]): Value = | ||
| 471 | + for x in elems(a[0]): discard | ||
| 472 | + NilV | ||
| 345 | def "sort", proc (a: seq[Value]): Value = | 473 | def "sort", proc (a: seq[Value]): Value = |
| 346 | var s = toSeq(a[^1]) | 474 | var s = toSeq(a[^1]) |
| 347 | let cmpFn = (if a.len > 1: a[0] else: NilV) | 475 | let cmpFn = (if a.len > 1: a[0] else: NilV) |
| @@ -370,7 +498,7 @@ proc registerCore*() = | |||
| 370 | mkList(s) | 498 | mkList(s) |
| 371 | def "distinct", proc (a: seq[Value]): Value = | 499 | def "distinct", proc (a: seq[Value]): Value = |
| 372 | var r: seq[Value] = @[] | 500 | var r: seq[Value] = @[] |
| 373 | - for x in toSeq(a[0]): | 501 | + for x in elems(a[0]): |
| 374 | var dup = false | 502 | var dup = false |
| 375 | for y in r: | 503 | for y in r: |
| 376 | if equals(x, y): dup = true; break | 504 | if equals(x, y): dup = true; break |
| @@ -378,7 +506,7 @@ proc registerCore*() = | |||
| 378 | mkList(r) | 506 | mkList(r) |
| 379 | def "interpose", proc (a: seq[Value]): Value = | 507 | def "interpose", proc (a: seq[Value]): Value = |
| 380 | var r: seq[Value] = @[] | 508 | var r: seq[Value] = @[] |
| 381 | - for x in toSeq(a[1]): | 509 | + for x in elems(a[1]): |
| 382 | if r.len > 0: r.add a[0] | 510 | if r.len > 0: r.add a[0] |
| 383 | r.add x | 511 | r.add x |
| 384 | mkList(r) | 512 | mkList(r) |
| @@ -398,41 +526,20 @@ proc registerCore*() = | |||
| 398 | callArgs.add toSeq(a[^1]) | 526 | callArgs.add toSeq(a[^1]) |
| 399 | call(a[0], callArgs) | 527 | call(a[0], callArgs) |
| 400 | def "map", proc (a: seq[Value]): Value = | 528 | def "map", proc (a: seq[Value]): Value = |
| 401 | - let f = a[0] | 529 | + if a.len == 2: return lazyMap(a[0], cursor(a[1])) |
| 402 | - if a.len == 2: | 530 | + var cs: seq[Cursor] = @[] |
| 403 | - var r: seq[Value] = @[] | 531 | + for i in 1 ..< a.len: cs.add cursor(a[i]) |
| 404 | - for x in toSeq(a[1]): r.add call(f, @[x]) | 532 | + lazyMapN(a[0], cs) |
| 405 | - return mkList(r) | ||
| 406 | - var colls: seq[seq[Value]] = @[] | ||
| 407 | - for i in 1 ..< a.len: colls.add toSeq(a[i]) | ||
| 408 | - var n = colls[0].len | ||
| 409 | - for c in colls: n = min(n, c.len) | ||
| 410 | - var r: seq[Value] = @[] | ||
| 411 | - for i in 0 ..< n: | ||
| 412 | - var args: seq[Value] = @[] | ||
| 413 | - for c in colls: args.add c[i] | ||
| 414 | - r.add call(f, args) | ||
| 415 | - mkList(r) | ||
| 416 | def "mapv", proc (a: seq[Value]): Value = | 533 | def "mapv", proc (a: seq[Value]): Value = |
| 417 | var r: seq[Value] = @[] | 534 | var r: seq[Value] = @[] |
| 418 | - for x in toSeq(a[1]): r.add call(a[0], @[x]) | 535 | + for x in elems(a[1]): r.add call(a[0], @[x]) |
| 419 | mkVector(r) | 536 | mkVector(r) |
| 420 | def "map-indexed", proc (a: seq[Value]): Value = | 537 | def "map-indexed", proc (a: seq[Value]): Value = |
| 421 | - var r: seq[Value] = @[] | 538 | + lazyMapIndexed(a[0], 0, cursor(a[1])) |
| 422 | - var i = 0 | ||
| 423 | - for x in toSeq(a[1]): | ||
| 424 | - r.add call(a[0], @[mkInt(i), x]); inc i | ||
| 425 | - mkList(r) | ||
| 426 | def "filter", proc (a: seq[Value]): Value = | 539 | def "filter", proc (a: seq[Value]): Value = |
| 427 | - var r: seq[Value] = @[] | 540 | + lazyFilter(a[0], cursor(a[1]), keep = true) |
| 428 | - for x in toSeq(a[1]): | ||
| 429 | - if truthy(call(a[0], @[x])): r.add x | ||
| 430 | - mkList(r) | ||
| 431 | def "remove", proc (a: seq[Value]): Value = | 541 | def "remove", proc (a: seq[Value]): Value = |
| 432 | - var r: seq[Value] = @[] | 542 | + lazyFilter(a[0], cursor(a[1]), keep = false) |
| 433 | - for x in toSeq(a[1]): | ||
| 434 | - if not truthy(call(a[0], @[x])): r.add x | ||
| 435 | - mkList(r) | ||
| 436 | def "reduce", proc (a: seq[Value]): Value = | 543 | def "reduce", proc (a: seq[Value]): Value = |
| 437 | let f = a[0] | 544 | let f = a[0] |
| 438 | if a.len == 2: | 545 | if a.len == 2: |
| @@ -442,40 +549,30 @@ proc registerCore*() = | |||
| 442 | for i in 1 ..< s.len: acc = call(f, @[acc, s[i]]) | 549 | for i in 1 ..< s.len: acc = call(f, @[acc, s[i]]) |
| 443 | return acc | 550 | return acc |
| 444 | var acc = a[1] | 551 | var acc = a[1] |
| 445 | - for x in toSeq(a[2]): acc = call(f, @[acc, x]) | 552 | + for x in elems(a[2]): acc = call(f, @[acc, x]) |
| 446 | acc | 553 | acc |
| 447 | def "some", proc (a: seq[Value]): Value = | 554 | def "some", proc (a: seq[Value]): Value = |
| 448 | - for x in toSeq(a[1]): | 555 | + for x in elems(a[1]): |
| 449 | let r = call(a[0], @[x]) | 556 | let r = call(a[0], @[x]) |
| 450 | if truthy(r): return r | 557 | if truthy(r): return r |
| 451 | NilV | 558 | NilV |
| 452 | def "every?", proc (a: seq[Value]): Value = | 559 | def "every?", proc (a: seq[Value]): Value = |
| 453 | - for x in toSeq(a[1]): | 560 | + for x in elems(a[1]): |
| 454 | if not truthy(call(a[0], @[x])): return FalseV | 561 | if not truthy(call(a[0], @[x])): return FalseV |
| 455 | TrueV | 562 | TrueV |
| 456 | def "take-while", proc (a: seq[Value]): Value = | 563 | def "take-while", proc (a: seq[Value]): Value = |
| 457 | - var r: seq[Value] = @[] | 564 | + lazyTakeWhile(a[0], cursor(a[1])) |
| 458 | - for x in toSeq(a[1]): | ||
| 459 | - if not truthy(call(a[0], @[x])): break | ||
| 460 | - r.add x | ||
| 461 | - mkList(r) | ||
| 462 | def "drop-while", proc (a: seq[Value]): Value = | 565 | def "drop-while", proc (a: seq[Value]): Value = |
| 463 | - var r: seq[Value] = @[] | 566 | + lazyDropWhile(a[0], cursor(a[1])) |
| 464 | - var dropping = true | ||
| 465 | - for x in toSeq(a[1]): | ||
| 466 | - if dropping and truthy(call(a[0], @[x])): continue | ||
| 467 | - dropping = false | ||
| 468 | - r.add x | ||
| 469 | - mkList(r) | ||
| 470 | def "group-by", proc (a: seq[Value]): Value = | 567 | def "group-by", proc (a: seq[Value]): Value = |
| 471 | var m = emptyPMap() | 568 | var m = emptyPMap() |
| 472 | - for x in toSeq(a[1]): | 569 | + for x in elems(a[1]): |
| 473 | let k = call(a[0], @[x]) | 570 | let k = call(a[0], @[x]) |
| 474 | m = mapAssoc(m, k, conjOne(mapGet(m, k, mkVector(@[])), x)) | 571 | m = mapAssoc(m, k, conjOne(mapGet(m, k, mkVector(@[])), x)) |
| 475 | mkMapOf(m) | 572 | mkMapOf(m) |
| 476 | def "frequencies", proc (a: seq[Value]): Value = | 573 | def "frequencies", proc (a: seq[Value]): Value = |
| 477 | var m = emptyPMap() | 574 | var m = emptyPMap() |
| 478 | - for x in toSeq(a[0]): | 575 | + for x in elems(a[0]): |
| 479 | m = mapAssoc(m, x, mkInt(mapGet(m, x, mkInt(0)).i + 1)) | 576 | m = mapAssoc(m, x, mkInt(mapGet(m, x, mkInt(0)).i + 1)) |
| 480 | mkMapOf(m) | 577 | mkMapOf(m) |
| 481 | def "identity", proc (a: seq[Value]): Value = a[0] | 578 | def "identity", proc (a: seq[Value]): Value = a[0] |
modified
src/runtime.nim +201 -44 | @@ -17,7 +17,7 @@ const | ||
| 17 | 17 | type |
| 18 | 18 | Kind* = enum |
| 19 | 19 | kNil, kBool, kInt, kFloat, kStr, kKeyword, kSymbol, |
| 20 | - kList, kVector, kMap, kSet, kFn | |
| 20 | + kList, kVector, kMap, kSet, kCons, kLazy, kFn | |
| 21 | 21 | |
| 22 | 22 | VNode* = ref object |
| 23 | 23 | ## A trie node: leaves hold values, internal nodes hold children. |
| @@ -56,7 +56,8 @@ type | ||
| 56 | 56 | cnt*: int |
| 57 | 57 | nextOrd*: int |
| 58 | 58 | |
| 59 | - Value* = ref object | |
| 59 | + Value* = ref ValueObj | |
| 60 | + ValueObj* = object | |
| 60 | 61 | case kind*: Kind |
| 61 | 62 | of kNil: discard |
| 62 | 63 | of kBool: b*: bool |
| @@ -66,12 +67,58 @@ type | ||
| 66 | 67 | of kList: xs*: seq[Value] |
| 67 | 68 | of kVector: vec*: PVec |
| 68 | 69 | of kMap, kSet: m*: PMap |
| 70 | + of kCons: | |
| 71 | + head*: Value | |
| 72 | + tl*: Value ## rest of the seq: a cons, a lazy seq, a coll, or nil | |
| 73 | + of kLazy: | |
| 74 | + thunk*: proc (): Value {.closure.} | |
| 75 | + cached*: Value | |
| 76 | + forced*: bool | |
| 69 | 77 | of kFn: |
| 70 | 78 | fn*: proc (args: seq[Value]): Value {.closure.} |
| 71 | 79 | name*: string |
| 72 | 80 | |
| 73 | 81 | CljError* = object of CatchableError |
| 74 | 82 | |
| 83 | +# ------------------------------------------------------------ teardown | |
| 84 | +## A realized lazy seq is a chain of `cons -> lazy -> cons -> …` refs, and ARC | |
| 85 | +## frees a chain by recursing into it — a million-element seq means a million | |
| 86 | +## destructor frames, i.e. a segfault at scope exit. So `kCons`/`kLazy` hand | |
| 87 | +## their tail to a worklist instead of letting the field drop inline, and the | |
| 88 | +## outermost destructor drains it in a loop. Nothing shared is mutated: a node | |
| 89 | +## another seq still holds simply survives with its refcount intact. | |
| 90 | +## | |
| 91 | +## Defining `=destroy` means the compiler stops generating field teardown for | |
| 92 | +## `ValueObj`, so every branch below has to release its own fields. | |
| 93 | + | |
| 94 | +var pendingFree: seq[Value] = @[] | |
| 95 | +var draining = false | |
| 96 | + | |
| 97 | +proc `=destroy`*(x: var ValueObj) = | |
| 98 | + case x.kind | |
| 99 | + of kStr, kKeyword, kSymbol: `=destroy`(x.s) | |
| 100 | + of kList: `=destroy`(x.xs) | |
| 101 | + of kVector: `=destroy`(x.vec) | |
| 102 | + of kMap, kSet: `=destroy`(x.m) | |
| 103 | + of kFn: | |
| 104 | + `=destroy`(x.fn) | |
| 105 | + `=destroy`(x.name) | |
| 106 | + of kCons: | |
| 107 | + `=destroy`(x.head) | |
| 108 | + if not x.tl.isNil: pendingFree.add x.tl # +1, outlives the release below | |
| 109 | + `=destroy`(x.tl) | |
| 110 | + of kLazy: | |
| 111 | + `=destroy`(x.thunk) | |
| 112 | + if not x.cached.isNil: pendingFree.add x.cached | |
| 113 | + `=destroy`(x.cached) | |
| 114 | + of kNil, kBool, kInt, kFloat: discard | |
| 115 | + if draining: return | |
| 116 | + draining = true | |
| 117 | + while pendingFree.len > 0: | |
| 118 | + let v = pendingFree.pop() | |
| 119 | + discard v # dies here: its own tail is queued, not recursed into | |
| 120 | + draining = false | |
| 121 | + | |
| 75 | 122 | let NilV* = Value(kind: kNil) |
| 76 | 123 | let TrueV* = Value(kind: kBool, b: true) |
| 77 | 124 | let FalseV* = Value(kind: kBool, b: false) |
| @@ -81,6 +128,7 @@ proc err*(msg: string) {.noreturn.} = raise newException(CljError, msg) | ||
| 81 | 128 | proc equals*(a, b: Value): bool |
| 82 | 129 | proc hashValue*(v: Value): uint32 |
| 83 | 130 | proc prStr*(v: Value): string |
| 131 | +proc toSeq*(v: Value): seq[Value] | |
| 84 | 132 | |
| 85 | 133 | # ------------------------------------------------------- persistent vector |
| 86 | 134 | let emptyVNode = VNode(leaf: false, kids: @[]) |
| @@ -322,36 +370,6 @@ proc mapEntries*(m: PMap): seq[MEntry] = | ||
| 322 | 370 | collect(m.root, result) |
| 323 | 371 | result.sort(proc (a, b: MEntry): int = cmp(a.ord, b.ord)) |
| 324 | 372 | |
| 325 | -proc hashValue*(v: Value): uint32 = | |
| 326 | - if v.isNil: return 0 | |
| 327 | - case v.kind | |
| 328 | - of kNil: 0'u32 | |
| 329 | - of kBool: (if v.b: 0x9e3779b9'u32 else: 0x85ebca6b'u32) | |
| 330 | - of kInt: uint32(hash(v.i)) | |
| 331 | - of kFloat: | |
| 332 | - # ints and floats compare equal across kinds, so they must hash alike | |
| 333 | - if v.f == float64(int64(v.f)): uint32(hash(int64(v.f))) | |
| 334 | - else: uint32(hash(v.f)) | |
| 335 | - of kStr: mixHash(1'u32, uint32(hash(v.s))) | |
| 336 | - of kKeyword: mixHash(2'u32, uint32(hash(v.s))) | |
| 337 | - of kSymbol: mixHash(3'u32, uint32(hash(v.s))) | |
| 338 | - of kList, kVector: | |
| 339 | - # lists and vectors are `=` when their elements are, so they hash alike | |
| 340 | - var h = 7'u32 | |
| 341 | - for x in (if v.kind == kList: v.xs else: vecToSeq(v.vec)): | |
| 342 | - h = mixHash(h, hashValue(x)) | |
| 343 | - h | |
| 344 | - of kSet: | |
| 345 | - var h = 0'u32 # xor: independent of iteration order | |
| 346 | - for e in mapEntries(v.m): h = h xor hashValue(e.key) | |
| 347 | - h | |
| 348 | - of kMap: | |
| 349 | - var h = 0'u32 | |
| 350 | - for e in mapEntries(v.m): | |
| 351 | - h = h xor mixHash(hashValue(e.key), hashValue(e.val)) | |
| 352 | - h | |
| 353 | - of kFn: uint32(hash(cast[int](cast[pointer](v)))) | |
| 354 | - | |
| 355 | 373 | # ------------------------------------------------------------ constructors |
| 356 | 374 | proc mkBool*(x: bool): Value = (if x: TrueV else: FalseV) |
| 357 | 375 | proc mkInt*(x: int64): Value = Value(kind: kInt, i: x) |
| @@ -379,6 +397,131 @@ proc mkSet*(xs: seq[Value]): Value = | ||
| 379 | 397 | proc mkFn*(name: string, f: proc (args: seq[Value]): Value {.closure.}): Value = |
| 380 | 398 | Value(kind: kFn, fn: f, name: name) |
| 381 | 399 | |
| 400 | +# --------------------------------------------------------------- lazy seqs | |
| 401 | +## A lazy seq is a thunk that, when forced, yields either nil/`kNil` (the end) | |
| 402 | +## or a cons cell whose tail is usually another lazy seq. Forcing is memoized | |
| 403 | +## in place, so each element is computed once no matter how often it is walked. | |
| 404 | +## Nothing here recurses per element: `force` loops, and so does every producer | |
| 405 | +## in core, which is what keeps `(nth (iterate inc 0) 1000000)` from blowing the | |
| 406 | +## stack. | |
| 407 | + | |
| 408 | +proc mkCons*(h, t: Value): Value = Value(kind: kCons, head: h, tl: t) | |
| 409 | + | |
| 410 | +proc mkLazy*(f: proc (): Value {.closure.}): Value = | |
| 411 | + Value(kind: kLazy, thunk: f, cached: nil, forced: false) | |
| 412 | + | |
| 413 | +proc force*(v: Value): Value = | |
| 414 | + ## Realize one step: follow a chain of lazy seqs down to a cons, a concrete | |
| 415 | + ## collection, or the end of the seq. | |
| 416 | + var cur = v | |
| 417 | + while not cur.isNil and cur.kind == kLazy: | |
| 418 | + if not cur.forced: | |
| 419 | + cur.cached = cur.thunk() | |
| 420 | + cur.forced = true | |
| 421 | + cur.thunk = nil # drop the closure so its captures can be collected | |
| 422 | + cur = cur.cached | |
| 423 | + cur | |
| 424 | + | |
| 425 | +proc isSeqNode(v: Value): bool = | |
| 426 | + not v.isNil and v.kind in {kCons, kLazy} | |
| 427 | + | |
| 428 | +type Cursor* = object | |
| 429 | + ## Walks any seqable value without materializing it. Cons/lazy chains are | |
| 430 | + ## followed link by link; concrete collections are indexed. | |
| 431 | + node: Value | |
| 432 | + backing: seq[Value] | |
| 433 | + idx: int | |
| 434 | + isNode: bool | |
| 435 | + | |
| 436 | +proc cursor*(v: Value): Cursor = | |
| 437 | + ## Forces nothing: a cons/lazy value is walked link by link, and `hasNext` | |
| 438 | + ## is the only thing that ever forces. So building `(take 3 (map f xs))` | |
| 439 | + ## runs `f` zero times until something asks for an element. | |
| 440 | + if v.isNil: return Cursor(isNode: false) | |
| 441 | + if v.kind in {kCons, kLazy, kNil}: Cursor(isNode: true, node: v) | |
| 442 | + else: Cursor(isNode: false, backing: toSeq(v)) | |
| 443 | + | |
| 444 | +proc hasNext*(c: var Cursor): bool = | |
| 445 | + if c.isNode: | |
| 446 | + c.node = force(c.node) | |
| 447 | + not c.node.isNil and c.node.kind == kCons | |
| 448 | + else: c.idx < c.backing.len | |
| 449 | + | |
| 450 | +proc next*(c: var Cursor): Value = | |
| 451 | + if c.isNode: | |
| 452 | + result = c.node.head | |
| 453 | + c.node = c.node.tl | |
| 454 | + else: | |
| 455 | + result = c.backing[c.idx] | |
| 456 | + inc c.idx | |
| 457 | + | |
| 458 | +iterator elems*(v: Value): Value = | |
| 459 | + ## The one way to walk a collection in core: works for lists, vectors, maps, | |
| 460 | + ## sets, strings and lazy seqs alike, and never realizes more than it is asked | |
| 461 | + ## for. | |
| 462 | + var c = cursor(v) | |
| 463 | + while hasNext(c): yield next(c) | |
| 464 | + | |
| 465 | +proc seqFirst*(v: Value): Value = | |
| 466 | + let f = force(v) | |
| 467 | + if f.isNil: return NilV | |
| 468 | + if f.kind == kCons: return f.head | |
| 469 | + var c = cursor(f) | |
| 470 | + (if hasNext(c): next(c) else: NilV) | |
| 471 | + | |
| 472 | +proc seqRest*(v: Value): Value = | |
| 473 | + ## The rest of a seq, as a seq. Empty is an empty list, never nil — `next` | |
| 474 | + ## is the one that nils out. | |
| 475 | + let f = force(v) | |
| 476 | + if f.isNil or f.kind == kNil: return mkList(@[]) | |
| 477 | + if f.kind == kCons: return (if f.tl.isNil: mkList(@[]) else: f.tl) | |
| 478 | + let xs = toSeq(f) | |
| 479 | + (if xs.len <= 1: mkList(@[]) else: mkList(xs[1 .. ^1])) | |
| 480 | + | |
| 481 | +proc seqIsEmpty*(v: Value): bool = | |
| 482 | + ## O(1) for lazy seqs: forces at most the first element. | |
| 483 | + var c = cursor(v) | |
| 484 | + not hasNext(c) | |
| 485 | + | |
| 486 | +proc seqDrop*(v: Value, n: int): Value = | |
| 487 | + ## Skip n elements. Used by `& rest` destructuring, so it must not realize | |
| 488 | + ## anything past the n-th link — `(let [[a b & more] (range)] …)` works. | |
| 489 | + result = v | |
| 490 | + var k = n | |
| 491 | + while k > 0: | |
| 492 | + if seqIsEmpty(result): return mkList(@[]) | |
| 493 | + result = seqRest(result) | |
| 494 | + dec k | |
| 495 | + | |
| 496 | +proc hashValue*(v: Value): uint32 = | |
| 497 | + if v.isNil: return 0 | |
| 498 | + case v.kind | |
| 499 | + of kNil: 0'u32 | |
| 500 | + of kBool: (if v.b: 0x9e3779b9'u32 else: 0x85ebca6b'u32) | |
| 501 | + of kInt: uint32(hash(v.i)) | |
| 502 | + of kFloat: | |
| 503 | + # ints and floats compare equal across kinds, so they must hash alike | |
| 504 | + if v.f == float64(int64(v.f)): uint32(hash(int64(v.f))) | |
| 505 | + else: uint32(hash(v.f)) | |
| 506 | + of kStr: mixHash(1'u32, uint32(hash(v.s))) | |
| 507 | + of kKeyword: mixHash(2'u32, uint32(hash(v.s))) | |
| 508 | + of kSymbol: mixHash(3'u32, uint32(hash(v.s))) | |
| 509 | + of kList, kVector, kCons, kLazy: | |
| 510 | + # sequentials are `=` when their elements are, so they hash alike | |
| 511 | + var h = 7'u32 | |
| 512 | + for x in elems(v): h = mixHash(h, hashValue(x)) | |
| 513 | + h | |
| 514 | + of kSet: | |
| 515 | + var h = 0'u32 # xor: independent of iteration order | |
| 516 | + for e in mapEntries(v.m): h = h xor hashValue(e.key) | |
| 517 | + h | |
| 518 | + of kMap: | |
| 519 | + var h = 0'u32 | |
| 520 | + for e in mapEntries(v.m): | |
| 521 | + h = h xor mixHash(hashValue(e.key), hashValue(e.val)) | |
| 522 | + h | |
| 523 | + of kFn: uint32(hash(cast[int](cast[pointer](v)))) | |
| 524 | + | |
| 382 | 525 | # --------------------------------------------------------------- accessors |
| 383 | 526 | proc items*(v: Value): seq[Value] = |
| 384 | 527 | ## Elements of any sequential value, in order. O(n) — prefer `count`/`nth` |
| @@ -391,6 +534,11 @@ proc items*(v: Value): seq[Value] = | ||
| 391 | 534 | var r = newSeqOfCap[Value](v.m.cnt) |
| 392 | 535 | for e in mapEntries(v.m): r.add e.key |
| 393 | 536 | r |
| 537 | + of kCons, kLazy: | |
| 538 | + var r: seq[Value] = @[] | |
| 539 | + var c = cursor(v) | |
| 540 | + while hasNext(c): r.add next(c) | |
| 541 | + r | |
| 394 | 542 | else: @[] |
| 395 | 543 | |
| 396 | 544 | proc pairs*(v: Value): seq[(Value, Value)] = |
| @@ -406,6 +554,12 @@ proc count*(v: Value): int = | ||
| 406 | 554 | of kVector: v.vec.cnt |
| 407 | 555 | of kMap, kSet: v.m.cnt |
| 408 | 556 | of kStr: v.s.len |
| 557 | + of kCons, kLazy: | |
| 558 | + # realizes the whole seq, which is the honest cost of counting one | |
| 559 | + var n = 0 | |
| 560 | + var c = cursor(v) | |
| 561 | + while hasNext(c): discard next(c); inc n | |
| 562 | + n | |
| 409 | 563 | else: err("Don't know how to count: " & prStr(v)) |
| 410 | 564 | |
| 411 | 565 | proc truthy*(v: Value): bool = |
| @@ -421,14 +575,16 @@ proc equals*(a, b: Value): bool = | ||
| 421 | 575 | # numeric tower: int and float compare across types |
| 422 | 576 | if a.kind == kInt and b.kind == kFloat: return float64(a.i) == b.f |
| 423 | 577 | if a.kind == kFloat and b.kind == kInt: return a.f == float64(b.i) |
| 424 | - # lists and vectors are sequentially equal in Clojure | |
| 425 | - if a.kind in {kList, kVector} and b.kind in {kList, kVector}: | |
| 426 | - if count(a) != count(b): return false | |
| 427 | - let xs = items(a) | |
| 428 | - let ys = items(b) | |
| 429 | - for i in 0 ..< xs.len: | |
| 430 | - if not equals(xs[i], ys[i]): return false | |
| 431 | - return true | |
| 578 | + # every sequential thing is `=` to every other with the same elements | |
| 579 | + const Seqs = {kList, kVector, kCons, kLazy} | |
| 580 | + if a.kind in Seqs and b.kind in Seqs: | |
| 581 | + var ca = cursor(a) | |
| 582 | + var cb = cursor(b) | |
| 583 | + while true: | |
| 584 | + let ha = hasNext(ca) | |
| 585 | + if ha != hasNext(cb): return false | |
| 586 | + if not ha: return true | |
| 587 | + if not equals(next(ca), next(cb)): return false | |
| 432 | 588 | if a.kind != b.kind: return false |
| 433 | 589 | case a.kind |
| 434 | 590 | of kNil: true |
| @@ -448,7 +604,7 @@ proc equals*(a, b: Value): bool = | ||
| 448 | 604 | if not equals(e.val, mapGet(b.m, e.key, missing)): return false |
| 449 | 605 | true |
| 450 | 606 | of kFn: a == b |
| 451 | - of kList, kVector: false # handled above | |
| 607 | + of kList, kVector, kCons, kLazy: false # handled above | |
| 452 | 608 | # ---------------------------------------------------------------- printing |
| 453 | 609 | proc escapeStr(s: string): string = |
| 454 | 610 | result = "\"" |
| @@ -475,9 +631,10 @@ proc toStr*(v: Value, readable: bool): string = | ||
| 475 | 631 | of kStr: (if readable: escapeStr(v.s) else: v.s) |
| 476 | 632 | of kKeyword: ":" & v.s |
| 477 | 633 | of kSymbol: v.s |
| 478 | - of kList: | |
| 634 | + of kList, kCons, kLazy: | |
| 635 | + # printing a lazy seq realizes it, exactly as in Clojure | |
| 479 | 636 | var parts: seq[string] = @[] |
| 480 | - for x in v.items: parts.add toStr(x, readable) | |
| 637 | + for x in elems(v): parts.add toStr(x, readable) | |
| 481 | 638 | "(" & parts.join(" ") & ")" |
| 482 | 639 | of kVector: |
| 483 | 640 | var parts: seq[string] = @[] |
| @@ -563,7 +720,7 @@ proc toSeq*(v: Value): seq[Value] = | ||
| 563 | 720 | if v.isNil: return @[] |
| 564 | 721 | case v.kind |
| 565 | 722 | of kNil: @[] |
| 566 | - of kList, kVector, kSet: v.items | |
| 723 | + of kList, kVector, kSet, kCons, kLazy: v.items | |
| 567 | 724 | of kStr: |
| 568 | 725 | var r: seq[Value] = @[] |
| 569 | 726 | for c in v.s: r.add mkStr($c) |
| @@ -17,7 +17,7 @@ const | |||
| 17 | type | 17 | type |
| 18 | Kind* = enum | 18 | Kind* = enum |
| 19 | kNil, kBool, kInt, kFloat, kStr, kKeyword, kSymbol, | 19 | kNil, kBool, kInt, kFloat, kStr, kKeyword, kSymbol, |
| 20 | - kList, kVector, kMap, kSet, kFn | 20 | + kList, kVector, kMap, kSet, kCons, kLazy, kFn |
| 21 | 21 | ||
| 22 | VNode* = ref object | 22 | VNode* = ref object |
| 23 | ## A trie node: leaves hold values, internal nodes hold children. | 23 | ## A trie node: leaves hold values, internal nodes hold children. |
| @@ -56,7 +56,8 @@ type | |||
| 56 | cnt*: int | 56 | cnt*: int |
| 57 | nextOrd*: int | 57 | nextOrd*: int |
| 58 | 58 | ||
| 59 | - Value* = ref object | 59 | + Value* = ref ValueObj |
| 60 | + ValueObj* = object | ||
| 60 | case kind*: Kind | 61 | case kind*: Kind |
| 61 | of kNil: discard | 62 | of kNil: discard |
| 62 | of kBool: b*: bool | 63 | of kBool: b*: bool |
| @@ -66,12 +67,58 @@ type | |||
| 66 | of kList: xs*: seq[Value] | 67 | of kList: xs*: seq[Value] |
| 67 | of kVector: vec*: PVec | 68 | of kVector: vec*: PVec |
| 68 | of kMap, kSet: m*: PMap | 69 | of kMap, kSet: m*: PMap |
| 70 | + of kCons: | ||
| 71 | + head*: Value | ||
| 72 | + tl*: Value ## rest of the seq: a cons, a lazy seq, a coll, or nil | ||
| 73 | + of kLazy: | ||
| 74 | + thunk*: proc (): Value {.closure.} | ||
| 75 | + cached*: Value | ||
| 76 | + forced*: bool | ||
| 69 | of kFn: | 77 | of kFn: |
| 70 | fn*: proc (args: seq[Value]): Value {.closure.} | 78 | fn*: proc (args: seq[Value]): Value {.closure.} |
| 71 | name*: string | 79 | name*: string |
| 72 | 80 | ||
| 73 | CljError* = object of CatchableError | 81 | CljError* = object of CatchableError |
| 74 | 82 | ||
| 83 | +# ------------------------------------------------------------ teardown | ||
| 84 | +## A realized lazy seq is a chain of `cons -> lazy -> cons -> …` refs, and ARC | ||
| 85 | +## frees a chain by recursing into it — a million-element seq means a million | ||
| 86 | +## destructor frames, i.e. a segfault at scope exit. So `kCons`/`kLazy` hand | ||
| 87 | +## their tail to a worklist instead of letting the field drop inline, and the | ||
| 88 | +## outermost destructor drains it in a loop. Nothing shared is mutated: a node | ||
| 89 | +## another seq still holds simply survives with its refcount intact. | ||
| 90 | +## | ||
| 91 | +## Defining `=destroy` means the compiler stops generating field teardown for | ||
| 92 | +## `ValueObj`, so every branch below has to release its own fields. | ||
| 93 | + | ||
| 94 | +var pendingFree: seq[Value] = @[] | ||
| 95 | +var draining = false | ||
| 96 | + | ||
| 97 | +proc `=destroy`*(x: var ValueObj) = | ||
| 98 | + case x.kind | ||
| 99 | + of kStr, kKeyword, kSymbol: `=destroy`(x.s) | ||
| 100 | + of kList: `=destroy`(x.xs) | ||
| 101 | + of kVector: `=destroy`(x.vec) | ||
| 102 | + of kMap, kSet: `=destroy`(x.m) | ||
| 103 | + of kFn: | ||
| 104 | + `=destroy`(x.fn) | ||
| 105 | + `=destroy`(x.name) | ||
| 106 | + of kCons: | ||
| 107 | + `=destroy`(x.head) | ||
| 108 | + if not x.tl.isNil: pendingFree.add x.tl # +1, outlives the release below | ||
| 109 | + `=destroy`(x.tl) | ||
| 110 | + of kLazy: | ||
| 111 | + `=destroy`(x.thunk) | ||
| 112 | + if not x.cached.isNil: pendingFree.add x.cached | ||
| 113 | + `=destroy`(x.cached) | ||
| 114 | + of kNil, kBool, kInt, kFloat: discard | ||
| 115 | + if draining: return | ||
| 116 | + draining = true | ||
| 117 | + while pendingFree.len > 0: | ||
| 118 | + let v = pendingFree.pop() | ||
| 119 | + discard v # dies here: its own tail is queued, not recursed into | ||
| 120 | + draining = false | ||
| 121 | + | ||
| 75 | let NilV* = Value(kind: kNil) | 122 | let NilV* = Value(kind: kNil) |
| 76 | let TrueV* = Value(kind: kBool, b: true) | 123 | let TrueV* = Value(kind: kBool, b: true) |
| 77 | let FalseV* = Value(kind: kBool, b: false) | 124 | let FalseV* = Value(kind: kBool, b: false) |
| @@ -81,6 +128,7 @@ proc err*(msg: string) {.noreturn.} = raise newException(CljError, msg) | |||
| 81 | proc equals*(a, b: Value): bool | 128 | proc equals*(a, b: Value): bool |
| 82 | proc hashValue*(v: Value): uint32 | 129 | proc hashValue*(v: Value): uint32 |
| 83 | proc prStr*(v: Value): string | 130 | proc prStr*(v: Value): string |
| 131 | +proc toSeq*(v: Value): seq[Value] | ||
| 84 | 132 | ||
| 85 | # ------------------------------------------------------- persistent vector | 133 | # ------------------------------------------------------- persistent vector |
| 86 | let emptyVNode = VNode(leaf: false, kids: @[]) | 134 | let emptyVNode = VNode(leaf: false, kids: @[]) |
| @@ -322,36 +370,6 @@ proc mapEntries*(m: PMap): seq[MEntry] = | |||
| 322 | collect(m.root, result) | 370 | collect(m.root, result) |
| 323 | result.sort(proc (a, b: MEntry): int = cmp(a.ord, b.ord)) | 371 | result.sort(proc (a, b: MEntry): int = cmp(a.ord, b.ord)) |
| 324 | 372 | ||
| 325 | -proc hashValue*(v: Value): uint32 = | ||
| 326 | - if v.isNil: return 0 | ||
| 327 | - case v.kind | ||
| 328 | - of kNil: 0'u32 | ||
| 329 | - of kBool: (if v.b: 0x9e3779b9'u32 else: 0x85ebca6b'u32) | ||
| 330 | - of kInt: uint32(hash(v.i)) | ||
| 331 | - of kFloat: | ||
| 332 | - # ints and floats compare equal across kinds, so they must hash alike | ||
| 333 | - if v.f == float64(int64(v.f)): uint32(hash(int64(v.f))) | ||
| 334 | - else: uint32(hash(v.f)) | ||
| 335 | - of kStr: mixHash(1'u32, uint32(hash(v.s))) | ||
| 336 | - of kKeyword: mixHash(2'u32, uint32(hash(v.s))) | ||
| 337 | - of kSymbol: mixHash(3'u32, uint32(hash(v.s))) | ||
| 338 | - of kList, kVector: | ||
| 339 | - # lists and vectors are `=` when their elements are, so they hash alike | ||
| 340 | - var h = 7'u32 | ||
| 341 | - for x in (if v.kind == kList: v.xs else: vecToSeq(v.vec)): | ||
| 342 | - h = mixHash(h, hashValue(x)) | ||
| 343 | - h | ||
| 344 | - of kSet: | ||
| 345 | - var h = 0'u32 # xor: independent of iteration order | ||
| 346 | - for e in mapEntries(v.m): h = h xor hashValue(e.key) | ||
| 347 | - h | ||
| 348 | - of kMap: | ||
| 349 | - var h = 0'u32 | ||
| 350 | - for e in mapEntries(v.m): | ||
| 351 | - h = h xor mixHash(hashValue(e.key), hashValue(e.val)) | ||
| 352 | - h | ||
| 353 | - of kFn: uint32(hash(cast[int](cast[pointer](v)))) | ||
| 354 | - | ||
| 355 | # ------------------------------------------------------------ constructors | 373 | # ------------------------------------------------------------ constructors |
| 356 | proc mkBool*(x: bool): Value = (if x: TrueV else: FalseV) | 374 | proc mkBool*(x: bool): Value = (if x: TrueV else: FalseV) |
| 357 | proc mkInt*(x: int64): Value = Value(kind: kInt, i: x) | 375 | proc mkInt*(x: int64): Value = Value(kind: kInt, i: x) |
| @@ -379,6 +397,131 @@ proc mkSet*(xs: seq[Value]): Value = | |||
| 379 | proc mkFn*(name: string, f: proc (args: seq[Value]): Value {.closure.}): Value = | 397 | proc mkFn*(name: string, f: proc (args: seq[Value]): Value {.closure.}): Value = |
| 380 | Value(kind: kFn, fn: f, name: name) | 398 | Value(kind: kFn, fn: f, name: name) |
| 381 | 399 | ||
| 400 | +# --------------------------------------------------------------- lazy seqs | ||
| 401 | +## A lazy seq is a thunk that, when forced, yields either nil/`kNil` (the end) | ||
| 402 | +## or a cons cell whose tail is usually another lazy seq. Forcing is memoized | ||
| 403 | +## in place, so each element is computed once no matter how often it is walked. | ||
| 404 | +## Nothing here recurses per element: `force` loops, and so does every producer | ||
| 405 | +## in core, which is what keeps `(nth (iterate inc 0) 1000000)` from blowing the | ||
| 406 | +## stack. | ||
| 407 | + | ||
| 408 | +proc mkCons*(h, t: Value): Value = Value(kind: kCons, head: h, tl: t) | ||
| 409 | + | ||
| 410 | +proc mkLazy*(f: proc (): Value {.closure.}): Value = | ||
| 411 | + Value(kind: kLazy, thunk: f, cached: nil, forced: false) | ||
| 412 | + | ||
| 413 | +proc force*(v: Value): Value = | ||
| 414 | + ## Realize one step: follow a chain of lazy seqs down to a cons, a concrete | ||
| 415 | + ## collection, or the end of the seq. | ||
| 416 | + var cur = v | ||
| 417 | + while not cur.isNil and cur.kind == kLazy: | ||
| 418 | + if not cur.forced: | ||
| 419 | + cur.cached = cur.thunk() | ||
| 420 | + cur.forced = true | ||
| 421 | + cur.thunk = nil # drop the closure so its captures can be collected | ||
| 422 | + cur = cur.cached | ||
| 423 | + cur | ||
| 424 | + | ||
| 425 | +proc isSeqNode(v: Value): bool = | ||
| 426 | + not v.isNil and v.kind in {kCons, kLazy} | ||
| 427 | + | ||
| 428 | +type Cursor* = object | ||
| 429 | + ## Walks any seqable value without materializing it. Cons/lazy chains are | ||
| 430 | + ## followed link by link; concrete collections are indexed. | ||
| 431 | + node: Value | ||
| 432 | + backing: seq[Value] | ||
| 433 | + idx: int | ||
| 434 | + isNode: bool | ||
| 435 | + | ||
| 436 | +proc cursor*(v: Value): Cursor = | ||
| 437 | + ## Forces nothing: a cons/lazy value is walked link by link, and `hasNext` | ||
| 438 | + ## is the only thing that ever forces. So building `(take 3 (map f xs))` | ||
| 439 | + ## runs `f` zero times until something asks for an element. | ||
| 440 | + if v.isNil: return Cursor(isNode: false) | ||
| 441 | + if v.kind in {kCons, kLazy, kNil}: Cursor(isNode: true, node: v) | ||
| 442 | + else: Cursor(isNode: false, backing: toSeq(v)) | ||
| 443 | + | ||
| 444 | +proc hasNext*(c: var Cursor): bool = | ||
| 445 | + if c.isNode: | ||
| 446 | + c.node = force(c.node) | ||
| 447 | + not c.node.isNil and c.node.kind == kCons | ||
| 448 | + else: c.idx < c.backing.len | ||
| 449 | + | ||
| 450 | +proc next*(c: var Cursor): Value = | ||
| 451 | + if c.isNode: | ||
| 452 | + result = c.node.head | ||
| 453 | + c.node = c.node.tl | ||
| 454 | + else: | ||
| 455 | + result = c.backing[c.idx] | ||
| 456 | + inc c.idx | ||
| 457 | + | ||
| 458 | +iterator elems*(v: Value): Value = | ||
| 459 | + ## The one way to walk a collection in core: works for lists, vectors, maps, | ||
| 460 | + ## sets, strings and lazy seqs alike, and never realizes more than it is asked | ||
| 461 | + ## for. | ||
| 462 | + var c = cursor(v) | ||
| 463 | + while hasNext(c): yield next(c) | ||
| 464 | + | ||
| 465 | +proc seqFirst*(v: Value): Value = | ||
| 466 | + let f = force(v) | ||
| 467 | + if f.isNil: return NilV | ||
| 468 | + if f.kind == kCons: return f.head | ||
| 469 | + var c = cursor(f) | ||
| 470 | + (if hasNext(c): next(c) else: NilV) | ||
| 471 | + | ||
| 472 | +proc seqRest*(v: Value): Value = | ||
| 473 | + ## The rest of a seq, as a seq. Empty is an empty list, never nil — `next` | ||
| 474 | + ## is the one that nils out. | ||
| 475 | + let f = force(v) | ||
| 476 | + if f.isNil or f.kind == kNil: return mkList(@[]) | ||
| 477 | + if f.kind == kCons: return (if f.tl.isNil: mkList(@[]) else: f.tl) | ||
| 478 | + let xs = toSeq(f) | ||
| 479 | + (if xs.len <= 1: mkList(@[]) else: mkList(xs[1 .. ^1])) | ||
| 480 | + | ||
| 481 | +proc seqIsEmpty*(v: Value): bool = | ||
| 482 | + ## O(1) for lazy seqs: forces at most the first element. | ||
| 483 | + var c = cursor(v) | ||
| 484 | + not hasNext(c) | ||
| 485 | + | ||
| 486 | +proc seqDrop*(v: Value, n: int): Value = | ||
| 487 | + ## Skip n elements. Used by `& rest` destructuring, so it must not realize | ||
| 488 | + ## anything past the n-th link — `(let [[a b & more] (range)] …)` works. | ||
| 489 | + result = v | ||
| 490 | + var k = n | ||
| 491 | + while k > 0: | ||
| 492 | + if seqIsEmpty(result): return mkList(@[]) | ||
| 493 | + result = seqRest(result) | ||
| 494 | + dec k | ||
| 495 | + | ||
| 496 | +proc hashValue*(v: Value): uint32 = | ||
| 497 | + if v.isNil: return 0 | ||
| 498 | + case v.kind | ||
| 499 | + of kNil: 0'u32 | ||
| 500 | + of kBool: (if v.b: 0x9e3779b9'u32 else: 0x85ebca6b'u32) | ||
| 501 | + of kInt: uint32(hash(v.i)) | ||
| 502 | + of kFloat: | ||
| 503 | + # ints and floats compare equal across kinds, so they must hash alike | ||
| 504 | + if v.f == float64(int64(v.f)): uint32(hash(int64(v.f))) | ||
| 505 | + else: uint32(hash(v.f)) | ||
| 506 | + of kStr: mixHash(1'u32, uint32(hash(v.s))) | ||
| 507 | + of kKeyword: mixHash(2'u32, uint32(hash(v.s))) | ||
| 508 | + of kSymbol: mixHash(3'u32, uint32(hash(v.s))) | ||
| 509 | + of kList, kVector, kCons, kLazy: | ||
| 510 | + # sequentials are `=` when their elements are, so they hash alike | ||
| 511 | + var h = 7'u32 | ||
| 512 | + for x in elems(v): h = mixHash(h, hashValue(x)) | ||
| 513 | + h | ||
| 514 | + of kSet: | ||
| 515 | + var h = 0'u32 # xor: independent of iteration order | ||
| 516 | + for e in mapEntries(v.m): h = h xor hashValue(e.key) | ||
| 517 | + h | ||
| 518 | + of kMap: | ||
| 519 | + var h = 0'u32 | ||
| 520 | + for e in mapEntries(v.m): | ||
| 521 | + h = h xor mixHash(hashValue(e.key), hashValue(e.val)) | ||
| 522 | + h | ||
| 523 | + of kFn: uint32(hash(cast[int](cast[pointer](v)))) | ||
| 524 | + | ||
| 382 | # --------------------------------------------------------------- accessors | 525 | # --------------------------------------------------------------- accessors |
| 383 | proc items*(v: Value): seq[Value] = | 526 | proc items*(v: Value): seq[Value] = |
| 384 | ## Elements of any sequential value, in order. O(n) — prefer `count`/`nth` | 527 | ## Elements of any sequential value, in order. O(n) — prefer `count`/`nth` |
| @@ -391,6 +534,11 @@ proc items*(v: Value): seq[Value] = | |||
| 391 | var r = newSeqOfCap[Value](v.m.cnt) | 534 | var r = newSeqOfCap[Value](v.m.cnt) |
| 392 | for e in mapEntries(v.m): r.add e.key | 535 | for e in mapEntries(v.m): r.add e.key |
| 393 | r | 536 | r |
| 537 | + of kCons, kLazy: | ||
| 538 | + var r: seq[Value] = @[] | ||
| 539 | + var c = cursor(v) | ||
| 540 | + while hasNext(c): r.add next(c) | ||
| 541 | + r | ||
| 394 | else: @[] | 542 | else: @[] |
| 395 | 543 | ||
| 396 | proc pairs*(v: Value): seq[(Value, Value)] = | 544 | proc pairs*(v: Value): seq[(Value, Value)] = |
| @@ -406,6 +554,12 @@ proc count*(v: Value): int = | |||
| 406 | of kVector: v.vec.cnt | 554 | of kVector: v.vec.cnt |
| 407 | of kMap, kSet: v.m.cnt | 555 | of kMap, kSet: v.m.cnt |
| 408 | of kStr: v.s.len | 556 | of kStr: v.s.len |
| 557 | + of kCons, kLazy: | ||
| 558 | + # realizes the whole seq, which is the honest cost of counting one | ||
| 559 | + var n = 0 | ||
| 560 | + var c = cursor(v) | ||
| 561 | + while hasNext(c): discard next(c); inc n | ||
| 562 | + n | ||
| 409 | else: err("Don't know how to count: " & prStr(v)) | 563 | else: err("Don't know how to count: " & prStr(v)) |
| 410 | 564 | ||
| 411 | proc truthy*(v: Value): bool = | 565 | proc truthy*(v: Value): bool = |
| @@ -421,14 +575,16 @@ proc equals*(a, b: Value): bool = | |||
| 421 | # numeric tower: int and float compare across types | 575 | # numeric tower: int and float compare across types |
| 422 | if a.kind == kInt and b.kind == kFloat: return float64(a.i) == b.f | 576 | if a.kind == kInt and b.kind == kFloat: return float64(a.i) == b.f |
| 423 | if a.kind == kFloat and b.kind == kInt: return a.f == float64(b.i) | 577 | if a.kind == kFloat and b.kind == kInt: return a.f == float64(b.i) |
| 424 | - # lists and vectors are sequentially equal in Clojure | 578 | + # every sequential thing is `=` to every other with the same elements |
| 425 | - if a.kind in {kList, kVector} and b.kind in {kList, kVector}: | 579 | + const Seqs = {kList, kVector, kCons, kLazy} |
| 426 | - if count(a) != count(b): return false | 580 | + if a.kind in Seqs and b.kind in Seqs: |
| 427 | - let xs = items(a) | 581 | + var ca = cursor(a) |
| 428 | - let ys = items(b) | 582 | + var cb = cursor(b) |
| 429 | - for i in 0 ..< xs.len: | 583 | + while true: |
| 430 | - if not equals(xs[i], ys[i]): return false | 584 | + let ha = hasNext(ca) |
| 431 | - return true | 585 | + if ha != hasNext(cb): return false |
| 586 | + if not ha: return true | ||
| 587 | + if not equals(next(ca), next(cb)): return false | ||
| 432 | if a.kind != b.kind: return false | 588 | if a.kind != b.kind: return false |
| 433 | case a.kind | 589 | case a.kind |
| 434 | of kNil: true | 590 | of kNil: true |
| @@ -448,7 +604,7 @@ proc equals*(a, b: Value): bool = | |||
| 448 | if not equals(e.val, mapGet(b.m, e.key, missing)): return false | 604 | if not equals(e.val, mapGet(b.m, e.key, missing)): return false |
| 449 | true | 605 | true |
| 450 | of kFn: a == b | 606 | of kFn: a == b |
| 451 | - of kList, kVector: false # handled above | 607 | + of kList, kVector, kCons, kLazy: false # handled above |
| 452 | # ---------------------------------------------------------------- printing | 608 | # ---------------------------------------------------------------- printing |
| 453 | proc escapeStr(s: string): string = | 609 | proc escapeStr(s: string): string = |
| 454 | result = "\"" | 610 | result = "\"" |
| @@ -475,9 +631,10 @@ proc toStr*(v: Value, readable: bool): string = | |||
| 475 | of kStr: (if readable: escapeStr(v.s) else: v.s) | 631 | of kStr: (if readable: escapeStr(v.s) else: v.s) |
| 476 | of kKeyword: ":" & v.s | 632 | of kKeyword: ":" & v.s |
| 477 | of kSymbol: v.s | 633 | of kSymbol: v.s |
| 478 | - of kList: | 634 | + of kList, kCons, kLazy: |
| 635 | + # printing a lazy seq realizes it, exactly as in Clojure | ||
| 479 | var parts: seq[string] = @[] | 636 | var parts: seq[string] = @[] |
| 480 | - for x in v.items: parts.add toStr(x, readable) | 637 | + for x in elems(v): parts.add toStr(x, readable) |
| 481 | "(" & parts.join(" ") & ")" | 638 | "(" & parts.join(" ") & ")" |
| 482 | of kVector: | 639 | of kVector: |
| 483 | var parts: seq[string] = @[] | 640 | var parts: seq[string] = @[] |
| @@ -563,7 +720,7 @@ proc toSeq*(v: Value): seq[Value] = | |||
| 563 | if v.isNil: return @[] | 720 | if v.isNil: return @[] |
| 564 | case v.kind | 721 | case v.kind |
| 565 | of kNil: @[] | 722 | of kNil: @[] |
| 566 | - of kList, kVector, kSet: v.items | 723 | + of kList, kVector, kSet, kCons, kLazy: v.items |
| 567 | of kStr: | 724 | of kStr: |
| 568 | var r: seq[Value] = @[] | 725 | var r: seq[Value] = @[] |
| 569 | for c in v.s: r.add mkStr($c) | 726 | for c in v.s: r.add mkStr($c) |
added
tests/lazy.expected +24 -0 | new file mode 100644 | ||
| @@ -0,0 +1,24 @@ | ||
| 1 | +(0 1 2 3 4) | |
| 2 | +(1 2 3 4 5) | |
| 3 | +(0 2 4 6 8) | |
| 4 | +(1 2 4 8 16) | |
| 5 | +(:x :x :x :x) (:y :y :y) | |
| 6 | +(1 2 3 1 2 3 1) | |
| 7 | +(100 101 102) | |
| 8 | +(0 1 2 3 4) | |
| 9 | +(10 11 12) | |
| 10 | +(1 2 0 1 2) | |
| 11 | +1 1 1000 | |
| 12 | +built, calls so far: 0 | |
| 13 | +took (0 1 2) - calls: 3 | |
| 14 | +took (0 1 2) - calls: 3 (memoized) | |
| 15 | +1330 | |
| 16 | +0 1 (2 3 4) | |
| 17 | +(2 3 4) (2 4) | |
| 18 | +(0 1 2 3 4) (2 4 6) (5 4 3 2 1) | |
| 19 | +100 true nil (0 1) | |
| 20 | +true true | |
| 21 | +[0 1 2] (6 7 8 9) | |
| 22 | +(0 0 1 2) (1 2) nil 3 | |
| 23 | +1 2 3 | |
| 24 | +200000 200000 | |
| new file mode 100644 | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | +(0 1 2 3 4) | ||
| 2 | +(1 2 3 4 5) | ||
| 3 | +(0 2 4 6 8) | ||
| 4 | +(1 2 4 8 16) | ||
| 5 | +(:x :x :x :x) (:y :y :y) | ||
| 6 | +(1 2 3 1 2 3 1) | ||
| 7 | +(100 101 102) | ||
| 8 | +(0 1 2 3 4) | ||
| 9 | +(10 11 12) | ||
| 10 | +(1 2 0 1 2) | ||
| 11 | +1 1 1000 | ||
| 12 | +built, calls so far: 0 | ||
| 13 | +took (0 1 2) - calls: 3 | ||
| 14 | +took (0 1 2) - calls: 3 (memoized) | ||
| 15 | +1330 | ||
| 16 | +0 1 (2 3 4) | ||
| 17 | +(2 3 4) (2 4) | ||
| 18 | +(0 1 2 3 4) (2 4 6) (5 4 3 2 1) | ||
| 19 | +100 true nil (0 1) | ||
| 20 | +true true | ||
| 21 | +[0 1 2] (6 7 8 9) | ||
| 22 | +(0 0 1 2) (1 2) nil 3 | ||
| 23 | +1 2 3 | ||
| 24 | +200000 200000 | ||