From d28b40a26fbdc8649accac91043b05333ec26cf1 Mon Sep 17 00:00:00 2001 From: James Hamlin Date: Mon, 13 Oct 2025 13:57:20 -0700 Subject: [PATCH] Fix MapKeySeq reduce operations (#125) Implements IReduce and IReduceInit interfaces for MapKeySeq to fix "No method in multimethod 'coll-reduce' for dispatch value: *lang.MapKeySeq" error when using mapv on map keys. Changes: - Add Reduce and ReduceInit methods to MapKeySeq in pkg/lang/persistentarraymap.go - Add interface assertions for IReduce and IReduceInit compliance - Add comprehensive test cases in test/glojure/test_glojure/basic.glj Fixes issue where operations like (mapv identity (keys some-map)) would fail due to missing reduce implementation. --- pkg/lang/persistentarraymap.go | 37 ++++++++++++++++++++++++++++- test/glojure/test_glojure/basic.glj | 20 ++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/pkg/lang/persistentarraymap.go b/pkg/lang/persistentarraymap.go index bf1cad4..d47d6f0 100644 --- a/pkg/lang/persistentarraymap.go +++ b/pkg/lang/persistentarraymap.go @@ -51,7 +51,9 @@ var ( _ IReduceInit = (*MapSeq)(nil) _ IDrop = (*MapSeq)(nil) - _ ASeq = (*MapKeySeq)(nil) + _ ASeq = (*MapKeySeq)(nil) + _ IReduce = (*MapKeySeq)(nil) + _ IReduceInit = (*MapKeySeq)(nil) _ ASeq = (*MapValSeq)(nil) _ IReduce = (*MapValSeq)(nil) @@ -563,6 +565,39 @@ func (s *MapKeySeq) HashEq() uint32 { return aseqHashEq(&s.hasheq, s) } +func (s *MapKeySeq) Reduce(f IFn) any { + count := 0 + var res any + first := true + for seq := Seq(s); seq != nil; seq = seq.Next() { + count++ + if first { + res = seq.First() + first = false + continue + } + res = f.Invoke(res, seq.First()) + if IsReduced(res) { + return res.(IDeref).Deref() + } + } + if count == 0 { + return f.Invoke() + } + return res +} + +func (s *MapKeySeq) ReduceInit(f IFn, init any) any { + res := init + for seq := Seq(s); seq != nil; seq = seq.Next() { + res = f.Invoke(res, seq.First()) + if IsReduced(res) { + return res.(IDeref).Deref() + } + } + return res +} + //////////////////////////////////////////////////////////////////////////////// func NewMapValSeq(s ISeq) ISeq { diff --git a/test/glojure/test_glojure/basic.glj b/test/glojure/test_glojure/basic.glj index 2c5ca22..8f2328b 100644 --- a/test/glojure/test_glojure/basic.glj +++ b/test/glojure/test_glojure/basic.glj @@ -108,4 +108,24 @@ (is (:foo (meta ^:foo #{}))) (is (:foo (meta ^:foo {})))) +(deftest map-key-seq-reduce + (test-that "MapKeySeq implements reduce operations correctly" + (let [test-map {:a 1 :b 2 :c 3 :d 4 :e 5}] + ;; Test mapv on keys (this was the original failing case) + (is (= 5 (count (mapv identity (keys test-map))))) + (is (vector? (mapv identity (keys test-map)))) + + ;; Test reduce on keys + (is (= 5 (reduce (fn [acc _] (inc acc)) 0 (keys test-map)))) + + ;; Test mapv with transformation on keys + (is (= 5 (count (mapv str (keys test-map))))) + + ;; Test that vals still work correctly + (is (= 5 (count (mapv identity (vals test-map))))) + (is (= 15 (reduce + (vals test-map)))) + + ;; Test with namespace map keys (the original bug report case) + (is (number? (count (mapv str (keys (ns-map *ns*))))))))) + (run-tests)