Skip to content

Instantly share code, notes, and snippets.

@borkdude
Last active May 13, 2022 17:50
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save borkdude/5f9a4ae710217e893a9462ff90b6cac3 to your computer and use it in GitHub Desktop.
Save borkdude/5f9a4ae710217e893a9462ff90b6cac3 to your computer and use it in GitHub Desktop.
Vanilla Clojure vs. Specter vs. transducers
(ns specter-idea
(:require [com.rpl.specter :as specter :refer [setval ALL
NONE multi-path
selected?
pred
]]))
(def data ;; only x and y are allowed
[{:name "x" :rels ["x" "y"]} ;=> fine, keep as is
{:name "y" :rels ["x" "y" "z"]} ;=> keep only allowed rels: {:name "y" :rels ["x" "y"]}
{:name "y" :rels ["z"]} ;=> no allowed rels, remove map completely
{:name "z" :rels ["x" "y"]}] ;=> disallowed name, remove completely
)
(def disallowed? (complement #{"x" "y"}))
;; Vanilla Clojure
(keep
(fn [m]
(when-not (disallowed? (:name m))
(when-let [new-rels (seq (remove disallowed? (:rels m)))]
(assoc m :rels new-rels))))
data)
;;=> ({:name "x", :rels ("x" "y")} {:name "y", :rels ("x" "y")})
;; Note that it doesn't maintain 'types', vector is converted to seq
;; Util
(defn SOME? [x]
(not (identical? NONE x)))
;; Specter
(setval
[ALL
(multi-path
(selected? :name disallowed?)
[SOME? :rels ALL disallowed?]
(selected? SOME? :rels empty?))]
NONE
data)
;;=> [{:name "x", :rels ["x" "y"]} {:name "y", :rels ["x" "y"]}]
;; Specter solution maintains types, nice if you need it
;; Vanilla Clojure which preserves types
(vec
(keep
(fn [m]
(if-not (disallowed? (:name m))
(if-let [new-rels (seq (remove disallowed? (:rels m)))]
(assoc m :rels (vec new-rels)))))
data))
;; Vanilla Clojure transducer version, also preserves types
(into []
(comp
(remove #(-> % :name disallowed?))
(map (fn [m] (update-in m [:rels] #(into [] (remove disallowed?) %))))
(remove #(-> % :rels empty?)))
data)
@puredanger
Copy link

puredanger commented Nov 3, 2017

Preserving the types is easy enough:

(vec
 (keep
  (fn [m]
    (when-not (disallowed? (:name m))
      (when-let [new-rels (seq (remove disallowed? (:rels m)))]
        (assoc m :rels (vec new-rels)))))
	data))

But I would probably do something like this with transducers (also maintains collection types):

(into []
  (comp
    (remove #(-> % :name disallowed?))
    (map (fn [m] (update-in m [:rels] #(into [] (remove disallowed?) %))))
    (remove #(-> % :rels empty?)))									
  data)

Benchmarked it with criterium bench too, execution means:

seq rewrite: 1.685164 µs
transducers: 2.450658 µs
specter: 2.769364 µs

@borkdude
Copy link
Author

borkdude commented Nov 4, 2017

Added @puredanger's examples to the main gist

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment