├── .gitignore ├── project.clj └── src └── specter_demo ├── bank.clj └── examples.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject specter-demo "0.1.0-SNAPSHOT" 2 | :dependencies [[org.clojure/clojure "1.7.0"] 3 | [com.rpl/specter "0.7.1"]]) 4 | -------------------------------------------------------------------------------- /src/specter_demo/bank.clj: -------------------------------------------------------------------------------- 1 | (ns specter-demo.bank 2 | (:use [com.rpl.specter] 3 | [com.rpl.specter.macros] 4 | [clojure.pprint :only [pprint]] 5 | [com.rpl.specter.impl :only [benchmark]]) 6 | (:require [com.rpl.specter [protocols :as p]] 7 | [clojure.core.reducers :as r])) 8 | 9 | (declare world) 10 | (defn print-results [val] 11 | (println " ") 12 | (pprint world) 13 | (println "->") 14 | (pprint val) 15 | (println " ")) 16 | 17 | (def world 18 | {:people [{:money 129827 :name "Alice Brown"} 19 | {:money 100 :name "John Smith"} 20 | {:money 6821212339 :name "Donald Trump"} 21 | {:money 2870 :name "Charlie Johnson"} 22 | {:money 8273821 :name "Charlie Rose"} 23 | ] 24 | :bank {:funds 4782328748273}} 25 | ) 26 | 27 | (defn user->bank [world name amt] 28 | (let [curr-funds (->> world 29 | :people 30 | (filter (fn [user] (= (:name user) name))) 31 | first 32 | :money 33 | )] 34 | (if (< curr-funds amt) 35 | (throw (IllegalArgumentException. "Not enough funds!")) 36 | (-> world 37 | (update 38 | :people 39 | (fn [user-list] 40 | (mapv (fn [user] 41 | (if (= (:name user) name) 42 | (update user :money #(- % amt)) 43 | user 44 | )) 45 | user-list))) 46 | (update-in 47 | [:bank :funds] 48 | #(+ % amt)) 49 | )))) 50 | 51 | (comment 52 | (print-results 53 | (user->bank world "John Smith" 25)) 54 | ) 55 | 56 | 57 | (defn transfer 58 | [world from-path to-path amt] 59 | (let [givers (select from-path world) 60 | 61 | receivers (select to-path world) 62 | 63 | total-receive (* amt (count givers)) 64 | 65 | total-give (* amt (count receivers))] 66 | (if (every? #(>= % total-give) givers) 67 | (->> world 68 | (transform from-path #(- % total-give)) 69 | (transform to-path #(+ % total-receive)) 70 | ) 71 | (throw (IllegalArgumentException. "Not enough funds!")) 72 | ))) 73 | 74 | ;; show examples.clj 75 | 76 | (defn pay-fee [world] 77 | (transfer world 78 | [:people ALL :money] 79 | [:bank :funds] 80 | 1) 81 | ) 82 | 83 | (comment 84 | (print-results 85 | (pay-fee world)) 86 | ) 87 | 88 | (defn bank-give-dollar [world] 89 | (transfer world 90 | [:bank :funds] 91 | [:people ALL :money] 92 | 1) 93 | ) 94 | 95 | (comment 96 | (print-results 97 | (bank-give-dollar world)) 98 | ) 99 | 100 | (defn pay-poor-fee [world] 101 | (transfer world 102 | [:people ALL :money #(< % 3000)] 103 | [:bank :funds] 104 | 50) 105 | ) 106 | 107 | (comment 108 | (print-results 109 | (pay-poor-fee world)) 110 | ) 111 | 112 | (defn rich-people [world] 113 | (select [:people 114 | ALL 115 | (selected? :money #(>= % 1000000000)) 116 | :name] 117 | world)) 118 | 119 | (comment 120 | (print-results 121 | (rich-people world)) 122 | ) 123 | 124 | (defn user [name] 125 | [:people 126 | ALL 127 | #(= (:name %) name)]) 128 | 129 | (defn transfer-users [world from to amt] 130 | (transfer world 131 | [(user from) :money] 132 | [(user to) :money] 133 | amt)) 134 | 135 | (comment 136 | (print-results 137 | (transfer-users world "Alice Brown" "John Smith" 10)) 138 | ) 139 | 140 | (defn bank-loyal-bonus 141 | "Bank gives $5000 to earliest three users" 142 | [world] 143 | (transfer world 144 | [:bank :funds] 145 | [:people (srange 0 3) ALL :money] 146 | 5000)) 147 | 148 | (comment 149 | (print-results 150 | (bank-loyal-bonus world)) 151 | ) 152 | 153 | (defn add-person [world person] 154 | (setval [:people END] 155 | [person] 156 | world) 157 | ) 158 | 159 | (defn bank-recent-charity-bonus 160 | "Bank gives $1000 to most recent person with less than 5000 dollars" 161 | [world] 162 | (transfer world 163 | [:bank :funds] 164 | [:people 165 | (filterer [:money #(< % 5000)]) 166 | LAST 167 | :money] 168 | 1000)) 169 | 170 | (comment 171 | (print-results 172 | (bank-recent-charity-bonus world)) 173 | ) 174 | 175 | (defn mark-wealth-status [world] 176 | (setval [:people 177 | ALL 178 | (if-path [:money #(>= % 100000)] 179 | :rich 180 | :not-so-rich)] 181 | true 182 | world)) 183 | 184 | (comment 185 | (print-results 186 | (mark-wealth-status world)) 187 | ) 188 | 189 | ;; show performance examples in examples.clj 190 | 191 | (defn user->bank-uncompiled 192 | [world name amt] 193 | (transfer world [(user name) :money] [:bank :funds] amt)) 194 | 195 | 196 | 197 | (deftype AllVStructurePath []) 198 | 199 | (extend-protocol p/StructurePath 200 | AllVStructurePath 201 | (select* [this structure next-fn] 202 | (into [] (r/mapcat next-fn structure))) 203 | (transform* [this structure next-fn] 204 | (mapv next-fn structure) 205 | )) 206 | 207 | (def ALLV (->AllVStructurePath)) 208 | 209 | (def user-compiled 210 | (comp-paths :people 211 | ALLV 212 | (paramsfn [name] 213 | [elem] 214 | (= name (:name elem))) 215 | )) 216 | 217 | (def user-money-compiled (comp-paths user-compiled :money)) 218 | 219 | (def BANK-MONEY (comp-paths :bank :funds)) 220 | 221 | (defn user->bank-compiled [world name amt] 222 | (transfer world (user-money-compiled name) BANK-MONEY amt) 223 | ) 224 | 225 | 226 | ;; Not a direct comparison since Specter version is built on top of 227 | ;; a *much* more general way of doing transfers 228 | (comment 229 | (benchmark 100000 #(user->bank world "John Smith" 25)) 230 | (benchmark 100000 #(user->bank-uncompiled world "John Smith" 25)) 231 | (benchmark 100000 #(user->bank-compiled world "John Smith" 25)) 232 | ) 233 | 234 | ;; show graph example in examples.clj 235 | -------------------------------------------------------------------------------- /src/specter_demo/examples.clj: -------------------------------------------------------------------------------- 1 | (ns specter-demo.examples 2 | (:use [com.rpl.specter] 3 | [com.rpl.specter.macros] 4 | [clojure.pprint :only [pprint]] 5 | [com.rpl.specter.impl :only [benchmark]]) 6 | (:require [clojure.string :as str])) 7 | 8 | (defmacro print-results [form] 9 | (let [val (last form)] 10 | `(let [res# ~form] 11 | (println " ") 12 | (println " ") 13 | (pprint ~val) 14 | (println "->") 15 | (pprint res#) 16 | ))) 17 | 18 | (comment 19 | (print-results 20 | (select [ALL :a even?] 21 | [{:a 2 :b 3} {:a 1} {:a 4}])) 22 | 23 | 24 | (print-results 25 | (transform [ALL :a even?] 26 | dec 27 | [{:a 2 :b 3} {:a 1} {:a 4}])) 28 | 29 | ;;=>input 30 | [{:a 2 :b 3} {:a 1} {:a 4}] 31 | ;;=>ALL 32 | {:a 2 :b 3} 33 | {:a 1} 34 | {:a 4} 35 | ;;=>:a 36 | 2 37 | 1 38 | 4 39 | ;;=>even? 40 | 2 41 | 4 42 | ;;=> dec 43 | 1 44 | 3 45 | ;;=>even? 46 | 1 47 | 1 48 | 3 49 | ;;=>:a 50 | {:a 1 :b 3} 51 | {:a 1} 52 | {:a 3} 53 | ;;=>ALL 54 | [{:a 1 :b 3} {:a 1} {:a 3}] 55 | ;;=>output 56 | 57 | 58 | (print-results 59 | (transform [ALL :a even?] 60 | dec 61 | '({:a 2 :b 3} {:a 1} {:a 4}))) 62 | 63 | (print-results 64 | (transform [(filterer odd?) LAST] 65 | inc 66 | [1 2 3 4 5 6 7 8 9 18 12 14])) 67 | 68 | ;;=>input 69 | [1 2 3 4 5 6 7 8 9 18 12 14] 70 | ;;=>(filterer odd?) 71 | [1 3 5 7 9] 72 | ;;=>LAST 73 | 9 74 | ;;=>inc 75 | 10 76 | ;;=>LAST 77 | [1 3 5 7 10] 78 | ;;=>(filterer odd?) 79 | [1 2 3 4 5 6 7 8 10 18 12 14] 80 | ;;=>output 81 | 82 | 83 | (print-results 84 | (transform (srange 3 9) 85 | reverse 86 | [1 2 3 4 5 6 7 8 9 10 11 12])) 87 | 88 | (print-results 89 | (transform [(srange 1 4) ALL odd?] 90 | #(+ % 10) 91 | [0 1 2 3 4 5 6 7])) 92 | 93 | (print-results 94 | (transform (srange 2 4) 95 | (fn [_] [-1 -1 -1 -1 -1]) 96 | [0 1 2 3 4 5 6 7 8 9])) 97 | 98 | (print-results 99 | (setval (srange 2 4) 100 | [-1 -1 -1 -1 -1] 101 | [0 1 2 3 4 5 6 7 8 9])) 102 | 103 | (print-results 104 | (setval (srange 2 4) 105 | [] 106 | [0 1 2 3 4 5 6 7 8 9])) 107 | 108 | (print-results 109 | (transform [(srange 4 11) (filterer even?)] 110 | reverse 111 | [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15])) 112 | 113 | (print-results 114 | (setval [ALL END] 115 | [:a :b] 116 | [[1] '(1 2) [:c]])) 117 | 118 | (print-results 119 | (transform [ALL (collect-one :b) :a even?] 120 | + 121 | [{:a 1 :b 3} {:a 2 :b -10} {:a 4 :b 10} {:a 3}])) 122 | 123 | (print-results 124 | (setval [ALL 125 | (selected? (filterer even?) (view count) 126 | #(>= % 2)) 127 | END] 128 | [:c :d] 129 | [[1 2 3 4 5 6] [7 0 -1] [8 8] []])) 130 | 131 | ) 132 | 133 | ;; show implementations of keyword and ALL 134 | 135 | ;; back to bank examples 136 | 137 | (def DATA {:a {:b {:c 1}}}) 138 | 139 | 140 | ;; comp-paths strips protocol invocation and directly connects 141 | ;; the selectors to each other 142 | (def compiled-path (comp-paths :a :b :c)) 143 | 144 | (defn manual-transform [data] 145 | (update data 146 | :a 147 | (fn [d1] 148 | (update d1 149 | :b 150 | (fn [d2] 151 | (update d2 :c inc)))))) 152 | 153 | (comment 154 | (benchmark 1000000 #(get-in DATA [:a :b :c])) 155 | 156 | (benchmark 1000000 #(select [:a :b :c] DATA)) 157 | 158 | (benchmark 1000000 #(select compiled-path DATA)) 159 | 160 | (benchmark 1000000 #(compiled-select compiled-path DATA)) 161 | 162 | (benchmark 1000000 #(-> DATA :a :b :c vector)) 163 | 164 | (benchmark 1000000 #(update-in DATA [:a :b :c] inc)) 165 | 166 | (benchmark 1000000 #(transform [:a :b :c] inc DATA)) 167 | 168 | (benchmark 1000000 #(transform compiled-path inc DATA)) 169 | 170 | (benchmark 1000000 #(compiled-transform compiled-path inc DATA)) 171 | 172 | (benchmark 1000000 #(manual-transform DATA)) 173 | 174 | ) 175 | 176 | ;; example of late-bound parameterization 177 | 178 | (defn reverse-matching-in-range [aseq start end predicate] 179 | (transform [(srange start end) (filterer predicate)] 180 | reverse 181 | aseq)) 182 | 183 | (def MATCHING-RANGE (comp-paths srange (filterer pred))) 184 | (defn reverse-matching-in-range-fast [aseq start end predicate] 185 | (compiled-transform (MATCHING-RANGE start end predicate) 186 | reverse 187 | aseq)) 188 | 189 | (def RANGE [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20]) 190 | 191 | (comment 192 | (benchmark 100000 #(reverse-matching-in-range RANGE 4 11 odd?)) 193 | (benchmark 100000 #(reverse-matching-in-range-fast RANGE 4 11 odd?)) 194 | ) 195 | 196 | 197 | (def param-compiled (comp-paths keypath keypath keypath)) 198 | 199 | (comment 200 | (benchmark 1000000 #(update-in DATA [:a :b :c] inc)) 201 | (benchmark 1000000 #(compiled-transform compiled-path inc DATA)) 202 | (benchmark 1000000 #(compiled-transform (param-compiled :a :b :c) inc DATA)) 203 | ) 204 | 205 | ;; back to bank examples 206 | 207 | 208 | (comment 209 | (transform [TOPSORT 210 | (collect PARENTS NODE :name) 211 | NODE 212 | (collect-one :name) 213 | :royal-name 214 | ] 215 | (fn [parent-names name _] 216 | (str name " of " (str/join ", " parent-names))) 217 | ancestry-graph 218 | )) 219 | --------------------------------------------------------------------------------