Skip to content

Commit a5e94c9

Browse files
swannodettefogus
andauthored
Port CLJ-2979: Implement the selector macro in terms of destructuring…
Port CLJ-2979: Implement the selector macro in terms of destructuring and its directives * comment out compile time error cases for now Co-authored-by: Fogus <mefogus@gmail.com>
1 parent 0142b46 commit a5e94c9

2 files changed

Lines changed: 147 additions & 0 deletions

File tree

‎src/main/clojure/cljs/core.cljc‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,45 @@
855855
bindings
856856
(reduce process-entry [] bents))))
857857

858+
(core/defn- selector-impl [m]
859+
(core/let [dirs [:select :excess :missing :all]
860+
names (zipmap (filter m dirs) (repeatedly gensym))]
861+
(if (empty? names)
862+
#?(:clj (throw (IllegalArgumentException.
863+
"form must contain at least one of :select :excess :missing :all"))
864+
:cljs (throw (js/Error. "form must contain at least one of :select :excess :missing :all")))
865+
`(fn ~(gensym "selector")
866+
[map#]
867+
(core/let [~(merge m names) map#]
868+
~(if (= 1 (count names))
869+
(core/-> names first val)
870+
(core/list `some-vals names)))))))
871+
872+
(core/defmacro selector
873+
"Builds a selecting-fn from m, a map destructuring form that must
874+
include one or more of the :select, :all, :missing, and :excess
875+
directives. The return function takes a collection, destructures it
876+
per m, and returns a map of the result(s).
877+
878+
If m has exactly one directive, the result is the value that
879+
directive would yield. If m has more than one directive, then it
880+
returns a map of directives to values.
881+
882+
As in destructuring, :missing controls whether missing required keys
883+
throw or are collected.
884+
885+
While a map destructuring form may and sometimes must include
886+
bindings, selector doesn't produce bindings, thus ignoring the
887+
associated directive names.
888+
889+
Throws an exception if the argument is not a map."
890+
{:added "1.13"}
891+
[m]
892+
(core/when-not (map? m)
893+
#?(:clj (throw (IllegalArgumentException. "expected a map"))
894+
:cljs (throw (js/Error. "expected a map"))))
895+
(selector-impl m))
896+
858897
(core/defmacro ^:private return-first
859898
[& body]
860899
`(let [ret# ~(first body)]

‎src/test/cljs/cljs/core_test.cljs‎

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2028,3 +2028,111 @@
20282028
(select-keys {:a 1 :b 2 :c nil} [:a :c :d]) {:a 1 :c nil}
20292029
(select-keys nil [:a]) {}
20302030
(select-keys #{:a :b} [:a :c]) {:a :a}))
2031+
2032+
(deftest selector-test
2033+
(let [sample-map {:a 1 :b 2 :c 3 :d 4
2034+
:e 5
2035+
::x 10000
2036+
:nested {:aa 1 'saa 10}}]
2037+
2038+
;; these are compile time
2039+
#_(testing "error cases"
2040+
(is (thrown? js/Error (selector {:keys [a b]})))
2041+
(is (thrown? js/Error (selector sample-map)))
2042+
(is (thrown? js/Error (selector nil)))
2043+
(is (thrown? js/Error (selector {}))))
2044+
2045+
(testing "single directives return their values directly"
2046+
(let [ex1 (selector {:keys [a b & :c :z]
2047+
:keys! [d]
2048+
:select keys-sel})]
2049+
(is (= {:a 1 :b 2 :c 3 :d 4}
2050+
(ex1 sample-map)))
2051+
2052+
(testing "checked keys without :missing should throw"
2053+
(is (thrown? js/Error (ex1 (dissoc sample-map :d)))))
2054+
2055+
(testing ":select with :or"
2056+
(let [ex1 (selector {:keys [a b & :c :z]
2057+
:keys! [d]
2058+
:select keys-sel
2059+
:or {:z 42}})]
2060+
(is (= {:a 1 :b 2 :c 3 :d 4 :z 42}
2061+
(ex1 sample-map))))))
2062+
2063+
(let [ex1 (selector {:keys [a b & :c :z]
2064+
:keys! [d]
2065+
:all keys-all})]
2066+
(is (= sample-map (ex1 sample-map))))
2067+
2068+
(let [ex1 (selector {:keys [a b & :c :z]
2069+
:keys! [d]
2070+
:missing keys-missing})]
2071+
(is (nil? (ex1 sample-map)))
2072+
(is (= {:d nil} (ex1 (dissoc sample-map :d)))))
2073+
2074+
(let [ex1 (selector {:keys [a b & :c :z]
2075+
:keys! [d]
2076+
:excess keys-excess})]
2077+
(is (= (dissoc sample-map :a :b :c :d) (ex1 sample-map)))))
2078+
2079+
(testing ":select plus :missing, but nothing missing"
2080+
(let [ex2 (selector {:keys [a b & :c :z]
2081+
:keys! [d]
2082+
:select keys-sel
2083+
:missing keys-missing})]
2084+
(is (= {:select {:a 1 :b 2 :c 3 :d 4}}
2085+
(ex2 sample-map)))))
2086+
2087+
(testing ":select plus :all"
2088+
(let [ex3 (selector {:keys [a b & :c :z]
2089+
:keys! [d]
2090+
:select keys-sel
2091+
:missing keys-missing
2092+
:all keys-all})]
2093+
(is (= {:select {:a 1 :b 2 :c 3 :d 4}
2094+
:all sample-map}
2095+
(ex3 sample-map)))))
2096+
2097+
(testing ":select, :all, and :excess"
2098+
(let [ex4 (selector {:keys [a b & :c :z]
2099+
:keys! [d]
2100+
:select keys-sel
2101+
:missing keys-missing
2102+
:all keys-all
2103+
:excess keys-excess})]
2104+
(is (= {:select {:a 1 :b 2 :c 3 :d 4}
2105+
:excess (dissoc sample-map :a :b :c :d)
2106+
:all sample-map}
2107+
(ex4 sample-map)))
2108+
(testing "plus :missing"
2109+
(is (= {:d nil}
2110+
(:missing (ex4 (dissoc sample-map :d))))))))
2111+
2112+
(testing "directive names bound to _"
2113+
(let [ex_ (selector {:keys [a b & :c :z]
2114+
:keys! [d]
2115+
:select _
2116+
:missing _
2117+
:all _
2118+
:excess _})]
2119+
(is (= {:select {:a 1 :b 2 :c 3 :d 4}
2120+
:excess (dissoc sample-map :a :b :c :d)
2121+
:all sample-map}
2122+
(ex_ sample-map)))))
2123+
2124+
(testing "nested :select"
2125+
(let [exnest (selector {{aa :aa saa 'saa
2126+
:select nest-sel} :nested
2127+
aqx ::x
2128+
:select tl-sel})]
2129+
(is (= {::x 10000 :nested {:aa 1 'saa 10}}
2130+
(exnest sample-map)))))
2131+
2132+
(testing "nested selection behavior with _ bindings"
2133+
(let [exnest_ (selector {{aa :aa saa 'saa
2134+
:select _} :nested
2135+
aqx ::x
2136+
:select _})]
2137+
(is (= {::x 10000 :nested {:aa 1 'saa 10}}
2138+
(exnest_ sample-map)))))))

0 commit comments

Comments
 (0)