├── .compatibility-test-config ├── .gitignore ├── .midje.clj ├── .travis.yml ├── LICENSE ├── README.md ├── project.clj └── src ├── book_code ├── ch1 │ ├── all.clj │ ├── continuation_passing.clj │ ├── klunky_protocols.clj │ ├── realistic.clj │ ├── select_both.clj │ ├── select_kw.clj │ └── select_pred.clj └── ch2 │ ├── combined.clj │ ├── final.clj │ └── generalized.clj ├── exercises └── ch1 │ ├── all.clj │ ├── basic_specter.clj │ ├── continuation_passing.clj │ ├── select_kw.clj │ └── select_pred.clj └── specter_reference ├── ch1 ├── select_all.clj ├── select_kw.clj ├── select_kw_plus_pred.clj └── select_pred.clj └── ch2 └── simple_transform.clj /.compatibility-test-config: -------------------------------------------------------------------------------- 1 | (change-defaults :print-level :print-normally 2 | :visible-deprecation false 3 | :visible-future false) 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *jar 4 | /lib/ 5 | /classes/ 6 | /target/ 7 | /checkouts/ 8 | .lein-deps-sum 9 | .lein-repl-history 10 | .lein-plugins/ 11 | .lein-failures 12 | .nrepl-port 13 | -------------------------------------------------------------------------------- /.midje.clj: -------------------------------------------------------------------------------- 1 | (change-defaults :print-level :print-namespaces) 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | script: lein travis 4 | sudo: false 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Brian Marick 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Source and test code for examples and exercises from [*Extending and Using Specter*](https://leanpub.com/specter). 2 | 3 | This code corresponds to Version 2 of the book. 4 | 5 | License: [MIT](http://opensource.org/licenses/MIT) 6 | 7 | [![Build Status](https://travis-ci.org/marick/specter-book-code.png?branch=master)](https://travis-ci.org/marick/specter-book-code) 8 | 9 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject fun "0.1.0" 2 | :description "Source code for /Extending and Using Specter/" 3 | :url "https://leanpub.com/specter" 4 | :pedantic? :warn 5 | :license {:name "The MIT License (MIT)" 6 | :url "http://opensource.org/licenses/mit-license.php"} 7 | 8 | 9 | :dependencies [[org.clojure/clojure "1.8.0"] 10 | [com.rpl/specter "0.11.2" :exclusions [org.clojure/clojure org.clojure/clojurescript]] 11 | [marick/suchwow "5.1.3" :exclusions [org.clojure/clojure org.clojure/clojurescript]] 12 | [marick/clojure-commons "2.0.1" :exclusions [org.clojure/clojure]] 13 | ] 14 | :profiles {:dev {:dependencies [[midje "1.9.0-alpha2" :exclusions [org.clojure/clojure]]]} 15 | :1.6 {:dependencies [[org.clojure/clojure "1.6.0"]]} 16 | :1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]} 17 | :1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]} 18 | :1.9 {:dependencies [[org.clojure/clojure "1.9.0-alpha5"]]} 19 | } 20 | :plugins [[lein-midje "3.2"]] 21 | 22 | :aliases {"compatibility" ["with-profile" "+1.6:+1.7:+1.8:+1.9" "midje" ":config" ".compatibility-test-config"] 23 | "travis" ["with-profile" "+1.6:+1.7:+1.8:+1.9" "midje"]} 24 | 25 | ;; For Clojure snapshots 26 | :repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/"} 27 | ) 28 | -------------------------------------------------------------------------------- /src/book_code/ch1/all.clj: -------------------------------------------------------------------------------- 1 | (ns book-code.ch1.all 2 | (:use midje.sweet commons.clojure.core) 3 | (:use book-code.ch1.realistic)) 4 | 5 | (deftype AllType []) 6 | (def ALL (->AllType)) 7 | 8 | (extend-type AllType 9 | Navigator 10 | (select* [this structure continuation] 11 | (into [] (mapcat continuation structure)))) 12 | 13 | 14 | 15 | (facts "about ALL" 16 | (fact "all by itself is a no-op" 17 | (select [ALL] [1 2 3 4]) => [1 2 3 4]) 18 | 19 | (fact "Since ALL 'spreads' the elements, it can be used to flatten" 20 | (select [ALL ALL] [ [1] [2 3] ]) 21 | => [ 1 2 3 ] 22 | 23 | (fact "... but it won't flatten deeper than the level of nesting" 24 | (select [ALL ALL] [[0] [[1 2] 3]]) 25 | => [ 0 [1 2] 3]) 26 | 27 | (fact "both nil and an empty vector are flattened into nothing" 28 | (select [ALL ALL] [[1] nil [] [2]]) 29 | => [ 1 2])) 30 | 31 | 32 | (fact "ALL applies the rest of the selector to each element" 33 | (select [ALL :a] [{:a 1} {:a 2} { }]) 34 | => [1 2 nil ] 35 | (select [ALL even?] [1 2 3 4]) 36 | => [ 2 4] 37 | (select [ALL :a even?] [{:a 1} {:a 2}]) 38 | => [ 2 ]) 39 | 40 | (fact "ALL returns vectors" 41 | (select [ALL] '(1 2 3)) => vector? 42 | (select [ALL even?] [1 2 3]) => vector?)) 43 | 44 | 45 | 46 | ;; Old tests continue to pass. 47 | 48 | (fact "works the same for keywords" 49 | (select [:a] nil) => [nil] 50 | (select [:a] :something-random) => [nil] 51 | (select [:a] {:a 1}) => [1] 52 | (select [:a] {:not-a 1}) => [nil] 53 | (select [:a] {}) => [nil] 54 | 55 | (select [:a :b] {:a {:b 1}}) => [1] 56 | (select [:a :b] {:a 1}) => [nil] 57 | (select [:a :b] {:a {}}) => [nil]) 58 | 59 | (fact "works the same for predicates" 60 | (select [odd?] 1) => [1] 61 | (select [even?] 1) => nil 62 | (select [integer? odd?] 1) => [1] 63 | (select [integer? even?] 1) => nil 64 | (select [integer? odd?] "hi") => nil) 65 | 66 | (facts "combining keywords and predicates" 67 | (select [:a map? :b] {:a 1}) => nil 68 | (select [:a map? :b] {:a {:b 1}}) => [1] 69 | (select [:a map? :b] {:a {}}) => [nil] 70 | (select [map? :a] {:b 1}) => [nil] 71 | (select [map? :a] 1) => nil) 72 | 73 | (facts "all forms return specifically vectors" 74 | (select [:a :b] {:a {:b 1}}) => vector? 75 | (select [odd?] 1) => vector?) 76 | -------------------------------------------------------------------------------- /src/book_code/ch1/continuation_passing.clj: -------------------------------------------------------------------------------- 1 | (ns book-code.ch1.continuation-passing 2 | (:use midje.sweet commons.clojure.core)) 3 | 4 | (defprotocol Navigator 5 | (select* [this structure continuation])) 6 | 7 | (extend-type clojure.lang.Keyword 8 | Navigator 9 | (select* [this structure continuation] 10 | (continuation (get structure this)))) 11 | 12 | (extend-type clojure.lang.AFn 13 | Navigator 14 | (select* [this structure continuation] 15 | (if (this structure) 16 | (continuation structure) 17 | nil))) 18 | 19 | (defn predict-select-computation [selector] 20 | (reduce (fn [continuation element] 21 | (fn [structure] 22 | (select* element structure continuation))) 23 | vector 24 | (reverse selector))) 25 | 26 | (defn select [selector structure] 27 | ((predict-select-computation selector) structure)) 28 | 29 | 30 | 31 | 32 | (fact "works the same for keywords" 33 | (select [:a] nil) => [nil] 34 | (select [:a] :something-random) => [nil] 35 | (select [:a] {:a 1}) => [1] 36 | (select [:a] {:not-a 1}) => [nil] 37 | (select [:a] {}) => [nil] 38 | 39 | (select [:a :b] {:a {:b 1}}) => [1] 40 | (select [:a :b] {:a 1}) => [nil] 41 | (select [:a :b] {:a {}}) => [nil]) 42 | 43 | (fact "works the same for predicates" 44 | (select [odd?] 1) => [1] 45 | (select [even?] 1) => nil 46 | (select [integer? odd?] 1) => [1] 47 | (select [integer? even?] 1) => nil 48 | (select [integer? odd?] "hi") => nil) 49 | 50 | (facts "combining keywords and predicates" 51 | (select [:a map? :b] {:a 1}) => nil 52 | (select [:a map? :b] {:a {:b 1}}) => [1] 53 | (select [:a map? :b] {:a {}}) => [nil] 54 | (select [map? :a] {:b 1}) => [nil] 55 | (select [map? :a] 1) => nil) 56 | 57 | (fact "the two forms normally return specifically vectors" 58 | (select [:a :b] {:a {:b 1}}) => vector? 59 | (select [odd?] 1) => vector?) 60 | -------------------------------------------------------------------------------- /src/book_code/ch1/klunky_protocols.clj: -------------------------------------------------------------------------------- 1 | (ns book-code.ch1.klunky-protocols 2 | (:use midje.sweet commons.clojure.core)) 3 | 4 | 5 | (defprotocol Navigator 6 | (select* [this remainder structure])) 7 | 8 | (defn select [[x & xs :as selector] structure] 9 | (if (empty? selector) 10 | (vector structure) 11 | (select* x xs structure))) 12 | 13 | (extend-type clojure.lang.Keyword 14 | Navigator 15 | (select* [this remainder structure] 16 | (select remainder (get structure this)))) 17 | 18 | (extend-type clojure.lang.AFn 19 | Navigator 20 | (select* [this remainder structure] 21 | (if (this structure) 22 | (select remainder structure) 23 | nil))) 24 | 25 | 26 | 27 | (fact "works the same for keywords" 28 | (select [:a] nil) => [nil] 29 | (select [:a] :something-random) => [nil] 30 | (select [:a] {:a 1}) => [1] 31 | (select [:a] {:not-a 1}) => [nil] 32 | (select [:a] {}) => [nil] 33 | 34 | (select [:a :b] {:a {:b 1}}) => [1] 35 | (select [:a :b] {:a 1}) => [nil] 36 | (select [:a :b] {:a {}}) => [nil]) 37 | 38 | (fact "works the same for predicates" 39 | (select [odd?] 1) => [1] 40 | (select [even?] 1) => nil 41 | (select [integer? odd?] 1) => [1] 42 | (select [integer? even?] 1) => nil 43 | (select [integer? odd?] "hi") => nil) 44 | 45 | (facts "combining keywords and predicates" 46 | (select [:a map? :b] {:a 1}) => nil 47 | (select [:a map? :b] {:a {:b 1}}) => [1] 48 | (select [:a map? :b] {:a {}}) => [nil] 49 | (select [map? :a] {:b 1}) => [nil] 50 | (select [map? :a] 1) => nil) 51 | 52 | (fact "the two forms normally return specifically vectors" 53 | (select [:a :b] {:a {:b 1}}) => vector? 54 | (select [odd?] 1) => vector?) 55 | -------------------------------------------------------------------------------- /src/book_code/ch1/realistic.clj: -------------------------------------------------------------------------------- 1 | (ns book-code.ch1.realistic 2 | (:use midje.sweet commons.clojure.core)) 3 | 4 | 5 | 6 | (defprotocol Navigator 7 | (select* [this structure continuation])) 8 | 9 | (extend-type clojure.lang.Keyword 10 | Navigator 11 | (select* [this structure continuation] 12 | (continuation (get structure this)))) 13 | 14 | (extend-type clojure.lang.AFn 15 | Navigator 16 | (select* [this structure continuation] 17 | (if (this structure) 18 | (continuation structure) 19 | nil))) 20 | 21 | 22 | (defn element-functions-for [selector-element] 23 | (find-protocol-impl Navigator selector-element)) 24 | 25 | (def selector-function-for (comp :select* element-functions-for)) 26 | 27 | 28 | (defn mkfn:selector-function-calling-continuation [element continuation] 29 | (let [selector-function (selector-function-for element)] 30 | (fn [structure] 31 | (selector-function element structure continuation)))) 32 | 33 | 34 | (defn predict-select-computation [selector] 35 | (reduce (fn [continuation element] 36 | (mkfn:selector-function-calling-continuation element continuation)) 37 | vector 38 | (reverse selector))) 39 | 40 | (defn select [selector structure] 41 | ((predict-select-computation selector) structure)) 42 | 43 | (fact "works the same for keywords" 44 | (select [:a] nil) => [nil] 45 | (select [:a] :something-random) => [nil] 46 | (select [:a] {:a 1}) => [1] 47 | (select [:a] {:not-a 1}) => [nil] 48 | (select [:a] {}) => [nil] 49 | 50 | (select [:a :b] {:a {:b 1}}) => [1] 51 | (select [:a :b] {:a 1}) => [nil] 52 | (select [:a :b] {:a {}}) => [nil]) 53 | 54 | (fact "works the same for predicates" 55 | (select [odd?] 1) => [1] 56 | (select [even?] 1) => nil 57 | (select [integer? odd?] 1) => [1] 58 | (select [integer? even?] 1) => nil 59 | (select [integer? odd?] "hi") => nil) 60 | 61 | (facts "combining keywords and predicates" 62 | (select [:a map? :b] {:a 1}) => nil 63 | (select [:a map? :b] {:a {:b 1}}) => [1] 64 | (select [:a map? :b] {:a {}}) => [nil] 65 | (select [map? :a] {:b 1}) => [nil] 66 | (select [map? :a] 1) => nil) 67 | 68 | (fact "the two forms normally return specifically vectors" 69 | (select [:a :b] {:a {:b 1}}) => vector? 70 | (select [odd?] 1) => vector?) 71 | -------------------------------------------------------------------------------- /src/book_code/ch1/select_both.clj: -------------------------------------------------------------------------------- 1 | (ns book-code.ch1.select-both 2 | (:use midje.sweet commons.clojure.core)) 3 | 4 | (defn select-both [[x & xs :as selector] structure] 5 | (cond (empty? selector) 6 | (vector structure) 7 | 8 | (keyword? x) 9 | (select-both xs (get structure x)) 10 | 11 | (fn? x) 12 | (if (x structure) 13 | (select-both xs structure) 14 | nil))) 15 | 16 | (fact "works the same for keywords" 17 | (select-both [:a] nil) => [nil] 18 | (select-both [:a] :something-random) => [nil] 19 | (select-both [:a] {:a 1}) => [1] 20 | (select-both [:a] {:not-a 1}) => [nil] 21 | (select-both [:a] {}) => [nil] 22 | 23 | (select-both [:a :b] {:a {:b 1}}) => [1] 24 | (select-both [:a :b] {:a 1}) => [nil] 25 | (select-both [:a :b] {:a {}}) => [nil]) 26 | 27 | (fact "works the same for predicates" 28 | (select-both [odd?] 1) => [1] 29 | (select-both [even?] 1) => nil 30 | (select-both [integer? odd?] 1) => [1] 31 | (select-both [integer? even?] 1) => nil 32 | (select-both [integer? odd?] "hi") => nil) 33 | 34 | (facts "combining keywords and predicates" 35 | (select-both [:a map? :b] {:a 1}) => nil 36 | (select-both [:a map? :b] {:a {:b 1}}) => [1] 37 | (select-both [:a map? :b] {:a {}}) => [nil] 38 | (select-both [map? :a] {:b 1}) => [nil] 39 | (select-both [map? :a] 1) => nil) 40 | 41 | (fact "the two forms normally return specifically vectors" 42 | (select-both [:a :b] {:a {:b 1}}) => vector? 43 | (select-both [odd?] 1) => vector?) 44 | -------------------------------------------------------------------------------- /src/book_code/ch1/select_kw.clj: -------------------------------------------------------------------------------- 1 | (ns book-code.ch1.select-kw 2 | (:use midje.sweet commons.clojure.core)) 3 | 4 | (defn select-kw [selector structure] 5 | (if (empty? selector) 6 | (vector structure) 7 | (select-kw (rest selector) (get structure (first selector))))) 8 | 9 | (facts "same behavior from local implementation" 10 | (select-kw [:a] nil) => [nil] 11 | (select-kw [:a] :something-random) => [nil] 12 | (select-kw [:a] {:a 1}) => [1] 13 | (select-kw [:a] {:not-a 1}) => [nil] 14 | (select-kw [:a] {}) => [nil] 15 | 16 | (select-kw [:a :b] {:a {:b 1}}) => [1] 17 | (select-kw [:a :b] {:a 1}) => [nil] 18 | (select-kw [:a :b] {:a {}}) => [nil]) 19 | 20 | ;; Like Clojure, Midje considers sequential collections equal. 21 | ;; That is: 22 | ;; (= [1 2 3] '(1 2 3)) => true 23 | ;; Since Specter specifically returns a vector, I've added 24 | ;; a specific type test. 25 | 26 | (fact "the result is specifically a vector" 27 | (select-kw [:a :b] {:a {:b 1}}) => vector?) 28 | -------------------------------------------------------------------------------- /src/book_code/ch1/select_pred.clj: -------------------------------------------------------------------------------- 1 | (ns book-code.ch1.select-pred 2 | (:use midje.sweet commons.clojure.core)) 3 | 4 | (defn select-pred [selector candidate] 5 | (if (empty? selector) 6 | (vector candidate) 7 | (if ( (first selector) candidate) 8 | (select-pred (rest selector) candidate) 9 | nil))) 10 | 11 | (fact "our implementation matches Specter's" 12 | (select-pred [odd?] 1) => [1] 13 | (select-pred [even?] 1) => nil 14 | (select-pred [integer? odd?] 1) => [1] 15 | (select-pred [integer? even?] 1) => nil 16 | (select-pred [integer? odd?] "hi") => nil) 17 | 18 | ;; Like Clojure, Midje considers sequential collections equal. 19 | ;; That is: 20 | ;; (= [1 2 3] '(1 2 3)) => true 21 | ;; Since Specter specifically returns a vector, I've added 22 | ;; a specific type test. 23 | 24 | (fact "the result is specifically a vector" 25 | (select-pred [odd?] 1) => vector?) 26 | -------------------------------------------------------------------------------- /src/book_code/ch2/combined.clj: -------------------------------------------------------------------------------- 1 | (ns book-code.ch2.combined 2 | (:use midje.sweet commons.clojure.core)) 3 | 4 | ;;; Protocols 5 | 6 | (defprotocol Navigator 7 | (select* [this structure continuation])) 8 | 9 | ;;; Generic support code 10 | 11 | (defn element-functions-for [selector-element] 12 | (find-protocol-impl Navigator selector-element)) 13 | 14 | (def selector-function-for (comp :select* element-functions-for)) 15 | 16 | (defn mkfn:selector-function-calling-continuation [element continuation] 17 | (let [selector-function (selector-function-for element)] 18 | (fn [structure] 19 | (selector-function element structure continuation)))) 20 | 21 | (defn predict-select-computation [selector] 22 | (reduce (fn [continuation element] 23 | (mkfn:selector-function-calling-continuation element continuation)) 24 | vector 25 | (reverse selector))) 26 | 27 | ;;; Core functions 28 | 29 | (defn select [selector structure] 30 | ((predict-select-computation selector) structure)) 31 | 32 | ;;; Implementations of different types of selector elements. 33 | 34 | (extend-type clojure.lang.Keyword 35 | Navigator 36 | (select* [this structure continuation] 37 | (continuation (get structure this)))) 38 | 39 | (extend-type clojure.lang.AFn 40 | Navigator 41 | (select* [this structure continuation] 42 | (if (this structure) 43 | (continuation structure) 44 | nil))) 45 | 46 | (deftype AllType []) 47 | (def ALL (->AllType)) 48 | 49 | (extend-type AllType 50 | Navigator 51 | (select* [this structure continuation] 52 | (into [] (mapcat continuation structure)))) 53 | 54 | ;;; Tests 55 | 56 | 57 | (fact "works the same for keywords" 58 | (select [:a] nil) => [nil] 59 | (select [:a] :something-random) => [nil] 60 | (select [:a] {:a 1}) => [1] 61 | (select [:a] {:not-a 1}) => [nil] 62 | (select [:a] {}) => [nil] 63 | 64 | (select [:a :b] {:a {:b 1}}) => [1] 65 | (select [:a :b] {:a 1}) => [nil] 66 | (select [:a :b] {:a {}}) => [nil]) 67 | 68 | (fact "works the same for predicates" 69 | (select [odd?] 1) => [1] 70 | (select [even?] 1) => nil 71 | (select [integer? odd?] 1) => [1] 72 | (select [integer? even?] 1) => nil 73 | (select [integer? odd?] "hi") => nil) 74 | 75 | (facts "combining keywords and predicates" 76 | (select [:a map? :b] {:a 1}) => nil 77 | (select [:a map? :b] {:a {:b 1}}) => [1] 78 | (select [:a map? :b] {:a {}}) => [nil] 79 | (select [map? :a] {:b 1}) => [nil] 80 | (select [map? :a] 1) => nil) 81 | 82 | (fact "the two forms normally return specifically vectors" 83 | (select [:a :b] {:a {:b 1}}) => vector? 84 | (select [odd?] 1) => vector?) 85 | 86 | (facts "about ALL" 87 | (fact "all by itself is a no-op" 88 | (select [ALL] [1 2 3 4]) => [1 2 3 4]) 89 | 90 | (fact "Since ALL 'spreads' the elements, it can be used to flatten" 91 | (select [ALL ALL] [ [1] [2 3] ]) 92 | => [ 1 2 3 ] 93 | 94 | (fact "... but it won't flatten deeper than the level of nesting" 95 | (select [ALL ALL] [[0] [[1 2] 3]]) 96 | => [ 0 [1 2] 3]) 97 | 98 | (fact "both nil and an empty vector are flattened into nothing" 99 | (select [ALL ALL] [[1] nil [] [2]]) 100 | => [ 1 2])) 101 | 102 | 103 | (fact "ALL applies the rest of the selector to each element" 104 | (select [ALL :a] [{:a 1} {:a 2} { }]) 105 | => [1 2 nil ] 106 | (select [ALL even?] [1 2 3 4]) 107 | => [ 2 4] 108 | (select [ALL :a even?] [{:a 1} {:a 2}]) 109 | => [ 2 ]) 110 | 111 | (fact "ALL returns vectors" 112 | (select [ALL] '(1 2 3)) => vector? 113 | (select [ALL even?] [1 2 3]) => vector?)) 114 | -------------------------------------------------------------------------------- /src/book_code/ch2/final.clj: -------------------------------------------------------------------------------- 1 | (ns book-code.ch2.final 2 | (:use midje.sweet commons.clojure.core)) 3 | 4 | ;;; Protocols 5 | 6 | (defprotocol Navigator 7 | (select* [this structure continuation]) 8 | (transform* [this structure continuation])) 9 | 10 | ;;; Generic support code 11 | 12 | (defn navigation-worker [worker-kw selector-element] 13 | (-> (find-protocol-impl Navigator selector-element) 14 | (get worker-kw))) 15 | 16 | (defn mkfn:worker-calling-continuation [worker-kw element continuation] 17 | (let [worker (navigation-worker worker-kw element)] 18 | (fn [structure] 19 | (worker element structure continuation)))) 20 | 21 | (defn predict-computation [worker-kw selector final-action] 22 | (reduce (fn [continuation element] 23 | (mkfn:worker-calling-continuation worker-kw element continuation)) 24 | final-action 25 | (reverse selector))) 26 | 27 | ;;; Core functions 28 | 29 | (defn select [selector structure] 30 | ((predict-computation :select* selector vector) structure)) 31 | 32 | (defn transform [selector transform-fn structure] 33 | ((predict-computation :transform* selector transform-fn) structure)) 34 | 35 | ;;; Implementations of different types of selector elements. 36 | 37 | (extend-type clojure.lang.Keyword 38 | Navigator 39 | (select* [this structure continuation] 40 | (continuation (get structure this))) 41 | (transform* [this structure continuation] 42 | (->> (get structure this) 43 | continuation 44 | (assoc structure this)))) 45 | 46 | (extend-type clojure.lang.AFn 47 | Navigator 48 | (select* [this structure continuation] 49 | (if (this structure) 50 | (continuation structure) 51 | nil)) 52 | (transform* [this structure continuation] 53 | (if (this structure) 54 | (continuation structure) 55 | structure))) 56 | 57 | (deftype AllType []) 58 | (def ALL (->AllType)) 59 | 60 | (extend-type AllType 61 | Navigator 62 | (select* [this structure continuation] 63 | (into [] (mapcat continuation structure))) 64 | (transform* [this structure continuation] 65 | ;; It happens that `ALL` produces a lazyseq for the following. 66 | ;; This is probably not something to be relied on. 67 | (map continuation structure))) 68 | 69 | ;;; New `transform` tests 70 | 71 | (fact "transform applies functions" 72 | ;; Notice that, unlike `select`, it does *not* wrap results in a vector" 73 | (transform [:a] inc {:a 1}) => {:a 2} 74 | (transform [odd?] inc 1) => 2 75 | (transform [odd?] inc 2) => 2 76 | (transform [ALL] inc [1 2 3]) => [2 3 4]) 77 | 78 | (fact "applies transform only to path endpoints" 79 | (transform [:a :b] inc {:a {:b 1} :b "hi"}) => {:a {:b 2} :b "hi"} 80 | (transform [ALL :b] inc [{:b 1} {:b 2}]) => [{:b 2} {:b 3}] 81 | (transform [:b ALL] inc {:b [1 2 3]}) => {:b [2 3 4]} 82 | (transform [ALL even?] inc [1 2 3]) => [1 3 3] 83 | (transform [ALL identity] str [true false nil 1]) => ["true" false nil "1"] 84 | 85 | (transform [ALL :b] inc [1 {:b 1}]) => (throws) 86 | (transform [ALL map? :b] inc [1 {:b 1}]) => [1 {:b 2}]) 87 | 88 | (fact "odd cases" 89 | (transform [:a :b] inc {}) => (throws) 90 | (transform [:a :b] inc {:a {}}) => (throws) 91 | 92 | (transform [even?] inc "string") => (throws) 93 | ;; The following is not true of our implementation. 94 | ;; In the real Specter, it's special-cased, probably for efficiency. 95 | ;; (transform [ALL] inc nil) => nil 96 | ) 97 | 98 | (fact "predicates are happy with `nil` results" 99 | (transform [even?] (constantly nil) 2) => nil 100 | (transform [vector? ALL even?] (constantly nil) [2]) => [nil]) 101 | 102 | (fact "ALL produces a `seq`, not a `vector`" 103 | (let [result (transform [ALL] inc (list 1 2 3))] 104 | result => seq? 105 | result =not=> vector?)) 106 | 107 | ;;; Old `select` tests 108 | 109 | 110 | (fact "works the same for keywords" 111 | (select [:a] nil) => [nil] 112 | (select [:a] :something-random) => [nil] 113 | (select [:a] {:a 1}) => [1] 114 | (select [:a] {:not-a 1}) => [nil] 115 | (select [:a] {}) => [nil] 116 | 117 | (select [:a :b] {:a {:b 1}}) => [1] 118 | (select [:a :b] {:a 1}) => [nil] 119 | (select [:a :b] {:a {}}) => [nil]) 120 | 121 | (fact "works the same for predicates" 122 | (select [odd?] 1) => [1] 123 | (select [even?] 1) => nil 124 | (select [integer? odd?] 1) => [1] 125 | (select [integer? even?] 1) => nil 126 | (select [integer? odd?] "hi") => nil) 127 | 128 | (facts "combining keywords and predicates" 129 | (select [:a map? :b] {:a 1}) => nil 130 | (select [:a map? :b] {:a {:b 1}}) => [1] 131 | (select [:a map? :b] {:a {}}) => [nil] 132 | (select [map? :a] {:b 1}) => [nil] 133 | (select [map? :a] 1) => nil) 134 | 135 | (fact "the two forms normally return specifically vectors" 136 | (select [:a :b] {:a {:b 1}}) => vector? 137 | (select [odd?] 1) => vector?) 138 | 139 | (facts "about ALL" 140 | (fact "all by itself is a no-op" 141 | (select [ALL] [1 2 3 4]) => [1 2 3 4]) 142 | 143 | (fact "Since ALL 'spreads' the elements, it can be used to flatten" 144 | (select [ALL ALL] [ [1] [2 3] ]) 145 | => [ 1 2 3 ] 146 | 147 | (fact "... but it won't flatten deeper than the level of nesting" 148 | (select [ALL ALL] [[0] [[1 2] 3]]) 149 | => [ 0 [1 2] 3]) 150 | 151 | (fact "both nil and an empty vector are flattened into nothing" 152 | (select [ALL ALL] [[1] nil [] [2]]) 153 | => [ 1 2])) 154 | 155 | 156 | (fact "ALL applies the rest of the selector to each element" 157 | (select [ALL :a] [{:a 1} {:a 2} { }]) 158 | => [1 2 nil ] 159 | (select [ALL even?] [1 2 3 4]) 160 | => [ 2 4] 161 | (select [ALL :a even?] [{:a 1} {:a 2}]) 162 | => [ 2 ]) 163 | 164 | (fact "ALL returns vectors" 165 | (select [ALL] '(1 2 3)) => vector? 166 | (select [ALL even?] [1 2 3]) => vector?)) 167 | -------------------------------------------------------------------------------- /src/book_code/ch2/generalized.clj: -------------------------------------------------------------------------------- 1 | (ns book-code.ch2.generalized 2 | (:use midje.sweet commons.clojure.core)) 3 | 4 | ;;; Protocols 5 | 6 | (defprotocol Navigator 7 | (select* [this structure continuation])) 8 | 9 | ;;; Generic support code 10 | 11 | (defn navigation-worker [worker-kw selector-element] 12 | (-> (find-protocol-impl Navigator selector-element) 13 | (get worker-kw))) 14 | 15 | (defn mkfn:worker-calling-continuation [worker-kw element continuation] 16 | (let [worker (navigation-worker worker-kw element)] 17 | (fn [structure] 18 | (worker element structure continuation)))) 19 | 20 | (defn predict-computation [worker-kw selector final-action] 21 | (reduce (fn [continuation element] 22 | (mkfn:worker-calling-continuation worker-kw element continuation)) 23 | final-action 24 | (reverse selector))) 25 | 26 | ;;; Core functions 27 | 28 | (defn select [selector structure] 29 | ((predict-computation :select* selector vector) structure)) 30 | 31 | ;;; Implementations of different types of selector elements. 32 | 33 | (extend-type clojure.lang.Keyword 34 | Navigator 35 | (select* [this structure continuation] 36 | (continuation (get structure this)))) 37 | 38 | (extend-type clojure.lang.AFn 39 | Navigator 40 | (select* [this structure continuation] 41 | (if (this structure) 42 | (continuation structure) 43 | nil))) 44 | 45 | (deftype AllType []) 46 | (def ALL (->AllType)) 47 | 48 | (extend-type AllType 49 | Navigator 50 | (select* [this structure continuation] 51 | (into [] (mapcat continuation structure)))) 52 | 53 | ;;; Tests 54 | 55 | 56 | (fact "works the same for keywords" 57 | (select [:a] nil) => [nil] 58 | (select [:a] :something-random) => [nil] 59 | (select [:a] {:a 1}) => [1] 60 | (select [:a] {:not-a 1}) => [nil] 61 | (select [:a] {}) => [nil] 62 | 63 | (select [:a :b] {:a {:b 1}}) => [1] 64 | (select [:a :b] {:a 1}) => [nil] 65 | (select [:a :b] {:a {}}) => [nil]) 66 | 67 | (fact "works the same for predicates" 68 | (select [odd?] 1) => [1] 69 | (select [even?] 1) => nil 70 | (select [integer? odd?] 1) => [1] 71 | (select [integer? even?] 1) => nil 72 | (select [integer? odd?] "hi") => nil) 73 | 74 | (facts "combining keywords and predicates" 75 | (select [:a map? :b] {:a 1}) => nil 76 | (select [:a map? :b] {:a {:b 1}}) => [1] 77 | (select [:a map? :b] {:a {}}) => [nil] 78 | (select [map? :a] {:b 1}) => [nil] 79 | (select [map? :a] 1) => nil) 80 | 81 | (fact "the two forms normally return specifically vectors" 82 | (select [:a :b] {:a {:b 1}}) => vector? 83 | (select [odd?] 1) => vector?) 84 | 85 | (facts "about ALL" 86 | (fact "all by itself is a no-op" 87 | (select [ALL] [1 2 3 4]) => [1 2 3 4]) 88 | 89 | (fact "Since ALL 'spreads' the elements, it can be used to flatten" 90 | (select [ALL ALL] [ [1] [2 3] ]) 91 | => [ 1 2 3 ] 92 | 93 | (fact "... but it won't flatten deeper than the level of nesting" 94 | (select [ALL ALL] [[0] [[1 2] 3]]) 95 | => [ 0 [1 2] 3]) 96 | 97 | (fact "both nil and an empty vector are flattened into nothing" 98 | (select [ALL ALL] [[1] nil [] [2]]) 99 | => [ 1 2])) 100 | 101 | 102 | (fact "ALL applies the rest of the selector to each element" 103 | (select [ALL :a] [{:a 1} {:a 2} { }]) 104 | => [1 2 nil ] 105 | (select [ALL even?] [1 2 3 4]) 106 | => [ 2 4] 107 | (select [ALL :a even?] [{:a 1} {:a 2}]) 108 | => [ 2 ]) 109 | 110 | (fact "ALL returns vectors" 111 | (select [ALL] '(1 2 3)) => vector? 112 | (select [ALL even?] [1 2 3]) => vector?)) 113 | -------------------------------------------------------------------------------- /src/exercises/ch1/all.clj: -------------------------------------------------------------------------------- 1 | (ns exercises.ch1.all 2 | (:use midje.sweet commons.clojure.core) 3 | (:use exercises.ch1.basic-specter)) 4 | 5 | (deftype AllType []) 6 | (def ALL (->AllType)) 7 | 8 | (extend-type AllType 9 | Navigator 10 | (select* [this structure continuation] 11 | :unimplemented)) 12 | 13 | (future-facts "about ALL" 14 | (fact "all by itself is a no-op" 15 | (select [ALL] [1 2 3 4]) => [1 2 3 4]) 16 | 17 | (fact "Since ALL 'spreads' the elements, it can be used to flatten" 18 | (select [ALL ALL] [ [1] [2 3] ]) 19 | => [ 1 2 3 ] 20 | 21 | (fact "... but it won't flatten deeper than the level of nesting" 22 | (select [ALL ALL] [[0] [[1 2] 3]]) 23 | => [ 0 [1 2] 3]) 24 | 25 | (fact "both nil and an empty vector are flattened into nothing" 26 | (select [ALL ALL] [[1] nil [] [2]]) 27 | => [ 1 2])) 28 | 29 | 30 | (fact "ALL applies the rest of the selector to each element" 31 | (select [ALL :a] [{:a 1} {:a 2} { }]) 32 | => [1 2 nil ] 33 | (select [ALL even?] [1 2 3 4]) 34 | => [ 2 4] 35 | (select [ALL :a even?] [{:a 1} {:a 2}]) 36 | => [ 2 ]) 37 | 38 | (fact "ALL returns vectors" 39 | (select [ALL] '(1 2 3)) => vector? 40 | (select [ALL even?] [1 2 3]) => vector?)) 41 | 42 | 43 | 44 | ;; Old tests continue to pass. 45 | 46 | (fact "works the same for keywords" 47 | (select [:a] nil) => [nil] 48 | (select [:a] :something-random) => [nil] 49 | (select [:a] {:a 1}) => [1] 50 | (select [:a] {:not-a 1}) => [nil] 51 | (select [:a] {}) => [nil] 52 | 53 | (select [:a :b] {:a {:b 1}}) => [1] 54 | (select [:a :b] {:a 1}) => [nil] 55 | (select [:a :b] {:a {}}) => [nil]) 56 | 57 | (fact "works the same for predicates" 58 | (select [odd?] 1) => [1] 59 | (select [even?] 1) => nil 60 | (select [integer? odd?] 1) => [1] 61 | (select [integer? even?] 1) => nil 62 | (select [integer? odd?] "hi") => nil) 63 | 64 | (facts "combining keywords and predicates" 65 | (select [:a map? :b] {:a 1}) => nil 66 | (select [:a map? :b] {:a {:b 1}}) => [1] 67 | (select [:a map? :b] {:a {}}) => [nil] 68 | (select [map? :a] {:b 1}) => [nil] 69 | (select [map? :a] 1) => nil) 70 | 71 | (facts "all forms return specifically vectors" 72 | (select [:a :b] {:a {:b 1}}) => vector? 73 | (select [odd?] 1) => vector?) 74 | -------------------------------------------------------------------------------- /src/exercises/ch1/basic_specter.clj: -------------------------------------------------------------------------------- 1 | (ns exercises.ch1.basic-specter 2 | "This is used by other namespaces so that the grotty details of looking up 3 | protocol functions aren't always visible.") 4 | 5 | (defprotocol Navigator 6 | (select* [this structure continuation])) 7 | 8 | (extend-type clojure.lang.Keyword 9 | Navigator 10 | (select* [this structure continuation] 11 | (continuation (get structure this)))) 12 | 13 | (extend-type clojure.lang.AFn 14 | Navigator 15 | (select* [this structure continuation] 16 | (if (this structure) 17 | (continuation structure) 18 | nil))) 19 | 20 | 21 | (defn element-functions-for [selector-element] 22 | (find-protocol-impl Navigator selector-element)) 23 | 24 | (def selector-function-for (comp :select* element-functions-for)) 25 | 26 | 27 | (defn mkfn:selector-function-calling-continuation [element continuation] 28 | (let [selector-function (selector-function-for element)] 29 | (fn [structure] 30 | (selector-function element structure continuation)))) 31 | 32 | 33 | (defn predict-select-computation [selector] 34 | (reduce (fn [continuation element] 35 | (mkfn:selector-function-calling-continuation element continuation)) 36 | vector 37 | (reverse selector))) 38 | 39 | (defn select [selector structure] 40 | ((predict-select-computation selector) structure)) 41 | -------------------------------------------------------------------------------- /src/exercises/ch1/continuation_passing.clj: -------------------------------------------------------------------------------- 1 | (ns exercises.ch1.continuation-passing 2 | (:use midje.sweet commons.clojure.core)) 3 | 4 | (defprotocol Navigator 5 | (select* [this structure continuation])) 6 | 7 | (extend-type clojure.lang.Keyword 8 | Navigator 9 | (select* [this structure continuation] 10 | (continuation (get structure this)))) 11 | 12 | (extend-type clojure.lang.AFn 13 | Navigator 14 | (select* [this structure continuation] 15 | (if (this structure) 16 | (continuation structure) 17 | nil))) 18 | 19 | (defn predict-select-computation [selector] 20 | ) 21 | 22 | (defn select [selector structure] 23 | ((predict-select-computation selector) structure)) 24 | 25 | 26 | 27 | 28 | (future-fact "works the same for keywords" 29 | (select [:a] nil) => [nil] 30 | (select [:a] :something-random) => [nil] 31 | (select [:a] {:a 1}) => [1] 32 | (select [:a] {:not-a 1}) => [nil] 33 | (select [:a] {}) => [nil] 34 | 35 | (select [:a :b] {:a {:b 1}}) => [1] 36 | (select [:a :b] {:a 1}) => [nil] 37 | (select [:a :b] {:a {}}) => [nil]) 38 | 39 | (future-fact "works the same for predicates" 40 | (select [odd?] 1) => [1] 41 | (select [even?] 1) => nil 42 | (select [integer? odd?] 1) => [1] 43 | (select [integer? even?] 1) => nil 44 | (select [integer? odd?] "hi") => nil) 45 | 46 | (future-facts "combining keywords and predicates" 47 | (select [:a map? :b] {:a 1}) => nil 48 | (select [:a map? :b] {:a {:b 1}}) => [1] 49 | (select [:a map? :b] {:a {}}) => [nil] 50 | (select [map? :a] {:b 1}) => [nil] 51 | (select [map? :a] 1) => nil) 52 | 53 | (future-facts 54 | "the two forms normally return vectors (not any other kind of sequential collection" 55 | (select [:a :b] {:a {:b 1}}) => vector? 56 | (select [odd?] 1) => vector?) 57 | -------------------------------------------------------------------------------- /src/exercises/ch1/select_kw.clj: -------------------------------------------------------------------------------- 1 | (ns exercises.ch1.select-kw 2 | (:use midje.sweet commons.clojure.core)) 3 | 4 | 5 | (defn select-kw [selector structure] 6 | :unimplemented) 7 | 8 | (future-facts "Behaves the way specter/select does" 9 | (select-kw [:a] nil) => [nil] 10 | (select-kw [:a] :something-random) => [nil] 11 | (select-kw [:a] {:a 1}) => [1] 12 | (select-kw [:a] {:not-a 1}) => [nil] 13 | (select-kw [:a] {}) => [nil] 14 | 15 | (select-kw [:a :b] {:a {:b 1}}) => [1] 16 | (select-kw [:a :b] {:a 1}) => [nil] 17 | (select-kw [:a :b] {:a {}}) => [nil]) 18 | 19 | ;; Like Clojure, Midje considers sequential collections equal. 20 | ;; That is: 21 | ;; (= [1 2 3] '(1 2 3)) => true 22 | ;; Since Specter specifically returns a vector, I've added 23 | ;; a specific type test. 24 | 25 | (future-fact "the result is specifically a vector" 26 | (select-kw [:a :b] {:a {:b 1}}) => vector?) 27 | -------------------------------------------------------------------------------- /src/exercises/ch1/select_pred.clj: -------------------------------------------------------------------------------- 1 | (ns exercises.ch1.select-pred 2 | (:use midje.sweet commons.clojure.core)) 3 | 4 | (defn select-pred [selector candidate] 5 | :unimplemented) 6 | 7 | (future-fact "our implementation matches Specter's" 8 | (select-pred [odd?] 1) => [1] 9 | (select-pred [even?] 1) => nil 10 | (select-pred [integer? odd?] 1) => [1] 11 | (select-pred [integer? even?] 1) => nil 12 | (select-pred [integer? odd?] "hi") => nil) 13 | 14 | ;; Like Clojure, Midje considers sequential collections equal. 15 | ;; That is: 16 | ;; (= [1 2 3] '(1 2 3)) => true 17 | ;; Since Specter specifically returns a vector, I've added 18 | ;; a specific type test. 19 | 20 | (future-fact "the result is specifically a vector" 21 | (select-pred [odd? pos?] 1) => vector) 22 | -------------------------------------------------------------------------------- /src/specter_reference/ch1/select_all.clj: -------------------------------------------------------------------------------- 1 | (ns specter-reference.ch1.select-all 2 | (:use com.rpl.specter com.rpl.specter.macros) 3 | (:use midje.sweet commons.clojure.core)) 4 | 5 | (fact "all by itself is a no-op" 6 | (select [ALL] [1 2 3 4]) => [1 2 3 4] 7 | (fact "except that ALL blows up given a non-collection" 8 | (select [ALL] 1) => (throws))) 9 | 10 | (fact "Since ALL 'spreads' the elements, it can be used to flatten" 11 | (select [ALL ALL] [ [1] [2 3] ]) 12 | => [ 1 2 3 ] 13 | 14 | (fact "... but it won't flatten deeper than the level of nesting" 15 | (select [ALL ALL] [[0] [[1 2] 3]]) 16 | => [ 0 [1 2] 3]) 17 | 18 | (fact "both nil and an empty vector are flattened into nothing" 19 | (select [ALL ALL] [[1] nil [] [2]]) 20 | => [ 1 2])) 21 | 22 | 23 | (fact "ALL applies the rest of the selector to each element" 24 | (select [ALL :a] [{:a 1} {:a 2} { }]) 25 | => [1 2 nil ] 26 | (select [ALL even?] [1 2 3 4]) 27 | => [ 2 4] 28 | (select [ALL :a even?] [{:a 1} {:a 2}]) 29 | => [ 2 ] 30 | (select [ALL :a even?] [{:a 1}]) 31 | => [ ]) 32 | 33 | (fact "ALL can appear later in the vector" 34 | (select [:a ALL even?] {:a [1 2 3]}) 35 | => [ 2 ]) 36 | 37 | (fact "The result is specifically a vector" 38 | (select [ALL] [1 2 3 4]) => vector? 39 | (select [ALL ALL] [[0] [[1 2] 3]]) => vector? 40 | (select [ALL even?] [1 2 3 4]) => vector? 41 | (select [ALL :a] [{:a 1} {:a 2} { }]) => vector?) 42 | -------------------------------------------------------------------------------- /src/specter_reference/ch1/select_kw.clj: -------------------------------------------------------------------------------- 1 | (ns specter-reference.ch1.select-kw 2 | (:use com.rpl.specter com.rpl.specter.macros) 3 | (:use midje.sweet commons.clojure.core)) 4 | 5 | (facts "Specter's behavior with keywords" 6 | (fact "descends a map" 7 | (select [:a] {:a 1}) => [1] 8 | (select [:a :b] {:a {:b 1}}) => [1]) 9 | 10 | (fact "missing keyword has a value of `nil`" 11 | (select [:a] {}) => [nil] 12 | (select [:a] {:not-a 1}) => [nil] 13 | (select [:a :b] {:a {}}) => [nil]) 14 | 15 | (fact "as with `get`, bogus structures are silently accepted" 16 | (select [:a] nil) => [nil] ; (get nil :a) => nil 17 | (select [:a] "no-map") => [nil] ; (get "no-map" :a) => nil 18 | (select [:a :b] {:a 1}) => [nil]) ; (get 1 :a) => nil 19 | 20 | (fact "the result is specifically a vector" 21 | (select [:a :b] {:a {:b 1}}) => vector?)) 22 | -------------------------------------------------------------------------------- /src/specter_reference/ch1/select_kw_plus_pred.clj: -------------------------------------------------------------------------------- 1 | (ns specter-reference.ch1.select-kw-plus-pred 2 | (:use com.rpl.specter com.rpl.specter.macros) 3 | (:use midje.sweet commons.clojure.core)) 4 | 5 | (facts "combining keywords and predicates" 6 | (select [:a map? :b] {:a 1}) => nil 7 | (select [:a map? :b] {:a {:b 1}}) => [1] 8 | (select [:a map? :b] {:a {}}) => [nil] 9 | 10 | (select [map? :a] {:b 1}) => [nil] 11 | (select [map? :a] 1) => nil) 12 | -------------------------------------------------------------------------------- /src/specter_reference/ch1/select_pred.clj: -------------------------------------------------------------------------------- 1 | (ns specter-reference.ch1.select-pred 2 | (:use com.rpl.specter com.rpl.specter.macros) 3 | (:use midje.sweet commons.clojure.core)) 4 | 5 | 6 | (facts "reference behavior for predicates" 7 | (fact "predicate success: the value is passed through to the result vector" 8 | (select [odd?] 1) => [1]) 9 | 10 | (fact "as is common, predicates don't have to be strictly true or false" 11 | (select [#(get % :a)] {:a 1}) => [{:a 1}]) 12 | 13 | (fact "predicate failure:, there is no result vector" 14 | ;; Note that this is different from the behavior of keywords: 15 | ;; there, a missing key results in `nil` being passed through 16 | (select [even?] 1) => nil) 17 | 18 | (fact "given multiple predicates, each passes judgment in turn" 19 | (select [integer? odd?] 1) => [1] 20 | (select [integer? even?] 1) => nil 21 | ;; Note that the first predicate's failure prevents the second 22 | ;; from being checked. (It would throw an exception if it were.) 23 | (select [integer? odd?] "hi") => nil) 24 | 25 | (fact "the result is specifically a vector" 26 | (select [odd?] 1) => vector?)) 27 | -------------------------------------------------------------------------------- /src/specter_reference/ch2/simple_transform.clj: -------------------------------------------------------------------------------- 1 | (ns specter-reference.ch2.simple-transform 2 | (:use com.rpl.specter com.rpl.specter.macros) 3 | (:use midje.sweet commons.clojure.core)) 4 | 5 | (fact "transform applies functions" 6 | ;; Notice that, unlike `select`, it does *not* wrap results in a vector" 7 | (transform [:a] inc {:a 1}) => {:a 2} 8 | (transform [odd?] inc 1) => 2 9 | (transform [odd?] inc 2) => 2 10 | (transform [ALL] inc [1 2 3]) => [2 3 4]) 11 | 12 | (fact "applies transform only to path endpoints" 13 | (transform [:a :b] inc {:a {:b 1} :b "hi"}) => {:a {:b 2} :b "hi"} 14 | (transform [ALL :b] inc [{:b 1} {:b 2}]) => [{:b 2} {:b 3}] 15 | (transform [:b ALL] inc {:b [1 2 3]}) => {:b [2 3 4]} 16 | (transform [ALL even?] inc [1 2 3]) => [1 3 3] 17 | (transform [ALL identity] str [true false nil 1]) => ["true" false nil "1"] 18 | 19 | (transform [ALL :b] inc [1 {:b 1}]) => (throws) 20 | (transform [ALL map? :b] inc [1 {:b 1}]) => [1 {:b 2}]) 21 | 22 | (fact "odd cases" 23 | (transform [:a :b] inc {}) => (throws) 24 | (transform [:a :b] inc {:a {}}) => (throws) 25 | 26 | (transform [even?] inc "string") => (throws) 27 | (transform [ALL] inc nil) => nil) 28 | 29 | (fact "predicates are happy with `nil` results" 30 | (transform [even?] (constantly nil) 2) => nil 31 | (transform [vector? ALL even?] (constantly nil) [2]) => [nil]) 32 | 33 | (fact "ALL produces a `seq`, not a `vector`" 34 | (let [result (transform [ALL] inc (list 1 2 3))] 35 | result => seq? 36 | result =not=> vector?)) 37 | --------------------------------------------------------------------------------