diff --git a/project.clj b/project.clj index d2fdaba..ef130b1 100644 --- a/project.clj +++ b/project.clj @@ -4,6 +4,7 @@ :jvm-opts ["-XX:-OmitStackTraceInFastThrow"] ; this prevents JVM from doing optimizations which can remove stack traces from NPE and other exceptions :source-paths ["src/clj"] :test-paths ["test/clj"] + :plugins [[lein-nodisassemble "0.1.3"]] :profiles {:dev {:dependencies [[org.clojure/test.check "0.5.9"]]} }) diff --git a/src/clj/com/rpl/specter.clj b/src/clj/com/rpl/specter.clj index 4ff8ba7..521e30a 100644 --- a/src/clj/com/rpl/specter.clj +++ b/src/clj/com/rpl/specter.clj @@ -22,6 +22,13 @@ (if-not (empty? vals) [(conj vals structure)] [structure]))) )) +(defn select-fast + [^com.rpl.specter.impl.StructureValsPathFunctions selfns structure] + ((.selector selfns) [] structure + (fn [vals structure] + (if-not (empty? vals) [(conj vals structure)] [structure]))) + ) + (defn select-one "Like select, but returns either one element or nil. Throws exception if multiple elements found" [selector structure] @@ -67,7 +74,7 @@ (defn replace-in [selector update-fn structure & {:keys [merge-fn] :or {merge-fn concat}}] "Similar to update, except returns a pair of [updated-structure sequence-of-user-ret]. - The update-fn in this case is expected to return [ret user-ret]. ret is + The update-fn in this case is expected to return [ret user-ret]. ret is what's used to update the data structure, while user-ret will be added to the user-ret sequence in the final return. replace-in is useful for situations where you need to know the specific values of what was updated in the data structure." diff --git a/src/clj/com/rpl/specter/impl.clj b/src/clj/com/rpl/specter/impl.clj index 8d52909..d5423d5 100644 --- a/src/clj/com/rpl/specter/impl.clj +++ b/src/clj/com/rpl/specter/impl.clj @@ -9,31 +9,55 @@ (dotimes [_ iters] (afn)))) -(defprotocol CoerceStructureValsPath +(deftype StructureValsPathFunctions [selector updater] + StructureValsPath + (select-full* [this vals structure next-fn] + (selector vals structure next-fn)) + (update-full* [this vals structure next-fn] + (updater vals structure next-fn))) + +(defprotocol CoerceStructureValsPathFunctions (coerce-path [this])) -(extend-protocol CoerceStructureValsPath +(extend-protocol CoerceStructureValsPathFunctions com.rpl.specter.protocols.StructureValsPath - (coerce-path [this] this) + (coerce-path [this] + (let [pimpl (->> this + (find-protocol-impl StructureValsPath)) + selector (:select-full* pimpl) + updater (:update-full* pimpl)] + (->StructureValsPathFunctions + (fn [vals structure next-fn] + (selector this vals structure next-fn)) + (fn [vals structure next-fn] + (updater this vals structure next-fn))) + )) com.rpl.specter.protocols.Collector (coerce-path [collector] - (reify StructureValsPath - (select-full* [this vals structure next-fn] - (next-fn (conj vals (collect-val collector structure)) structure)) - (update-full* [this vals structure next-fn] - (next-fn (conj vals (collect-val collector structure)) structure)))) + (let [pimpl (->> collector + (find-protocol-impl Collector) + :collect-val + ) + afn (fn [vals structure next-fn] + (next-fn (conj vals (pimpl collector structure)) structure) + )] + (->StructureValsPathFunctions afn afn))) ;; need to say Object instead of StructurePath so that things like Keyword are properly coerced Object - (coerce-path [spath] - (reify StructureValsPath - (select-full* [this vals structure next-fn] - (select* spath structure (fn [structure] (next-fn vals structure)))) - (update-full* [this vals structure next-fn] - (update* spath structure (fn [structure] (next-fn vals structure))) - ))) + (coerce-path [this] + (let [pimpl (->> this + (find-protocol-impl StructurePath)) + selector (:select* pimpl) + updater (:update* pimpl)] + (->StructureValsPathFunctions + (fn [vals structure next-fn] + (selector this structure (fn [structure] (next-fn vals structure)))) + (fn [vals structure next-fn] + (updater this structure (fn [structure] (next-fn vals structure)))) + ))) ) @@ -43,18 +67,26 @@ (coerce-path sp)) java.util.List (comp-paths* [structure-paths] - (reduce (fn [sp-curr sp] - (reify StructureValsPath - (select-full* [this vals structure next-fn] - (select-full* sp vals structure - (fn [vals-next structure-next] - (select-full* sp-curr vals-next structure-next next-fn))) - ) - (update-full* [this vals structure next-fn] - (update-full* sp vals structure - (fn [vals-next structure-next] - (update-full* sp-curr vals-next structure-next next-fn)))) - )) + ;;TODO: don't reify any protocols... instead coerce to a "StructureValsPathFunctions" record + ;; and compose functions directly + + (reduce (fn [^StructureValsPathFunctions sp-curr ^StructureValsPathFunctions sp] + (let [curr-selector (.selector sp-curr) + selector (.selector sp) + curr-updater (.updater sp-curr) + updater (.updater sp)] + (->StructureValsPathFunctions + (fn [vals structure next-fn] + (selector vals structure + (fn [vals-next structure-next] + (curr-selector vals-next structure-next next-fn) + ))) + (fn [vals structure next-fn] + (updater vals structure + (fn [vals-next structure-next] + (curr-updater vals-next structure-next next-fn) + ))) + ))) (->> structure-paths flatten (map coerce-path) reverse)) )) diff --git a/src/clj/com/rpl/specter/protocols.clj b/src/clj/com/rpl/specter/protocols.clj index 41d78ac..cd357e3 100644 --- a/src/clj/com/rpl/specter/protocols.clj +++ b/src/clj/com/rpl/specter/protocols.clj @@ -1,6 +1,7 @@ (ns com.rpl.specter.protocols) +;;TODO: can use find-protocol-impl function to avoid all the protocol calls (defprotocol StructureValsPath (select-full* [this vals structure next-fn]) (update-full* [this vals structure next-fn]))