From c8b0559897eb1ea42e0f8a11b777e129dc2ddd2b Mon Sep 17 00:00:00 2001 From: ikappaki Date: Mon, 30 Dec 2024 15:13:33 +0000 Subject: [PATCH 1/2] [wip] support generators in seq processing fns --- src/basilisp/core.lpy | 78 +++++++++++++++------------- tests/basilisp/test_core_fns.lpy | 88 +++++++++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 36 deletions(-) diff --git a/src/basilisp/core.lpy b/src/basilisp/core.lpy index 4c3259b2..1c57cbbd 100644 --- a/src/basilisp/core.lpy +++ b/src/basilisp/core.lpy @@ -2198,7 +2198,7 @@ [coll] (let [do-reverse (fn do-reverse [in out] - (if (seq in) + (if-let [in (seq in)] (recur (rest in) (cons (first in) out)) out))] (do-reverse coll '()))) @@ -2680,10 +2680,11 @@ (defn every? "Return ``true`` if every element in ``coll`` satisfies ``pred``\\." [pred coll] - (cond - (nil? (seq coll)) true - (pred (first coll)) (recur pred (rest coll)) - :else false)) + (let [coll (seq coll)] + (cond + (nil? coll) true + (pred (first coll)) (recur pred (rest coll)) + :else false))) (defn every-pred "Return a predicate composed of all of the input predicates, which returns ``true`` @@ -2712,7 +2713,7 @@ (defn some "Return ``true`` if at least one element in ``coll`` satisfies ``pred``\\." [pred coll] - (when (seq coll) + (when-let [coll (seq coll)] (or (pred (first coll)) (recur pred (rest coll))))) @@ -2757,13 +2758,15 @@ (rf result (apply f input inputs)))))) ([f coll] (lazy-seq - (when (seq coll) + (when-let [coll (seq coll)] (cons (f (first coll)) (map f (rest coll)))))) ([f coll & colls] (lazy-seq - (when (and (seq coll) (every? seq colls)) - (cons (apply f (first coll) (map first colls)) - (apply map f (rest coll) (map rest colls))))))) + (when-let [coll (seq coll)] + (let [colls (map seq colls)] + (when (every? some? colls) + (cons (apply f (first coll) (map first colls)) + (apply map f (rest coll) (map rest colls))))))))) (def ^{:doc "Return a vector of ``(f elem)`` for elements in ``coll``\\. More than one collection may be supplied. If more than one collection is supplied, the @@ -2818,7 +2821,7 @@ result))))) ([pred coll] (lazy-seq - (when (seq coll) + (when-let [coll (seq coll)] (if (pred (first coll)) (cons (first coll) (filter pred (rest coll))) (filter pred (rest coll))))))) @@ -2853,7 +2856,7 @@ (rf result v))))))) ([f coll] (lazy-seq - (when (seq coll) + (when-let [coll (seq coll)] (let [elem (f (first coll))] (if (nil? elem) (keep f (rest coll)) @@ -2879,7 +2882,7 @@ (let [keep-idx (fn keep-idx [rng coll] (lazy-seq - (when (seq coll) + (when-let [coll (seq coll)] (let [elem (f (first rng) (first coll))] (if (nil? elem) (keep-idx (rest rng) (rest coll)) @@ -2901,7 +2904,7 @@ ([n coll] (lazy-seq (when (> n 0) - (when (seq coll) + (when-let [coll (seq coll)] (cons (first coll) (take (dec n) (rest coll)))))))) (defn take-while @@ -2919,7 +2922,7 @@ (ensure-reduced result)))))) ([pred coll] (lazy-seq - (when (seq coll) + (when-let [coll (seq coll)] (when (pred (first coll)) (cons (first coll) (take-while pred (rest coll)))))))) @@ -2939,7 +2942,7 @@ (rf result input))))))) ([n coll] (lazy-seq - (when (seq coll) + (when-let [coll (seq coll)] (if (> n 0) (drop (dec n) (rest coll)) (seq coll)))))) @@ -2961,7 +2964,7 @@ :else result)))))) ([pred coll] (lazy-seq - (when (seq coll) + (when-let [coll (seq coll)] (if (pred (first coll)) (drop-while pred (rest coll)) (seq coll)))))) @@ -3031,7 +3034,7 @@ (rf (rf result sep) input))))))) ([sep coll] (lazy-seq - (when (seq coll) + (when-let [coll (seq coll)] (if (seq (rest coll)) (cons (first coll) (cons sep (interpose sep (rest coll)))) @@ -3050,14 +3053,17 @@ (when (seq colls) (cons (ffirst colls) (apply coll-firsts (rest colls))))))] (lazy-seq - (when (and (seq coll) (every? seq colls)) - (concat (apply coll-firsts coll colls) - (apply interleave (rest coll) (map rest colls)))))))) + (when-let [coll (seq coll)] + (let [colls (map seq colls)] + (when (every? some? colls) + (concat (apply coll-firsts coll colls) + (apply interleave (rest coll) (map rest colls)))))))))) (defn cycle "Cycle the items in ``coll`` infinitely." [coll] - (let [coll-cycle (fn coll-cycle + (let [coll (seq coll) + coll-cycle (fn coll-cycle [curr] (lazy-seq (if (seq curr) @@ -3103,7 +3109,7 @@ result))))))) ([n coll] (lazy-seq - (when (seq coll) + (when-let [coll (seq coll)] (if (<= n 0) (repeat (first coll)) (cons (first coll) @@ -3120,13 +3126,13 @@ (partition n n coll)) ([n step coll] (lazy-seq - (when (seq coll) + (when-let [coll (seq coll)] (let [s (take n coll)] (when (= n (count s)) (cons s (partition n step (drop step coll)))))))) ([n step pad coll] (lazy-seq - (when (seq coll) + (when-let [coll (seq coll)] (let [s (take n coll) ns (count s) s (if (< ns n) @@ -3163,7 +3169,7 @@ (partition-all n n coll)) ([n step coll] (lazy-seq - (when (seq coll) + (when-let [coll (seq coll)] (cons (take n coll) (partition-all n step (drop step coll))))))) (defn partition-by @@ -3203,7 +3209,7 @@ (rf result elem))))))))) ([f coll] (lazy-seq - (when (seq coll) + (when-let [coll (seq coll)] (let [elem (first coll) felem (f elem) run (cons elem (take-while #(= felem (f %)) (next coll)))] @@ -3228,7 +3234,7 @@ (let [coll-distinct (fn coll-distinct [coll found] (lazy-seq - (when (seq coll) + (when-let [coll (seq coll)] (let [e (first coll)] (if-not (contains? found e) (cons e (coll-distinct (rest coll) (conj found e))) @@ -3262,8 +3268,9 @@ (cons e (coll-dedupe (rest coll) e)) (coll-dedupe (rest coll) prev))))))] (lazy-seq - (when-let [e (first coll)] - (cons e (coll-dedupe (rest coll) e))))))) + (let [coll (seq coll)] + (when-let [e (first coll)] + (cons e (coll-dedupe (rest coll) e)))))))) (defn flatten "Flatten any combination of nested sequences (such as lists or vectors) into a single @@ -3281,11 +3288,12 @@ (defn take-last "Return the last ``n`` items in ``coll`` in linear time." [n coll] - (loop [c (seq coll) - rem (seq (drop n coll))] - (if rem - (recur (next c) (next rem)) - c))) + (let [coll (seq coll)] + (loop [c coll + rem (seq (drop n coll))] + (if rem + (recur (next c) (next rem)) + c)))) (defn min-key "Return the arg for which ``(k arg)`` is the smallest number. If multiple values diff --git a/tests/basilisp/test_core_fns.lpy b/tests/basilisp/test_core_fns.lpy index 18538f6a..878287b1 100644 --- a/tests/basilisp/test_core_fns.lpy +++ b/tests/basilisp/test_core_fns.lpy @@ -2497,6 +2497,92 @@ cat (iteration range-chunk :somef (partial some #(< % 7)) :kf (comp inc last) :initk 0)))))) +(def test-generator-vals [0 1 2 "three" nil 5]) + +(defn test-generator [] + (dotimes [i (count test-generator-vals)] + (yield (nth test-generator-vals i)))) + +(deftest python-iterators-seq-fns-test + ;; The plan here is to consume with each function the test generator + ;; in its entirety, and check for missing values. + + (testing "every?" + (let [nums* (atom [])] + (is (every? #(do (swap! nums* conj %) + (or (number? %) (string? %) (nil? %))) + (test-generator))) + (is (= test-generator-vals @nums*)))) + + (testing "some" + (let [nums* (atom [])] + (is (some #(do (swap! nums* conj %) + (= % 5)) + (test-generator))) + (is (= test-generator-vals @nums*)))) + + (testing "map" + (is (= test-generator-vals (map identity (test-generator)))) + (is (= ["00" "11" "22" "threethree" "" "55"] (map str (test-generator) (test-generator))))) + + (testing "filter" + (is (= [0 1 2 5] (filter #(and (number? %) (> % -1)) (test-generator))))) + + (testing "keep" + (is (= [0 1 2 "three" 5] (keep identity (test-generator))))) + + (testing "keep-indexed" + (is (= [[0 0] [1 1] [2 2] [3 "three"] [4 nil] [5 5]] (keep-indexed vector (test-generator))))) + + (testing "take" + (is (= test-generator-vals (take 6 (test-generator))))) + + (testing "take-while" + (is (= test-generator-vals (take-while #(not= % 7) (test-generator))))) + + (testing "take-while" + (is (= test-generator-vals (drop 0 (test-generator))))) + + (testing "take-while" + (is (= [1 2 "three" nil 5] (drop-while #(not= % 1) (test-generator))))) + + (testing "interpose" + (is (= [0 "-" 1 "-" 2 "-" "three" "-" nil "-" 5] (interpose "-" (test-generator))))) + + (testing "interleave" + (is (= [0 0 1 1 2 2 "three" "three" nil nil 5 5] (interleave (test-generator) (test-generator))))) + + (testing "cycle" + (is (= [0 1 2 "three" nil 5 0 1 2 "three"] (take 10 (cycle (test-generator)))))) + + (testing "take-nth" + (is (= [0 2 nil] (take-nth 2 (test-generator))))) + + (testing "partition" + (is (= [[0 1] [2 "three"] [nil 5]] (partition 2 (test-generator)))) + (is (= [[0 1] [1 2] [2 "three"] ["three" nil] [nil 5]] (partition 2 1 (test-generator)))) + (is (= [[0 1 2 "three"] [nil 5 :pad :pad]] (partition 4 4 (repeat :pad) (test-generator))))) + + (testing "partition-all" + (is (= [[0 1 2 "three"] [nil 5]] (partition-all 4 (test-generator)))) + (is (= [[0 1 2 "three"] [nil 5]] (partition-all 4 4 (test-generator))))) + + (testing "distinct" + (is (= test-generator-vals (dedupe (test-generator))))) + + ;; TODO + ;; (testing "flatten" + ;; (is (= nil (flatten [[:a :b :c] (test-generator)])))) + + + (testing "take-last" + (is (= [2 "three" nil 5] (take-last 4 (test-generator))))) + + (testing "sort-by" + (is (= [5 nil "three" 2 1 0] (reverse (test-generator))))) + + ) + ;;;;;;;;;; ;; Taps ;; ;;;;;;;;;; @@ -2514,7 +2600,7 @@ a2-fn #(swap! a2-atom conj [:a2 %]) b-fn #(swap! b-atom conj %)] (add-tap def-fn) - (remove-tap b-fn) ;; not an error + (remove-tap b-fn) ;; not an error (tap> "this is a test") ;; Wait for the tap thread to drain From b064f931c5a9c05f5d18c95abb20c42ac4784fea Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sun, 20 Apr 2025 16:48:33 +0100 Subject: [PATCH 2/2] Added changelog entry --- CHANGELOG.md | 1 + tests/basilisp/test_core_fns.lpy | 88 +------------------------------- 2 files changed, 2 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ce7046e..b3bbe64c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed * Removed implicit support for single-use iterables in sequences, and introduced `iterator-seq` to expliciltly handle them (#1192) * `basilisp.core/str` now delegates to the builtin Python `str` in all cases except for customizing the string output for builtin Python types (#1237) + * Optimised mainstream seq-consuming functions by coercing their inputs into `seq` upfront (#1234) ### Fixed * Fix a bug where protocols with methods with leading hyphens in the could not be defined (#1230) diff --git a/tests/basilisp/test_core_fns.lpy b/tests/basilisp/test_core_fns.lpy index 878287b1..18538f6a 100644 --- a/tests/basilisp/test_core_fns.lpy +++ b/tests/basilisp/test_core_fns.lpy @@ -2497,92 +2497,6 @@ cat (iteration range-chunk :somef (partial some #(< % 7)) :kf (comp inc last) :initk 0)))))) -(def test-generator-vals [0 1 2 "three" nil 5]) - -(defn test-generator [] - (dotimes [i (count test-generator-vals)] - (yield (nth test-generator-vals i)))) - -(deftest python-iterators-seq-fns-test - ;; The plan here is to consume with each function the test generator - ;; in its entirety, and check for missing values. - - (testing "every?" - (let [nums* (atom [])] - (is (every? #(do (swap! nums* conj %) - (or (number? %) (string? %) (nil? %))) - (test-generator))) - (is (= test-generator-vals @nums*)))) - - (testing "some" - (let [nums* (atom [])] - (is (some #(do (swap! nums* conj %) - (= % 5)) - (test-generator))) - (is (= test-generator-vals @nums*)))) - - (testing "map" - (is (= test-generator-vals (map identity (test-generator)))) - (is (= ["00" "11" "22" "threethree" "" "55"] (map str (test-generator) (test-generator))))) - - (testing "filter" - (is (= [0 1 2 5] (filter #(and (number? %) (> % -1)) (test-generator))))) - - (testing "keep" - (is (= [0 1 2 "three" 5] (keep identity (test-generator))))) - - (testing "keep-indexed" - (is (= [[0 0] [1 1] [2 2] [3 "three"] [4 nil] [5 5]] (keep-indexed vector (test-generator))))) - - (testing "take" - (is (= test-generator-vals (take 6 (test-generator))))) - - (testing "take-while" - (is (= test-generator-vals (take-while #(not= % 7) (test-generator))))) - - (testing "take-while" - (is (= test-generator-vals (drop 0 (test-generator))))) - - (testing "take-while" - (is (= [1 2 "three" nil 5] (drop-while #(not= % 1) (test-generator))))) - - (testing "interpose" - (is (= [0 "-" 1 "-" 2 "-" "three" "-" nil "-" 5] (interpose "-" (test-generator))))) - - (testing "interleave" - (is (= [0 0 1 1 2 2 "three" "three" nil nil 5 5] (interleave (test-generator) (test-generator))))) - - (testing "cycle" - (is (= [0 1 2 "three" nil 5 0 1 2 "three"] (take 10 (cycle (test-generator)))))) - - (testing "take-nth" - (is (= [0 2 nil] (take-nth 2 (test-generator))))) - - (testing "partition" - (is (= [[0 1] [2 "three"] [nil 5]] (partition 2 (test-generator)))) - (is (= [[0 1] [1 2] [2 "three"] ["three" nil] [nil 5]] (partition 2 1 (test-generator)))) - (is (= [[0 1 2 "three"] [nil 5 :pad :pad]] (partition 4 4 (repeat :pad) (test-generator))))) - - (testing "partition-all" - (is (= [[0 1 2 "three"] [nil 5]] (partition-all 4 (test-generator)))) - (is (= [[0 1 2 "three"] [nil 5]] (partition-all 4 4 (test-generator))))) - - (testing "distinct" - (is (= test-generator-vals (dedupe (test-generator))))) - - ;; TODO - ;; (testing "flatten" - ;; (is (= nil (flatten [[:a :b :c] (test-generator)])))) - - - (testing "take-last" - (is (= [2 "three" nil 5] (take-last 4 (test-generator))))) - - (testing "sort-by" - (is (= [5 nil "three" 2 1 0] (reverse (test-generator))))) - - ) - ;;;;;;;;;; ;; Taps ;; ;;;;;;;;;; @@ -2600,7 +2514,7 @@ a2-fn #(swap! a2-atom conj [:a2 %]) b-fn #(swap! b-atom conj %)] (add-tap def-fn) - (remove-tap b-fn) ;; not an error + (remove-tap b-fn) ;; not an error (tap> "this is a test") ;; Wait for the tap thread to drain