├── .gitignore ├── LICENSE ├── README.md ├── doc ├── 001-fnil.md ├── 002-case.md ├── 003-juxt.md ├── 004-range.md ├── 005-trampoline.md ├── 006-memoize.md ├── 007-subvec.md ├── 008-defmacro.md ├── 009-diff.md ├── 010-into.md ├── 011-count.md ├── 012-memfn.md ├── 013-empty?.md ├── 014-get.md ├── 015-contains?.md ├── 016-reduce.md ├── 018-fold.md ├── 18-fold-fork-join-model.png ├── get-bench.png ├── intro.md └── reduce-bench.png ├── pics ├── 004-range.png ├── 15-into-vector-list.png ├── Borgatti-MEAP-HI.jpg ├── Screen Shot 2017-01-24 at 20.52.42.png ├── contains-by-type.png ├── pills.jpg ├── splash-small.png └── splash.png ├── project.clj ├── sample-project.clj ├── slides ├── 001-fnil.key ├── 002-case.key ├── 003-juxt.key ├── 004-range.key ├── 005-trampoline.key ├── 006-memoize.key ├── 007-subvec.key ├── 008-defmacro.key ├── 009-data:diff.key ├── 010-into.key ├── 011-count.key ├── 012-memfn.key ├── 013-empty?.key ├── 014-get.key ├── 015-contains?.key ├── 016-reduce.key ├── 017-the-life-of-an-s-expression.key └── 018-fold.key ├── src └── clojure_pills │ ├── contains.clj │ ├── count.clj │ ├── defmacroz.clj │ ├── diff.clj │ ├── empty_qmark.clj │ ├── foldz.clj │ ├── getz.clj │ ├── into.clj │ ├── memfnz.clj │ ├── memo.clj │ ├── reduc.clj │ └── subvec.clj └── test └── clojure_pills └── core_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /target 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | .hgignore 12 | .hg/ 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor to control, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### clojure-pills 2 | 3 | The [Clojure Pills screencast](https://www.youtube.com/channel/UCH0CkLvbv6yEyrUnw9qujpQ) show-notes. Please check the "/doc" folder. 4 | 5 | #### Index 6 | 7 | * [fnil](https://github.com/reborg/clojure-pills/blob/master/doc/001-fnil.md) 8 | * [case](https://github.com/reborg/clojure-pills/blob/master/doc/002-case.md) 9 | * [juxt](https://github.com/reborg/clojure-pills/blob/master/doc/003-juxt.md) 10 | * [range](https://github.com/reborg/clojure-pills/blob/master/doc/004-range.md) 11 | * [trampoline](https://github.com/reborg/clojure-pills/blob/master/doc/005-trampoline.md) 12 | * [memoize](https://github.com/reborg/clojure-pills/blob/master/doc/006-memoize.md) 13 | * [subvec](https://github.com/reborg/clojure-pills/blob/master/doc/007-subvec.md) 14 | * [defmacro](https://github.com/reborg/clojure-pills/blob/master/doc/008-defmacro.md) 15 | * [clojure.data/diff](https://github.com/reborg/clojure-pills/blob/master/doc/009-diff.md) 16 | * [into](https://github.com/reborg/clojure-pills/blob/master/doc/010-into.md) 17 | * [count](https://github.com/reborg/clojure-pills/blob/master/doc/011-count.md) 18 | * [memfn](https://github.com/reborg/clojure-pills/blob/master/doc/012-memfn.md) 19 | * [empty?](https://github.com/reborg/clojure-pills/blob/master/doc/013-empty%3F.md) 20 | * [get](https://github.com/reborg/clojure-pills/blob/master/doc/014-get.md) 21 | * [contains?](https://github.com/reborg/clojure-pills/blob/master/doc/015-contains%3F.md) 22 | * [reduce](https://github.com/reborg/clojure-pills/blob/master/doc/016-reduce.md) 23 | * [Life and death of an s-expression (special episode)](https://youtu.be/Uv9fyDTIPig) 24 | * [fold](https://github.com/reborg/clojure-pills/blob/master/doc/018-fold.md) 25 | 26 | #### License 27 | 28 | Copyright © 2017 @reborg 29 | 30 | Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version. 31 | -------------------------------------------------------------------------------- /doc/001-fnil.md: -------------------------------------------------------------------------------- 1 | ## 001 fnil 2 | 3 | Screencast link: https://www.youtube.com/watch?v=uDOvBAcApC4 4 | 5 | See also chapter 2.2.1 of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | 8 | ### At the REPL 9 | 10 | ```clojure 11 | "Clojure Pills 001: fnil" 12 | (doc fnil) 13 | (source fnil) 14 | 15 | (re-find #"hello" "Hey hello you") 16 | (re-find #"hello" nil) 17 | (def re-find+ (fnil re-find #"" "")) 18 | (re-find+ #"hello" nil) 19 | (re-find+ nil nil) 20 | 21 | (when (some? s) (re-find #"hello" s)) 22 | (def s "hello") 23 | (when (some? s) (re-find #"hello" s)) 24 | (def s nil) 25 | (when (some? s) (re-find #"hello" s)) 26 | (re-find+ nil nil) 27 | 28 | (fnil + 0 0 0 0 0) 29 | (fnil + 0 0 0) 30 | (defn fnil+ [f & opts] 31 | (fn [& args] 32 | (apply f (map #(if (nil? %1) %2 %1) args (concat opts (repeat nil)))))) 33 | 34 | (def ++ (fnil+ + 0 0 0 0 0)) 35 | (+ 1 2 nil 4 5) 36 | (++ 1 2 nil 4 5) 37 | 38 | (some->> "hello" (re-find #"hello")) 39 | (some->> nil (re-find #"hello")) 40 | (some->> s (re-find #"hello")) 41 | 42 | (source fnil) 43 | (def ++ (apply fnil+ (range 1e9))) 44 | (++ 1 2 3) 45 | (def ++ (apply fnil+ + (range 1e9))) 46 | (++ 1 2 3) 47 | (time (++ 1 2 3)) 48 | (time (apply ++ 1 2 3 (range 1e7))) 49 | (time (apply + 1 2 3 (range 1e7))) 50 | ``` 51 | -------------------------------------------------------------------------------- /doc/002-case.md: -------------------------------------------------------------------------------- 1 | ## 002 case 2 | 3 | Screencast link: https://youtu.be/_f3WeoDSN-I 4 | 5 | See also chapter 3.3.4 of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | 8 | ### At the REPL 9 | 10 | ```clojure 11 | "Clojure Pills 002: case" 12 | (case 1 0 "0" 1 "1" :default) 13 | (case 1 (inc 0) "1" (dec 1) "0" :default) 14 | 'dec 15 | 16 | (case 'pi 'alpha "a" 'beta "b" 'pi "pi") 17 | 'beta 18 | (type 'beta) 19 | ''alpha 20 | (case 'pi (quote alpha) "a" (quote "beta") b (quote pi) "pi") 21 | (case 'pi alpha "a" beta "b" pi "pi") 22 | 23 | (defn calculator [op] 24 | (case op 25 | ("+" "plus") + 26 | ("-" "minus") - 27 | ("*" "times") * 28 | "/" / 29 | (constantly "uknown operand"))) 30 | 31 | ((calculator "plus") 2 5) 32 | ((calculator "*") 2 5) 33 | ((calculator ".") 2 5) 34 | (condp = 1 (inc 0) "1") 35 | 36 | (require '[criterium.core :refer [quick-bench]]) 37 | (let [n 5] (quick-bench (condp = 5 1 1 2 2 3 3 4 4 5 5))) 38 | (let [n 5] (quick-bench (case 5 1 1 2 2 3 3 4 4 5 5))) 39 | -------------------------------------------------------------------------------- /doc/003-juxt.md: -------------------------------------------------------------------------------- 1 | ## 003 juxt 2 | 3 | Screencast link: https://youtu.be/-qW6H0761JQ 4 | 5 | See also chapter 2.2.6 of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | ### At the REPL 8 | 9 | ```clojure 10 | ((juxt first second last) (range 10)) 11 | 12 | (defn up [[x y]] [x (dec y)]) 13 | (defn down [[x y]] [x (inc y)]) 14 | (defn left [[x y]] [(dec x) y]) 15 | (defn right [[x y]] [(inc x) y]) 16 | (up [2 1]) 17 | (down [2 1]) 18 | ((juxt up down left right) [2 1]) 19 | (def neighbors (juxt up down left right)) 20 | (neighbors [2 1]) 21 | 22 | (def words ["this" "book" "is" "awesome"]) 23 | (map count words) 24 | (map (juxt count identity) words) 25 | (map (juxt count str) words) 26 | (sort words) 27 | (sort-by identity words) 28 | (sort-by (juxt count str) words) 29 | (map (juxt count str) words) 30 | (sort (map (juxt count str) words)) 31 | (map last (sort (map (juxt count str) words))) 32 | (sort-by (juxt count str) words) 33 | 34 | (doc comp) 35 | (source juxt) 36 | ``` 37 | -------------------------------------------------------------------------------- /doc/004-range.md: -------------------------------------------------------------------------------- 1 | ## 004 range 2 | 3 | Screencast link: https://youtu.be/-Ul-XbIUiqE 4 | 5 | See also chapter 3.4.2 of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | ### At the REPL 8 | 9 | ```clojure 10 | "Clojure Pills - 004 range" 11 | 12 | ;; longs, pos and neg 13 | (range 10) 14 | (range 5 10) 15 | (range 0 10 2) 16 | (range 20 10 -2) 17 | 18 | ;; doubles and ratios 19 | (range 0 5 0.5) 20 | (range 0 1 1/10) 21 | (take 5 (range)) 22 | 23 | ;; auto-promotion 24 | (range (-' Long/MAX_VALUE 3) (+' Long/MAX_VALUE 3)) 25 | (map type (range (-' Long/MAX_VALUE 3) (+' Long/MAX_VALUE 3))) 26 | 27 | ;; checking for palindrome sequences 28 | 29 | (def s "was it a car or a cat i saw") 30 | 31 | (defn palindrome? [xs] 32 | (let [cnt (count xs) idx (range (quot cnt 2) 0 -1)] 33 | (every? #(= (nth xs (dec %) ) (nth xs (- cnt %))) idx))) 34 | 35 | (palindrome? (remove #(= \space %) s)) 36 | ;; true 37 | 38 | (def s-2 "was it e car or a cat i saw") 39 | (palindrome? (remove #(= \space %) s-2)) 40 | ;; false 41 | 42 | ;; see also iterate 43 | (source range) 44 | (take 10 (iterate not true)) 45 | 46 | ;; range laziness 47 | (def s (range 1e20)) 48 | (def xs (map #(do (print ".") %) (range 100))) 49 | (take 5 xs) 50 | (last (take 33 xs)) 51 | 52 | ;; at the profiler (see below) 53 | (last (range 1000000000)) 54 | 55 | ``` 56 | 57 | Visualizing `range` laziness at the profiler. In general you need to pay attention at not retaining the head of the sequence, which sometimes happens in subtle ways. 58 | 59 | ![range at the profiler](https://github.com/reborg/clojure-pills/blob/master/pics/004-range.png) 60 | -------------------------------------------------------------------------------- /doc/005-trampoline.md: -------------------------------------------------------------------------------- 1 | ## 005 trampoline 2 | 3 | Screencast link: https://youtu.be/QrkRlQymfyw 4 | 5 | See also chapter 2.4.3 of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | ### At the REPL 8 | 9 | ```clojure 10 | (source trampoline) 11 | (fn? +) 12 | ([1 2 3] 2) 13 | (fn? []) 14 | (ifn? []) 15 | 16 | (defn caller [f n] 17 | (let [ret (f)] 18 | (if (zero? n) 19 | ret 20 | (caller f (dec n))))) 21 | 22 | (caller #(println ".") 10) 23 | (caller + 10) 24 | (+) 25 | (caller + 10) 26 | (caller + 10000) 27 | 28 | (defn caller [f n] 29 | (let [ret (f)] 30 | (if (zero? n) 31 | ret 32 | #(caller f (dec n))))) 33 | 34 | (caller + 2) 35 | (((caller + 2))) 36 | (trampoline caller + 10) 37 | (trampoline caller + 2) 38 | 39 | (declare is-odd?) 40 | (defn is-even? [n] (or (zero? n) (is-odd? (dec n)))) 41 | (defn is-odd? [n] (and (not (zero? n)) (is-even? (dec n)))) 42 | (is-even? 120) 43 | (is-even? 121) 44 | (is-odd? 121) 45 | (is-odd? 12000) 46 | (defn is-even? [n] (or (zero? n) #(is-odd? (dec n)))) 47 | (defn is-odd? [n] (and (not (zero? n)) #(is-even? (dec n)))) 48 | (trampoline is-odd? 12000) 49 | ``` 50 | -------------------------------------------------------------------------------- /doc/006-memoize.md: -------------------------------------------------------------------------------- 1 | ## 006 memoize 2 | 3 | Screencast link: https://youtu.be/rgAyFY1oF0s 4 | 5 | See also chapter 2.4.2 of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | ### Summary 8 | 9 | * Memoize contract and core goals, simple example. 10 | * Memoizing the powerset algorithm. 11 | * Problems with the key-space. 12 | * Different solution and improvement. 13 | 14 | ### At the REPL 15 | 16 | ```clojure 17 | 18 | (defn busy [a b] 19 | (println "busy" a b) 20 | (+ a b)) 21 | 22 | (busy 1 2) 23 | (busy 1 2) 24 | 25 | (def busymemo (memoize busy)) 26 | 27 | (busymemo 3 2) 28 | (busymemo 3 2) 29 | 30 | (defn powerset [[head & remaining]] 31 | (if head 32 | (let [subsets (powerset remaining)] 33 | (concat subsets (map (partial cons head) subsets))) 34 | '(()))) 35 | 36 | (powerset "abcd") 37 | (map set (powerset "abcd")) 38 | (count (powerset "abcd")) 39 | (Math/pow 2 4) 40 | 41 | (time (dorun (powerset (range 10)))) 42 | (time (dorun (powerset (range 20)))) 43 | (time (dorun (powerset (range 21)))) 44 | 45 | (def powerset 46 | (memoize 47 | (fn [[head & remaining]] 48 | (if head 49 | (let [subsets (powerset remaining)] 50 | (concat subsets (map (partial cons head) subsets))) 51 | '(()))))) 52 | 53 | (time (dorun (powerset (range 10)))) 54 | (time (dorun (powerset (range 10)))) 55 | (time (dorun (powerset (range 20)))) 56 | (time (dorun (powerset (range 20)))) 57 | (time (dorun (powerset (range 21)))) 58 | (time (dorun (powerset (range 21)))) 59 | 60 | (defn powerset [[head & remaining]] 61 | (println "Called with:" (cons head remaining)) 62 | (if head 63 | (let [subsets (powerset remaining)] 64 | (concat subsets (map (partial cons head) subsets))) 65 | '(()))) 66 | 67 | (dorun (powerset "abc")) 68 | (dorun (powerset "abcd")) 69 | (dorun (powerset "abcde")) 70 | 71 | (defn powerset [xs] 72 | (println "Called with:" xs) 73 | (let [remaining (butlast xs) 74 | head (last xs)] 75 | (if head 76 | (let [subsets (powerset remaining)] 77 | (concat subsets (map (partial cons head) subsets))) 78 | '(())))) 79 | 80 | (dorun (powerset "abc")) 81 | (dorun (powerset "abcd")) 82 | (dorun (powerset "abcde")) 83 | (powerset "abcd") 84 | 85 | (def powerset 86 | (memoize 87 | (fn [xs] 88 | (let [remaining (butlast xs) 89 | head (last xs)] 90 | (if head 91 | (let [subsets (powerset remaining)] 92 | (concat subsets (map (partial cons head) subsets))) 93 | '(())))))) 94 | 95 | (time (dorun (powerset (range 10)))) 96 | (time (dorun (powerset (range 10)))) 97 | (time (dorun (powerset (range 20)))) 98 | (time (dorun (powerset (range 20)))) 99 | (time (dorun (powerset (range 21)))) 100 | (time (dorun (powerset (range 21)))) 101 | 102 | ``` 103 | -------------------------------------------------------------------------------- /doc/007-subvec.md: -------------------------------------------------------------------------------- 1 | ## 007 subvec 2 | 3 | Screencast link: https://youtu.be/-TqI45IXdCk 4 | 5 | Not yet released, but available soon as a chapter of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | ### Summary 8 | 9 | * `subvec` vector operation, context, reasons, relationship with `mapv`, `filterv` and `reduce-kv`. 10 | * `subvec` contract, notable exceptions, relationship with referenced vector. 11 | * `remove-at` example. 12 | * `pmapv` example. 13 | * performance: overall speed. 14 | * performance: GC side effect on retained vector. 15 | 16 | ### At the REPL 17 | 18 | ```clojure 19 | 20 | ;; ============== contract 21 | 22 | (subvec [1 2 3 4] 1 3) 23 | (subvec [1 2 3 4] 1) 24 | 25 | (def subv (subvec (vector-of :int 1 2 3) 1)) 26 | (conj subv \a) 27 | (conj subv nil) ;; ! 28 | 29 | ;; ============== example 1 30 | 31 | (defn remove-at [v idx] 32 | (into (subvec v 0 idx) 33 | (subvec v (inc idx) (count v)))) 34 | 35 | (remove-at [0 1 2 3 4 5] 3) 36 | 37 | ;; ============== example 2 38 | 39 | (defn firstv [v] (nth v 0)) 40 | (defn restv [v] (subvec v 1)) 41 | 42 | (defn norm [v] 43 | (loop [v v 44 | res 0.] 45 | (if (= 0 (count v)) 46 | (Math/sqrt res) 47 | (recur (restv v) 48 | (+ res (Math/pow (firstv v) 2)))))) 49 | 50 | (norm [-3 4]) 51 | 52 | ;; ============== example 3 53 | 54 | ;; Straight from reducers.clj 55 | 56 | (def pool (delay (java.util.concurrent.ForkJoinPool.))) 57 | (defn fjtask [^Callable f] (java.util.concurrent.ForkJoinTask/adapt f)) 58 | 59 | (defn- fjinvoke [f] 60 | (if (java.util.concurrent.ForkJoinTask/inForkJoinPool) 61 | (f) 62 | (.invoke ^java.util.concurrent.ForkJoinPool 63 | @pool ^java.util.concurrent.ForkJoinTask 64 | (fjtask f)))) 65 | 66 | (defn- fjfork [task] (.fork ^java.util.concurrent.ForkJoinTask task)) 67 | (defn- fjjoin [task] (.join ^java.util.concurrent.ForkJoinTask task)) 68 | 69 | (defn pmapv [f v & [n]] 70 | (let [n (or n (+ 2 (.. Runtime getRuntime availableProcessors)))] 71 | (cond 72 | (empty? v) [] 73 | (<= (count v) n) (mapv f v) 74 | :else 75 | (let [split (quot (count v) 2) 76 | v1 (subvec v 0 split) 77 | v2 (subvec v split (count v)) 78 | fc (fn [child] #(pmapv f child n))] 79 | (fjinvoke 80 | #(let [f1 (fc v1) 81 | t2 (fjtask (fc v2))] 82 | (fjfork t2) 83 | (into (f1) (fjjoin t2)))))))) 84 | 85 | (defn slow-inc [n] (do (Thread/sleep 10) (inc n))) 86 | 87 | (let [v (vec (range 1000))] (dorun (time (mapv slow-inc v)))) 88 | (let [v (vec (range 1000))] (dorun (time (pmapv slow-inc v)))) 89 | 90 | ;; ============== perf 1 91 | 92 | (require '[criterium.core :refer [quick-bench]]) 93 | 94 | (defn norm [v] 95 | (loop [v v 96 | res 0. 97 | idx (dec (count v))] 98 | (if (< idx 0) 99 | (Math/sqrt res) 100 | (recur (subvec v 0 idx) 101 | (+ res (Math/pow (peek v) 2)) 102 | (dec idx))))) 103 | 104 | (let [v (vec (range 1000))] (quick-bench (norm v))) 105 | 106 | (defn norm-idx [v] 107 | (loop [idx (dec (count v)) 108 | res 0.] 109 | (if (< idx 0) 110 | (Math/sqrt res) 111 | (recur (dec idx) 112 | (+ res (Math/pow (nth v idx) 2)))))) 113 | 114 | (let [v (vec (range 1000))] (quick-bench (norm-idx v))) 115 | 116 | ;; ============== perf 2 117 | 118 | (defn bigv [n] (vec (range n))) 119 | 120 | (let [v1 (subvec (bigv 1e7) 0 5) 121 | v2 (subvec (bigv 1e7) 5 10)] 122 | (into v1 v2)) 123 | 124 | (let [v1 (into [] (subvec (bigv 1e7) 0 5)) 125 | v2 (into [] (subvec (bigv 1e7) 5 10))] 126 | (into v1 v2)) 127 | 128 | ``` 129 | -------------------------------------------------------------------------------- /doc/008-defmacro.md: -------------------------------------------------------------------------------- 1 | ## 008 defmacro 2 | 3 | Screencast link: https://youtu.be/msxG2rHcUaM 4 | 5 | Also see chapter 4.1 of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | ## Errata 8 | 9 | * At around 6:15 I confused the meaning of invoking a Symbol as a function. The symbol is looked up in the collection passed as the first argument (an ILookup to be precise). If the first argument is not a collection (like the case here) the default is returned so: 10 | 11 | ('a {'a 1 'b 2}) 12 | ;; 1 13 | 14 | There is no lookup in the interned symbol table (which doesn't exist, I was confusing it with Keywords). 15 | 16 | ### Summary 17 | 18 | * Growing a little debug helper, how to show the initial form? 19 | * Problems showing nested forms 20 | * Problems with evaluation of symbols and accidental capturing of locals 21 | * A better approach based on syntax-quote 22 | * Final defmacro version 23 | * How defmacro works 24 | * Using defn facilities 25 | * Performance considerations 26 | 27 | ### At the REPL 28 | 29 | ```clojure 30 | 31 | ;; === a simple debugging tool === 32 | 33 | (/ (* 3 (+ 1 1)) 10.) 34 | ;; 0.6 35 | ;; But I'd like to see something like: 36 | ;; (+ 1 1) is 2 37 | ;; 0.6 38 | 39 | (defn ? [form] 40 | (let [res (form)] 41 | (println form "is" res) 42 | res)) 43 | 44 | (/ (* 3 (? #(+ 1 1))) 10.) 45 | 46 | (defn ? [form] 47 | (let [res (apply (first form) (rest form))] 48 | (println form "is" res) 49 | res)) 50 | 51 | (/ (* 3 (? '(+ 1 1))) 10.) 52 | 53 | (type (first '(+ 1 1))) 54 | 55 | ('+ 1 1) 56 | 57 | (defn ? [form] 58 | (let [res (apply (ns-resolve *ns* (first form)) (rest form))] 59 | (println form "is" res) 60 | res)) 61 | 62 | (/ (* 3 (? '(+ 1 1))) 10.) 63 | 64 | (/ (? '(* 3 (? '(+ 1 1)))) 10.) 65 | 66 | ;; === A better approach === 67 | 68 | (defn ? [_ _ form] ;; 2 additional params 69 | (let [res (eval form)] ;; need to eval explicitly 70 | (println form "is" res) 71 | res)) 72 | (. (var ?) (setMacro)) 73 | 74 | (* 3 (? (+ 1 1))) 75 | (/ (? (* 3 (? (+ 1 1)))) 10.) 76 | 77 | (* 3 (let [res 5] (? (+ 1 res)))) ;; bang! 78 | 79 | (* 3 80 | (let [res 5] 81 | (let [res (eval '(+ 1 res))] 82 | (println form "is" res) 83 | res))) 84 | 85 | (eval '(+ 1 res)) ;; !! 86 | 87 | ;; === The correct approach === 88 | 89 | (defn ? [_ _ form] 90 | `(let [res# ~form] 91 | (println '~form '~'is res#) 92 | res#)) 93 | (. (var ?) (setMacro)) 94 | 95 | (* 3 (let [res 5] (? (+ 1 res))) 96 | 97 | (defmacro ? [form] 98 | `(let [res# ~form] 99 | (println '~form '~'is res#) 100 | res#)) 101 | 102 | (* 3 (let [res 5] (? (+ 1 res)))) ;; finally! 103 | 104 | (defmacro ? [form] 105 | `(let [res# ~form] 106 | (println '~&form '~'is res#) 107 | res#)) 108 | 109 | (* 3 (let [res 5] (? (+ 1 res)))) ;; finally! 110 | 111 | ;; === how does it work === 112 | 113 | (macroexpand '(defmacro simple [])) 114 | 115 | ;; === contract === 116 | 117 | (defmacro ^:dbg ? 118 | "Prints the result of the intermediate evaluations." 119 | ([form] 120 | {:pre [(some? form)]} 121 | `(? ~form #'prn)) 122 | ([form f] 123 | `(let [res# ~form] 124 | (~f '~form '~'is res#) 125 | res#))) 126 | 127 | (* 3 (? nil)) ;; assertion error 128 | (* 3 (? (+ 1 1) clojure.pprint/write)) ;; different printing 129 | 130 | ;; === perf === 131 | 132 | ``` 133 | -------------------------------------------------------------------------------- /doc/009-diff.md: -------------------------------------------------------------------------------- 1 | ## 009 diff 2 | 3 | Screencast link: https://youtu.be/-KQ5K-MW4AU 4 | 5 | Also see chapter 6.6 of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | ### Summary 8 | 9 | * Need to require namespace, early example of usage of protocols 10 | * Arbitrarily nested data structures 11 | * Comparable categories 12 | * What is not comparable? 13 | * How to use the results of diff to build additional processing of differences 14 | * Complexity of traversal, stack consumption. 15 | 16 | ### At the REPL 17 | 18 | ```clojure 19 | (require '[clojure.data :as d]) 20 | 21 | (d/diff {'a \1 :b "2"} {:b "2" :c "3"}) 22 | 23 | (d/diff {'a \1 :b [1N 2N {:k "v1"}]} 24 | {:c \3 :b [1N 2N {:k "v2"}]}) 25 | 26 | ;; ===== What can be compared? How are they compared? ===== 27 | 28 | (d/diff (hash-set 1 2 3) (sorted-set 2 3 4)) 29 | (d/diff (array-map :a 2 :b 4) (hash-map :a 2 :c 5)) 30 | (import 'java.util.ArrayList) 31 | (d/diff (doto (ArrayList.) (.add 1) (.add 2)) (vector-of :int 1 3)) 32 | 33 | ;; ===== Careful! ===== 34 | 35 | (d/diff (vector 1 2 3) (sorted-set 2 3 4)) 36 | (d/diff [1 2 nil 4] [1 2 3 4]) ;; easy to get confused by nils 37 | (d/diff [1 2 3] [1. 2. 3.]) ;; careful, "= semantic" not == 38 | 39 | ;; ===== How to use? For example test automation for html/json/edn ===== 40 | 41 | (require '[clojure.edn :as edn]) 42 | (def prj1 (edn/read-string (slurp "project.clj"))) 43 | (def prj2 (edn/read-string (slurp "sample-project.clj"))) 44 | (def d (d/diff prj1 prj2)) 45 | 46 | (def d1 (first d)) 47 | (def d2 (second d)) 48 | (def d3 (last d)) 49 | 50 | (require '[clojure.set :as s]) 51 | (defn to-hash [d] (apply hash-map (remove nil? d))) 52 | (defn to-keys [d] (into #{} (keys (to-hash d)))) 53 | 54 | (to-keys d1) 55 | 56 | (defn added [d1 d2] (s/difference (to-keys d2) (to-keys d1))) 57 | (defn removed [d1 d2] (s/difference (to-keys d1) (to-keys d2))) 58 | (defn changed [d1 d2] (s/difference (s/union (to-keys d1) (to-keys d2)) (added d1 d2))) 59 | 60 | (added d1 d2) 61 | (removed d1 d2) 62 | (changed d1 d2) 63 | 64 | [(:dependencies (to-hash d1)) (:dependencies (to-hash d2))] 65 | 66 | ;; ===== Implementation details ===== 67 | 68 | (defn generate [n] 69 | (reduce (fn [m e] (assoc-in m (range e) {e e})) {} (range 1 n))) 70 | 71 | (generate 10) 72 | (generate 11) 73 | (d/diff (generate 10) (generate 11)) 74 | 75 | ;; short-circuiting 76 | (doseq [i (range 10 20)] 77 | (print i "level deep: ") 78 | (time (d/diff (generate i) (generate i)))) 79 | 80 | ;; walk-all worst case 81 | (doseq [i (range 10 20)] 82 | (print i "level deep: ") 83 | (time (d/diff (generate i) (generate (inc i))))) 84 | 85 | ``` 86 | -------------------------------------------------------------------------------- /doc/010-into.md: -------------------------------------------------------------------------------- 1 | ## 010 into 2 | 3 | Screencast link: https://youtu.be/XMz14mxRFog 4 | 5 | Also see chapter 7 "Collections" as soon as available on the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | ### Summary 8 | 9 | * "changing" the type of a collection 10 | * follows "conj" semantic 11 | * merging and nils 12 | * transducers enabled! 13 | * metadata handling 14 | * mutables are not supported 15 | * Example: keeping the original type 16 | * Implementation details and performances 17 | 18 | ### At the REPL 19 | 20 | ```clojure 21 | (into #{} (range 10)) 22 | (into [] (range 10)) 23 | (into (sorted-set) (range 10)) 24 | (into {} [[:a 1] [:b 2] [:c 3]]) 25 | 26 | (into '() [1 2 3]) 27 | (import 'clojure.lang.PersistentQueue) 28 | (def q (into (PersistentQueue/EMPTY) [1 2 3])) 29 | (peek q) 30 | 31 | (into (range 10 20) (range 10)) 32 | (into nil #{1 2 3}) 33 | (into nil nil) 34 | 35 | (into (vector-of :int) 36 | (comp (map inc) (filter even?)) 37 | (range 10)) 38 | 39 | (defn sign [c] (with-meta c {:signature (apply str c)})) 40 | (meta (into (sign [1 2 3]) (sign (range 10)))) 41 | 42 | (into (transient []) (range 10)) 43 | (into (int-array []) (range 10)) 44 | (import 'java.util.ArrayList) (into (ArrayList.) (range 10)) 45 | 46 | (defn maintain [fx f coll] 47 | (into (empty coll) (fx f coll))) 48 | 49 | (->> #{1 2 3 4 5} (maintain map inc) (maintain filter odd?)) 50 | 51 | (->> {:a 1 :b 2 :c 5} (maintain filter (comp odd? last))) 52 | 53 | 54 | (require '[criterium.core :refer [quick-benchmark]]) 55 | 56 | (defmacro b [expr] 57 | `(str (first (:mean (quick-benchmark ~expr {}))) " secs")) 58 | 59 | (b (into '() (range 1e6))) 60 | (b (into [] (range 1e6))) 61 | ``` 62 | -------------------------------------------------------------------------------- /doc/011-count.md: -------------------------------------------------------------------------------- 1 | ## 011 count 2 | 3 | Screencast link: https://youtu.be/5xCEZAuPnT0 4 | 5 | Also see chapter 7 "Collections" as soon as available on the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | ### Summary 8 | 9 | * Different counts for different types 10 | * Corner cases and surprising results 11 | * Parsing arguments example 12 | * Counted? 13 | * Constant time to linear time. 14 | 15 | ### At the REPL 16 | 17 | ```clojure 18 | 19 | "Clojure Pills - 011 count" 20 | (clojure-version) 21 | 22 | ;; each type count different things 23 | (count [1 2 3 4 5]) 24 | (count {:a 1 :b 2}) 25 | (defrecord CanICount [a b c]) 26 | (count (CanICount. 1 2 3)) 27 | 28 | ;; corner cases 29 | (count nil) 30 | (count (range (inc Integer/MAX_VALUE))) 31 | (type (count [1])) 32 | 33 | ;; example 34 | (require '[clojure.java.io :as io]) 35 | 36 | (defn- print-usage [] (println "Usage: copy 'file-name' 'to-location' ['work-dir']")) 37 | 38 | (defn- copy 39 | ([in out] 40 | (copy in out "./")) 41 | ([in out dir] 42 | (io/copy (io/file (str dir in)) (io/file out)))) 43 | 44 | (defn -main [& args] 45 | (cond 46 | (< (count args) 2) (print-usage) 47 | (= 2 (count args)) (copy (first args) (second args)) 48 | (> (count args) 2) (copy (first args) (second args) (nth args 2)))) 49 | 50 | (-main "project.clj" "/tmp/copy1.clj") 51 | (-main "copy1.clj" "/tmp/copy2.clj" "/tmp/") 52 | 53 | ;; see also counted? 54 | 55 | (counted? []) 56 | (counted? '()) 57 | (counted? (for [i (range 10)] i)) 58 | (type (for [i (range 10)] i)) 59 | (counted? "asdf") 60 | (counted? (int-array [1 2 3])) 61 | 62 | ;; perf: see summary on the slide 63 | ``` 64 | -------------------------------------------------------------------------------- /doc/012-memfn.md: -------------------------------------------------------------------------------- 1 | ## 012 memfn 2 | 3 | Screencast link: https://youtu.be/b0HGDpc4S-k 4 | 5 | Also see chapter 2.2.7 of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | ### Summary 8 | 9 | * Calling with one argument 10 | * Using memfn with map/collections 11 | * Calls with more than one arguments 12 | * Example: using memfn with Java Instants 13 | * How it is implemented? 14 | * How type hinting works 15 | 16 | ### At the REPL 17 | 18 | ```clojure 19 | 20 | (def toLower (memfn toLowerCase)) 21 | (toLower "ABBA") 22 | 23 | (map toLowerCase ["ONE" "TWO"]) ; 24 | 25 | (map (memfn toLowerCase) ["ONE" "TWO"]) 26 | 27 | (map (memfn indexOf ch) ["abba" "trailer" "dakar"] ["a" "i" "k"]) 28 | (map (memfn indexOf 😱) ["abba" "trailer" "dakar"] ["a" "i" "k"]) 29 | 30 | ;; ============== example 31 | 32 | (import '[java.time Instant Duration]) 33 | 34 | (def instants 35 | (repeatedly (fn [] 36 | (Thread/sleep 100) 37 | (Instant/now)))) 38 | 39 | (take 10 instants) 40 | 41 | (defn durations [instants & [t0]] 42 | (let [start (or t0 (Instant/now))] 43 | (->> instants 44 | (map #(Duration/between % start)) 45 | (map (memfn toMillis)) 46 | (map #(/ % 1000.))))) 47 | 48 | (let [two (doall (take 2 instants))] (durations two)) 49 | 50 | (let [t1 (Instant/now) 51 | times (doall (take 2 instants))] 52 | (Thread/sleep 200) 53 | (first (durations times t1))) 54 | 55 | ;; ============== impl details 56 | 57 | (macroexpand '(memfn toUpperCase)) 58 | (macroexpand '(memfn indexOf ch)) 59 | 60 | ;; ============== perf 61 | 62 | (source memfn) 63 | 64 | (set! *warn-on-reflection* true) 65 | 66 | (time (dotimes [n 100000] 67 | (map (memfn toLowerCase) ["A" "B"]))) 68 | 69 | (time (dotimes [n 100000] 70 | (map (memfn ^String toLowerCase) ["A" "B"]))) 71 | ``` 72 | -------------------------------------------------------------------------------- /doc/013-empty?.md: -------------------------------------------------------------------------------- 1 | ## 013 empty? and not-empty 2 | 3 | Screencast link: https://youtu.be/tbsGaiyqxHk 4 | 5 | Also see chapter 7.1.6 of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | ### Summary 8 | 9 | * Quite easy functions to use. 10 | * Don't confuse with (empty). 11 | * Question mark or not to question mark? 12 | * Small examples. Things to be aware of. 13 | * Calls with more than one arguments. 14 | * Two idiomatic uses. 15 | * What else to look at? 16 | * Can we go faster? 17 | * Lazy sequences stay lazy, almost. 18 | 19 | ### At the REPL 20 | 21 | ```clojure 22 | 23 | ;; Some empty 24 | 25 | (empty? []) 26 | (empty? "") 27 | (empty? nil) 28 | (empty? (int-array [])) 29 | 30 | ;; Some full 31 | 32 | (empty? [1 2 3]) 33 | (empty? (clojure.lang.PersistentQueue/EMPTY)) 34 | (empty? " ") 35 | (empty? "nil") 36 | (empty? [nil]) 37 | (empty? [[]]) 38 | 39 | ;; Some not working 40 | 41 | (empty? \space) 42 | (empty? false) 43 | (empty? (transient [])) 44 | 45 | ;; And what about not-empty 46 | 47 | (not-empty []) 48 | (not-empty [1]) 49 | (not-empty nil) 50 | 51 | ;; Idioms 52 | 53 | (remove empty? [nil "a" {} "" (range 10)]) 54 | 55 | (defn is-digit [s] (every? #(Character/isDigit %) s)) 56 | (defn to-num [s] (and (not-empty s) (is-digit s) (Long/valueOf s))) 57 | 58 | (to-num nil) 59 | (to-num "") 60 | (to-num "a") 61 | (when-let [n (to-num "12")] (* 2 n)) 62 | (when-let [n (to-num "12A")] (* 2 n)) 63 | 64 | ;; Sources 65 | 66 | (source empty?) 67 | 68 | (let [coll [1 2 3]] (seq coll)) 69 | 70 | ;; Perf 71 | 72 | (require '[criterium.core :refer [quick-bench]]) 73 | 74 | (let [v (vec (range 1000))] (quick-bench (empty? v))) 75 | 76 | (let [v (vec (range 1000))] (quick-bench (zero? (count v)))) 77 | 78 | (let [v (vec (range 1000))] (quick-bench (.isEmpty ^java.util.Collection v))) 79 | 80 | ;; Lazy 81 | 82 | (empty? (map #(do (println "realizing" %) %) (range 100))) 83 | (zero? (count (map #(do (println "realizing" %) %) (range 100)))) 84 | 85 | ``` 86 | -------------------------------------------------------------------------------- /doc/014-get.md: -------------------------------------------------------------------------------- 1 | ## 014 get 2 | 3 | Screencast link: https://youtu.be/l02gOPHDCN4 4 | 5 | Also see chapter 7.2.2 of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | ### At the REPL 8 | 9 | ```clojure 10 | 11 | ;; Dedicated to associative data structure, 12 | 13 | (get {:a "a" :b "b"} :a) 14 | (get (transient {:a "a" :b "b"}) :a) 15 | (get (sorted-map :a "a" :b "b") :a) 16 | (get {:a "a" :b "b"} :c ":c not found") 17 | (get nil :k "nope") 18 | (get nil nil "nope") 19 | 20 | ;; but access many other things. 21 | ;; what about sets? 22 | 23 | (get #{"one" "two" "three"} "two") 24 | (get (sorted-set "one" "two" "three") "two") 25 | 26 | ;; vectors? 27 | 28 | (get ["zero" "one" "two"] 1) 29 | (get (transient ["zero" "one" "two"]) 1) 30 | 31 | ;; Wow, what else? 32 | 33 | (get "hello" 0) 34 | (get (int-array [1 2 3]) 0) 35 | (defrecord Address [street city code]) 36 | (get (Address. "High St" "Honolulu" "96805") :city) 37 | (import 'java.util.HashMap) 38 | (get (HashMap. {:a "a" :b "b"}) :b) 39 | 40 | ;; any surprise? 41 | 42 | (get '("one" "two" "three") "one") 43 | (get (transient #{"one" "two" "three"}) "one") 44 | (get (sorted-map 'a "a" 'b "b") :a "not found") 45 | (get [1 2 3 4] 4294967296) 46 | 47 | ;; One typical use 48 | 49 | (defn select-matching [m k] 50 | (let [regex (re-pattern (str ".*" k ".*"))] 51 | (->> (keys m) 52 | (filter #(re-find regex (.toLowerCase %))) 53 | (reduce #(assoc %1 (keyword %2) (get m %2)) {})))) 54 | 55 | (defn search [k] 56 | (merge (select-matching (System/getProperties) k) 57 | (select-matching (System/getenv) k))) 58 | 59 | (search "dir") 60 | 61 | ;; Alternatives 62 | 63 | (find {:a "a" :b "b"} :b) 64 | ({:a "a" :b "b"} :b) 65 | (:b {:a "a" :b "b"}) 66 | (.get {:a "a" :b "b"} :b) 67 | 68 | ;; perf 69 | 70 | ``` 71 | 72 | ![perf](https://github.com/reborg/clojure-pills/blob/master/doc/get-bench.png) 73 | -------------------------------------------------------------------------------- /doc/015-contains?.md: -------------------------------------------------------------------------------- 1 | ## 015 contains? 2 | 3 | Screencast link: https://youtu.be/Ln95283Anww 4 | 5 | Also see chapter 7.2.3 of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | #### summary 8 | 9 | * common use 10 | * Quiz: so given a record (map-like) what is the result of the following? 11 | * less common, with numerically indexed colls 12 | * it must be a number 13 | * it must be below 2^32 14 | * can be used to check for nils 15 | * extended example 16 | * see also 17 | * implementation details and perfs 18 | 19 | ### At the REPL 20 | 21 | ```clojure 22 | 23 | "Clojure Pills - 015 contains?" 24 | 25 | ;; common use 26 | 27 | (contains? {:a "a" :b "b"} :b) 28 | (contains? {:a "a" :b "b"} "b") 29 | (contains? #{:x :y :z} :z) 30 | 31 | ;; Quiz: so given a record (map-like) what is the result of the following? 32 | 33 | (defrecord A [x y z]) 34 | (def myA (A. 1 2 3)) 35 | (contains? myA 1) 36 | 37 | ;; less common, with numerically indexed colls 38 | 39 | (contains? [:a :b :c] 1) 40 | (contains? "1234" 4) 41 | 42 | ;; it must be a number 43 | 44 | (contains? [:a :b :c] :a) 45 | 46 | ;; it must be below 2^32 47 | 48 | (def power-2-32 (long (Math/pow 2 32))) 49 | (contains? [1 2 3] power-2-32) 50 | 51 | ;; can be used to check for nils 52 | 53 | (contains? #{1 2 nil 3 4} nil) 54 | (#{1 2 nil 3 4} nil) 55 | 56 | ;; extended example 57 | 58 | (def sensor-read 59 | [{:id "AR2" :location 2 :status "ok"} 60 | {:id "EF8" :location 2 :status "ok"} 61 | nil 62 | {:id "RR2" :location 1 :status "ok"} 63 | nil 64 | {:id "GT4" :location 1 :status "ok"} 65 | {:id "YR3" :location 4 :status "ok"}]) 66 | 67 | (defn problems? [sensors] 68 | (contains? (into #{} sensors) nil)) 69 | 70 | (defn raise-on-error [sensors] 71 | (if (problems? sensors) 72 | (throw (RuntimeException. 73 | "At least one sensor is malfunctioning")) 74 | :ok)) 75 | 76 | (raise-on-error sensor-read) 77 | 78 | ;; see also 79 | 80 | (some #{:a} [:a :b :c]) 81 | (.contains [:a :b :c] :c) 82 | (.contains "verylongstring" "long") 83 | 84 | ;; implementation details and perfs 85 | 86 | contains? 87 | 88 | ``` 89 | 90 | ![perf](https://github.com/reborg/clojure-pills/blob/master/doc/contains-by-type.png) 91 | -------------------------------------------------------------------------------- /doc/016-reduce.md: -------------------------------------------------------------------------------- 1 | ## 016 reduce 2 | 3 | Screencast link: https://youtu.be/pHbossQGcU8 4 | 5 | Also see chapter 3.5.4 of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | #### summary 8 | 9 | * Intro: a stack consuming VS iterative recursion 10 | * Reduce is the prototypical recursive iterative process: 11 | * Contract 12 | * Example: counting words (aka frequencies redo) 13 | * See also: 14 | * Performances 15 | * See chart 16 | 17 | ![perf](https://github.com/reborg/clojure-pills/blob/master/doc/reduce-bench.png) 18 | 19 | ### At the REPL 20 | 21 | ```clojure 22 | 23 | "Clojure Pills - 016 reduce" 24 | 25 | ;; ============== intro: a stack consuming VS iterative recursion 26 | 27 | (defn fibo [n] 28 | (condp = n 29 | 0 0 30 | 1 1 31 | (+ (fibo (- n 1)) (fibo (- n 2))))) 32 | 33 | (fibo 10) 34 | (fibo 10000) ;; stackover 35 | 36 | (defn fibo [n] 37 | (loop [a 1 b 0 cnt n] 38 | (if (zero? cnt) 39 | b 40 | (recur (+' a b) a (dec cnt))))) 41 | 42 | (fibo 10000) 43 | 44 | ;; reduce is the prototypical recursive iterative process: 45 | 46 | (defn fibo [n] 47 | (reduce 48 | (fn [[a b] cnt] 49 | (if (zero? cnt) 50 | b 51 | [(+' a b) a])) 52 | [1 0] 53 | (range n -1 -1))) 54 | 55 | ;; ============== contract 56 | 57 | (instance? clojure.lang.Seqable []) 58 | (instance? clojure.lang.Seqable (transient [])) 59 | (reduce + (transient (into [] (range 10)))) 60 | 61 | (defn dbg 62 | ([] (println "0-arity")) 63 | ([x] (println "1-arity with" x)) 64 | ([x y] (println "2-arity with" x y)) 65 | ([x y & z] (println "var-arg with" x y z))) 66 | 67 | (reduce dbg nil) 68 | (reduce dbg []) 69 | (reduce dbg "val" []) 70 | 71 | ;; ============= Example: counting words (aka frequencies redo) 72 | 73 | (defn count-occurrences [coll] 74 | (->> coll 75 | (map #(vector % 1)) 76 | (reduce (fn [m [k cnt]] 77 | (assoc m k (+ cnt (get m k 0)))) {}))) 78 | 79 | (defn word-count [s] 80 | (count-occurrences (.split #"\s+" s))) 81 | 82 | (word-count "Counting all words, all the words and sentences.") 83 | 84 | ;; ============= See also: 85 | 86 | (reduce-kv (fn [m k v] (assoc m (keyword v) (name k))) {} {:a "1" :b "2"}) 87 | (reductions + 0 (range 10)) 88 | (reductions (fn [acc itm] (if (> itm 5) (reduced (+ itm acc)) (+ itm acc))) (range 10)) 89 | 90 | ;; ============= Performances 91 | 92 | ;; see chart 93 | 94 | (let [xs (range 1e8)] (reduce + xs)) 95 | (take 10 (reduce merge '() (range 1e8))) ;; ops 96 | (let [xs (range 1e8)] (last xs) (reduce + xs)) ;; ops 97 | 98 | ``` 99 | 100 | -------------------------------------------------------------------------------- /doc/018-fold.md: -------------------------------------------------------------------------------- 1 | ## 018 fold 2 | 3 | Screencast link: https://youtu.be/c6T-uWShtqQ 4 | 5 | Also see chapter 32.1.1 of the [Clojure Standard Library](https://www.manning.com/books/clojure-standard-library) book. 6 | 7 | #### summary 8 | 9 | * Basic examples 10 | * How does it work? The fork-join model. 11 | * Controlling the computation, chunk size, reducef and combinef. 12 | * Working with hash-maps and r/monoid 13 | * Extended example: word-frequencies 14 | * Foldable reducers and transducers. 15 | * Comparing with pmap and performance details. 16 | 17 | ![Fork-Join (or reduce-combine) model diagram.](https://github.com/reborg/clojure-pills/blob/master/doc/18-fold-fork-join-model.png) 18 | 19 | ### At the REPL 20 | 21 | ```clojure 22 | 23 | "Clojure Pills - 018 fold" 24 | 25 | (require '[clojure.core.reducers :as r]) 26 | 27 | ;; Fold looks a lot like reduce 28 | (r/fold + (range 1000)) 29 | (r/fold + (map inc (filter odd? (range 1000)))) 30 | 31 | ;; But carries a secret wapon (for vectors and maps) 32 | (let [coll (doall (range 1e7))] (time (r/fold + coll))) 33 | (let [coll (vec (range 1e7))] (time (r/fold + coll))) 34 | 35 | ;; pic 36 | 37 | ;; fork-join fits nicely to process at the leaf, 38 | ;; but this is not happening if we go "plain sequential": 39 | (->> (vec (range 1000)) 40 | (map inc) 41 | (filter odd?) 42 | (map #(do (println (str (Thread/currentThread))) %)) 43 | (r/fold +)) 44 | 45 | ;; reducers allow deferred processing to happen on each chunk 46 | (->> (vec (range 1000)) 47 | (r/map inc) 48 | (r/filter odd?) 49 | (r/map #(do (println (str (Thread/currentThread))) %)) 50 | (r/fold +)) 51 | 52 | ;; Additional knobs. 53 | ;; combinef can be independent from reducef 54 | (r/fold * / (vec (range 1 600))) 55 | 56 | ;; desired chunk size can change (512 default) 57 | (r/fold 400 * / (vec (range 1 10))) 58 | 59 | ;; either reducef (but combinef takes precedence) zero-arity is used to define init 60 | (r/fold 61 | ; (fn combinef 62 | ; ([] (println "init -500") -500) 63 | ; ([a b] (+ a b))) 64 | (fn reducef 65 | ([] (println "init 0") 0) 66 | ([a b] (+ a b))) 67 | (r/map inc (r/filter odd? (vec (range 1000))))) 68 | 69 | ;; monoid is an helper for that 70 | (r/fold (r/monoid + (constantly -500)) + (r/map inc (r/filter odd? (vec (range 1000))))) 71 | 72 | ;; when working on maps, reducef has 3 args (like reduce-kv) 73 | (r/fold merge (fn [m k v] (assoc m k (str v))) (zipmap (range 2000) (range 2000))) 74 | 75 | ;; Extended example: calculating word-frequencies 76 | (require '[clojure.string :refer [split]]) 77 | 78 | (defn words [coll] 79 | (let [combinef (r/monoid #(merge-with + %1 %2) (constantly {})) 80 | reducef (fn [m [k cnt]] (assoc m k (+ cnt (get m k 0))))] 81 | (r/fold 82 | combinef 83 | reducef 84 | (r/map #(vector % 1) coll)))) 85 | 86 | (def freqs 87 | (-> "http://www.gutenberg.org/files/2600/2600-0.txt" 88 | slurp 89 | (split #"\s+") 90 | words)) 91 | 92 | (take 10 (sort-by second > freqs)) 93 | 94 | ;; Careful, some reducers are not foldable (r/drop, r/take, r/take-while) 95 | ;; this is going to be sequential even if I'm dropping nothing 96 | (->> (range 1000) 97 | (into []) 98 | (r/map range) 99 | (r/mapcat conj) 100 | (r/drop 0) 101 | (r/filter odd?) 102 | (r/fold +)) 103 | 104 | ;; With transducers, just invoke xform on reducef to go parallel: 105 | (r/fold ((comp (map inc) (filter odd?)) +) (vec (range 1000))) 106 | 107 | ;; Careful! Compared to reducers, stateful transducers 108 | ;; are in concurrent access danger: 109 | (distinct 110 | (for [i (range 5000] 111 | (r/fold ((comp (map inc) (drop 10)) +) (vec (range 1000))))) 112 | 113 | ;; A trick to get them right is explained here: 114 | ;; https://gist.github.com/reborg/6cef0d83a5035363bd242510d50dfd2a 115 | 116 | ;; How it compares to pmap? Completely different models. 117 | ;; * pmap on vectors runs concurrently on a number of threads which is multiple of 32 (the default size of a lazy sequence chunk). 118 | ;; * pmap on a lazy-seq will run num.core + 2 thread concurrently. 119 | ;; * fold run on num-core concurrent threads. But if a thread is free, it goes stealing work from a busy one. 120 | ;; The choice bois down to: task predictability, laziness, concurrency model. 121 | ;; Here's the point about predictability: 122 | 123 | (def clock (atom nil)) 124 | (def assorted-tasks (map vector (range) (shuffle (concat (range 0 90) (range 3500 3510))))) 125 | (defn pi [n] (->> (range) (filter odd?) (take n) (map / (cycle [1 -1])) (reduce +) (* 4.0))) 126 | 127 | (let [start (reset! clock (System/currentTimeMillis))] 128 | (dorun 129 | (pmap (fn [[idx t]] 130 | (println "exec" idx t) 131 | (if (= 99 idx) 132 | (swap! clock #(- (System/currentTimeMillis) %)) 133 | (pi t))) 134 | assorted-tasks)) 135 | @clock) 136 | 137 | (time (r/fold 1 (constantly nil) (fn [_ [idx t]] (pi t)) (vec assorted-tasks))) 138 | 139 | ;; more experiements. 140 | ;; Artificial number of cores up to 32. This is goind to execute 64 concurrent threads (and possibly freeze your system!) 141 | (let [pi (fn [n] (->> (range) (filter odd?) (take n) (map / (cycle [1 -1])) (reduce +) (* 4.0))) 142 | coll (vec (range 2000 2250)) 143 | n (+ 2 32) 144 | rets (map #(future (pi %)) coll) 145 | step (fn step [[x & xs :as vs] fs] 146 | (lazy-seq 147 | (if-let [s (seq fs)] 148 | (cons (deref x) (step xs (rest s))) 149 | (map deref vs))))] 150 | (step rets (drop n rets))) 151 | 152 | ;; by Rich: https://groups.google.com/d/msg/clojure/7pXvjjBhzp8/lvBydUXcaN0J 153 | ;; pmap for vectors very similar to fork-join but with agents. No work stealing. 154 | (defn vector-pmap [f v] 155 | (let [n (+ 2 (.. Runtime getRuntime availableProcessors)) 156 | sectn (quot (count v) n) 157 | agents (map #(agent (subvec v (* sectn %) (min (count v) (+ (* sectn %) sectn)))) (range n))] 158 | (doseq [a agents] (send a #(doall (map f %)))) 159 | (apply await agents) 160 | (into [] (apply concat (map deref agents))))) 161 | 162 | (time (dorun 163 | (vector-pmap 164 | (fn [[idx t]] 165 | (println "exec" idx t) 166 | (if (= 99 idx) 167 | (swap! clock #(- (System/currentTimeMillis) %)) 168 | (pi t))) 169 | (vec assorted-tasks)))) 170 | 171 | ``` 172 | -------------------------------------------------------------------------------- /doc/18-fold-fork-join-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/doc/18-fold-fork-join-model.png -------------------------------------------------------------------------------- /doc/get-bench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/doc/get-bench.png -------------------------------------------------------------------------------- /doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to clojure-pills 2 | 3 | Welcome to Clojure Pills, a weekly screencast about the functions in the Clojure standard library! 4 | 5 | This folder contains show notes for each episode, including the snippets illustrated at the REPL or other code that might be useful. Enjoy! 6 | 7 | ### Next episodes ideas? 8 | 9 | * [ ] = is a good one, introducing all the equality stuff. It can be one of those seemingly simple operator that instead opens up endless discussions. 10 | * [ ] constantly: is one of the free samples. Should be easy and simple enough but I just did another higher order, would wait. 11 | * [ ] condp goes into some interesting details about the so many forms and the history regarding it. But no perf analysis on its own. I could revise it. 12 | * [ ] defn: I'd like to tackle defn at some point, but probably in a two parts screencast. 13 | * [ ] for is when I want some complete tool. Probably a two part, or a long-ish one. 14 | * [x] trampoline: I though about trampoline as a bit esoteric, with strong connections to functional programming and recursion. 15 | -------------------------------------------------------------------------------- /doc/reduce-bench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/doc/reduce-bench.png -------------------------------------------------------------------------------- /pics/004-range.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/pics/004-range.png -------------------------------------------------------------------------------- /pics/15-into-vector-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/pics/15-into-vector-list.png -------------------------------------------------------------------------------- /pics/Borgatti-MEAP-HI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/pics/Borgatti-MEAP-HI.jpg -------------------------------------------------------------------------------- /pics/Screen Shot 2017-01-24 at 20.52.42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/pics/Screen Shot 2017-01-24 at 20.52.42.png -------------------------------------------------------------------------------- /pics/contains-by-type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/pics/contains-by-type.png -------------------------------------------------------------------------------- /pics/pills.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/pics/pills.jpg -------------------------------------------------------------------------------- /pics/splash-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/pics/splash-small.png -------------------------------------------------------------------------------- /pics/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/pics/splash.png -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject clojure-pills "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :plugins [[lein-nodisassemble "0.1.3"] 7 | [venantius/ultra "0.5.1"]] 8 | :jvm-opts ["-Xmx2G"] 9 | :dependencies [[org.clojure/clojure "1.8.0"] 10 | [criterium "0.4.3"]]) 11 | -------------------------------------------------------------------------------- /sample-project.clj: -------------------------------------------------------------------------------- 1 | (defproject clojure-pills "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :plugins [[lein-nodisassemble "0.1.3"] 7 | [venantius/ultra "0.5.1"]] 8 | :jvm-opts ["-Xmx2G"] 9 | :java-source-paths ["java"] 10 | :dependencies [[org.clojure/clojure "1.8.0"] 11 | [criterium "0.4.3"] 12 | [enlive "1.1.6"]]) 13 | -------------------------------------------------------------------------------- /slides/001-fnil.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/001-fnil.key -------------------------------------------------------------------------------- /slides/002-case.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/002-case.key -------------------------------------------------------------------------------- /slides/003-juxt.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/003-juxt.key -------------------------------------------------------------------------------- /slides/004-range.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/004-range.key -------------------------------------------------------------------------------- /slides/005-trampoline.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/005-trampoline.key -------------------------------------------------------------------------------- /slides/006-memoize.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/006-memoize.key -------------------------------------------------------------------------------- /slides/007-subvec.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/007-subvec.key -------------------------------------------------------------------------------- /slides/008-defmacro.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/008-defmacro.key -------------------------------------------------------------------------------- /slides/009-data:diff.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/009-data:diff.key -------------------------------------------------------------------------------- /slides/010-into.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/010-into.key -------------------------------------------------------------------------------- /slides/011-count.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/011-count.key -------------------------------------------------------------------------------- /slides/012-memfn.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/012-memfn.key -------------------------------------------------------------------------------- /slides/013-empty?.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/013-empty?.key -------------------------------------------------------------------------------- /slides/014-get.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/014-get.key -------------------------------------------------------------------------------- /slides/015-contains?.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/015-contains?.key -------------------------------------------------------------------------------- /slides/016-reduce.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/016-reduce.key -------------------------------------------------------------------------------- /slides/017-the-life-of-an-s-expression.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/017-the-life-of-an-s-expression.key -------------------------------------------------------------------------------- /slides/018-fold.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reborg/clojure-pills/8b407f14bbb3529a70e9a30e480774efbcf9802a/slides/018-fold.key -------------------------------------------------------------------------------- /src/clojure_pills/contains.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-pills.contains) 2 | 3 | "Clojure Pills - 015 contains?" 4 | 5 | ;; common use 6 | 7 | (contains? {:a "a" :b "b"} :b) 8 | (contains? {:a "a" :b "b"} "b") 9 | (contains? #{:x :y :z} :z) 10 | 11 | ;; Quiz: so given a record (map-like) what is the result of the following? 12 | 13 | (defrecord A [x y z]) 14 | (def myA (A. 1 2 3)) 15 | (contains? myA 1) 16 | 17 | ;; less common, with numerically indexed colls 18 | 19 | (contains? [:a :b :c] 1) 20 | (contains? "1234" 4) 21 | 22 | ;; it must be a number 23 | 24 | (contains? [:a :b :c] :a) 25 | 26 | ;; it must be below 2^32 27 | 28 | (def power-2-32 (long (Math/pow 2 32))) 29 | (contains? [1 2 3] power-2-32) 30 | 31 | ;; can be used to check for nils 32 | 33 | (contains? #{1 2 nil 3 4} nil) 34 | (#{1 2 nil 3 4} nil) 35 | 36 | ;; extended example 37 | 38 | (def sensor-read 39 | [{:id "AR2" :location 2 :status "ok"} 40 | {:id "EF8" :location 2 :status "ok"} 41 | nil 42 | {:id "RR2" :location 1 :status "ok"} 43 | nil 44 | {:id "GT4" :location 1 :status "ok"} 45 | {:id "YR3" :location 4 :status "ok"}]) 46 | 47 | (defn problems? [sensors] 48 | (contains? (into #{} sensors) nil)) 49 | 50 | (defn raise-on-error [sensors] 51 | (if (problems? sensors) 52 | (throw (RuntimeException. 53 | "At least one sensor is malfunctioning")) 54 | :ok)) 55 | 56 | (raise-on-error sensor-read) 57 | 58 | ;; see also 59 | 60 | (some #{:a} [:a :b :c]) 61 | (.contains [:a :b :c] :c) 62 | (.contains "verylongstring" "long") 63 | 64 | ;; implementation details and perfs 65 | 66 | contains? 67 | -------------------------------------------------------------------------------- /src/clojure_pills/count.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-pills.count) 2 | 3 | "Clojure Pills - 011 count" 4 | (clojure-version) 5 | 6 | ;; each type count different things 7 | (count [1 2 3 4 5]) 8 | (count {:a 1 :b 2}) 9 | (defrecord CanICount [a b c]) 10 | (count (CanICount. 1 2 3)) 11 | 12 | ;; corner cases 13 | (count nil) 14 | (count (range (inc Integer/MAX_VALUE))) 15 | (type (count [1])) 16 | 17 | ;; example 18 | (require '[clojure.java.io :as io]) 19 | 20 | (defn- print-usage [] (println "Usage: copy 'file-name' 'to-location' ['work-dir']")) 21 | 22 | (defn- copy 23 | ([in out] 24 | (copy in out "./")) 25 | ([in out dir] 26 | (io/copy (io/file (str dir in)) (io/file out)))) 27 | 28 | (defn -main [& args] 29 | (cond 30 | (< (count args) 2) (print-usage) 31 | (= 2 (count args)) (copy (first args) (second args)) 32 | (> (count args) 2) (copy (first args) (second args) (nth args 2)))) 33 | 34 | (-main "project.clj" "/tmp/copy1.clj") 35 | (-main "copy1.clj" "/tmp/copy2.clj" "/tmp/") 36 | 37 | ;; see also counted? 38 | 39 | (counted? []) 40 | (counted? '()) 41 | (counted? (for [i (range 10)] i)) 42 | (type (for [i (range 10)] i)) 43 | (counted? "asdf") 44 | (counted? (int-array [1 2 3])) 45 | 46 | ;; perf: see summary on the slide 47 | -------------------------------------------------------------------------------- /src/clojure_pills/defmacroz.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-pills.defmacroz) 2 | 3 | "Clojure Pills - 008 defmacro" 4 | 5 | ;; === a simple debugging tool === 6 | 7 | (/ (* 3 (+ 1 1)) 10.) 8 | ;; 0.6 9 | ;; But I'd like to see something like: 10 | ;; (+ 1 1) is 2 11 | ;; 0.6 12 | 13 | (defn ? [form] 14 | (let [res (form)] 15 | (println form "is" res) 16 | res)) 17 | 18 | (/ (* 3 (? #(+ 1 1))) 10.) 19 | 20 | (defn ? [form] 21 | (let [res (apply (first form) (rest form))] 22 | (println form "is" res) 23 | res)) 24 | 25 | (/ (* 3 (? '(+ 1 1))) 10.) 26 | 27 | (type (first '(+ 1 1))) 28 | 29 | ('+ 1 1) 30 | 31 | (defn ? [form] 32 | (let [res (apply (ns-resolve *ns* (first form)) (rest form))] 33 | (println form "is" res) 34 | res)) 35 | 36 | (/ (* 3 (? '(+ 1 1))) 10.) 37 | 38 | (/ (? '(* 3 (? '(+ 1 1)))) 10.) 39 | 40 | ;; === A better approach === 41 | 42 | (defn ? [_ _ form] ;; 2 additional params 43 | (let [res (eval form)] ;; need to eval explicitly 44 | (println form "is" res) 45 | res)) 46 | (. (var ?) (setMacro)) 47 | 48 | (* 3 (? (+ 1 1))) 49 | (/ (? (* 3 (? (+ 1 1)))) 10.) 50 | 51 | (* 3 (let [res 5] (? (+ 1 res)))) ;; bang! 52 | 53 | (* 3 54 | (let [res 5] 55 | (let [res (eval '(+ 1 res))] 56 | (println form "is" res) 57 | res))) 58 | 59 | (eval '(+ 1 res)) ;; !! 60 | 61 | ;; === The correct approach === 62 | 63 | (defn ? [_ _ form] 64 | `(let [res# ~form] 65 | (println '~form '~'is res#) 66 | res#)) 67 | (. (var ?) (setMacro)) 68 | 69 | (* 3 (let [res 5] (? (+ 1 res))) 70 | 71 | (defmacro ? [form] 72 | `(let [res# ~form] 73 | (println '~form '~'is res#) 74 | res#)) 75 | 76 | (* 3 (let [res 5] (? (+ 1 res)))) ;; finally! 77 | 78 | (defmacro ? [form] 79 | `(let [res# ~form] 80 | (println '~&form '~'is res#) 81 | res#)) 82 | 83 | (* 3 (let [res 5] (? (+ 1 res)))) ;; finally! 84 | 85 | ;; === how does it work === 86 | 87 | (macroexpand '(defmacro simple [])) 88 | 89 | ;; === contract === 90 | 91 | (defmacro ^:dbg ? 92 | "Prints the result of the intermediate evaluations." 93 | ([form] 94 | {:pre [(some? form)]} 95 | `(? ~form #'prn)) 96 | ([form f] 97 | `(let [res# ~form] 98 | (~f '~form '~'is res#) 99 | res#))) 100 | 101 | (* 3 (? nil)) ;; assertion error 102 | (* 3 (? (+ 1 1) clojure.pprint/write)) ;; different printing 103 | 104 | ;; === perf === 105 | -------------------------------------------------------------------------------- /src/clojure_pills/diff.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-pills.diff) 2 | 3 | "Clojure Pills - 009 clojure.data/diff" 4 | 5 | ;; ===== find differences in arbitrarily nested data structures ===== 6 | 7 | (require '[clojure.data :as d]) 8 | 9 | (d/diff {'a \1 :b "2"} {:b "2" :c "3"}) 10 | 11 | (d/diff {'a \1 :b [1N 2N {:k "v1"}]} 12 | {:c \3 :b [1N 2N {:k "v2"}]}) 13 | 14 | ;; ({:b [nil nil {:k "v1"}] a \1} // ONLY A 15 | ;; {:b [nil nil {:k "v2"}] :c \3} // ONLY B 16 | ;; {:b [1N 2N]}) // COMMON 17 | 18 | ;; ===== What can be compared? How are they compared? ===== 19 | 20 | (d/diff (hash-set 1 2 3) (sorted-set 2 3 4)) 21 | (d/diff (array-map :a 2 :b 4) (hash-map :a 2 :c 5)) 22 | (import 'java.util.ArrayList) 23 | (d/diff (doto (ArrayList.) (.add 1) (.add 2)) (vector-of :int 1 3)) 24 | 25 | (d/diff [1 2 nil 4] [1 2 nil 5]) ;; easy to get confused by nils 26 | 27 | (d/diff [1 2 3] [1. 2. 3.]) ;; careful, "= semantic" not == 28 | 29 | ;; ===== How to use? For example test automation for html/json/edn ===== 30 | 31 | (require '[clojure.edn :as edn]) 32 | (def prj1 (edn/read-string (slurp "project.clj"))) 33 | (def prj2 (edn/read-string (slurp "sample-project.clj"))) 34 | (def d (d/diff prj1 prj2)) 35 | 36 | (def d1 (first d)) 37 | (def d2 (second d)) 38 | (def d3 (last d)) 39 | 40 | (require '[clojure.set :as s]) 41 | (defn to-keys [d] (into #{} (keys (apply hash-map (remove nil? d))))) 42 | 43 | (to-keys d1) 44 | 45 | (defn added [d1 d2] (s/difference (to-keys d2) (to-keys d1))) 46 | (defn removed [d1 d2] (s/difference (to-keys d1) (to-keys d2))) 47 | (defn changed [d1 d2] (s/difference (s/union (to-keys d1) (to-keys d2)) (added d1 d2))) 48 | 49 | ;; ===== Implementation details ===== 50 | 51 | (defn generate [n] 52 | (reduce (fn [m e] (assoc-in m (range e) {e e})) {} (range 1 n))) 53 | 54 | (generate 10) 55 | (generate 11) 56 | (d/diff (generate 10) (generate 11)) 57 | 58 | ;; short-circuiting 59 | (doseq [i (range 10 20)] 60 | (print i "level deep: ") 61 | (time (d/diff (generate i) (generate i)))) 62 | 63 | ;; walk-all worst case 64 | (doseq [i (range 10 20)] 65 | (print i "level deep: ") 66 | (time (d/diff (generate i) (generate (inc i))))) 67 | -------------------------------------------------------------------------------- /src/clojure_pills/empty_qmark.clj: -------------------------------------------------------------------------------- 1 | (ns empty-qmark) 2 | 3 | (println "013 - empty? and not-empty") 4 | 5 | ;; Some empty 6 | 7 | (empty? []) 8 | (empty? "") 9 | (empty? nil) 10 | (empty? (int-array [])) 11 | 12 | ;; Some full 13 | 14 | (empty? [1 2 3]) 15 | (empty? (clojure.lang.PersistentQueue/EMPTY)) 16 | (empty? " ") 17 | (empty? "nil") 18 | (empty? [nil]) 19 | (empty? [[]]) 20 | 21 | ;; Some not working 22 | 23 | (empty? \space) 24 | (empty? false) 25 | (empty? (transient [])) 26 | 27 | ;; And what about not-empty 28 | 29 | (not-empty []) 30 | (not-empty [1]) 31 | (not-empty nil) 32 | 33 | ;; Idioms 34 | 35 | (remove empty? [nil "a" {} "" (range 10)]) 36 | 37 | (defn is-digit [s] (every? #(Character/isDigit %) s)) 38 | (defn to-num [s] (and (not-empty s) (is-digit s) (Long/valueOf s))) 39 | 40 | (to-num nil) 41 | (to-num "") 42 | (to-num "a") 43 | (when-let [n (to-num "12")] (* 2 n)) 44 | (when-let [n (to-num "12A")] (* 2 n)) 45 | 46 | ;; Sources 47 | 48 | (source empty?) 49 | 50 | (let [coll [1 2 3]] (not (empty? coll))) 51 | 52 | ;; Perf 53 | 54 | (require '[criterium.core :refer [quick-bench]]) 55 | 56 | (let [v (vec (range 1000))] (quick-bench (empty? v))) 57 | 58 | (let [v (vec (range 1000))] (quick-bench (zero? (count v)))) 59 | 60 | (let [v (vec (range 1000))] (quick-bench (.isEmpty ^java.util.Collection v))) 61 | 62 | ;; Lazy 63 | 64 | (empty? (map #(do (println "realizing" %) %) (range 100))) 65 | (zero? (count (map #(do (println "realizing" %) %) (range 100)))) 66 | -------------------------------------------------------------------------------- /src/clojure_pills/foldz.clj: -------------------------------------------------------------------------------- 1 | (require '[clojure.core.reducers :as r]) 2 | 3 | ;; Fold looks a lot like reduce 4 | (r/fold + (range 1000)) 5 | (r/fold + (map inc (filter odd? (range 1000)))) 6 | 7 | ;; But carries a secret wapon (for vectors and maps) 8 | (let [coll (doall (range 1e7))] (time (r/fold + coll))) 9 | (let [coll (vec (range 1e7))] (time (r/fold + coll))) 10 | 11 | ;; pic 12 | 13 | ;; fork-join fits nicely to process at the leaf, 14 | ;; but this is not happening if we go "plain sequential": 15 | (->> (vec (range 1000)) 16 | (map inc) 17 | (filter odd?) 18 | (map #(do (println (str (Thread/currentThread))) %)) 19 | (r/fold +)) 20 | 21 | ;; reducers allow deferred processing to happen on each chunk 22 | (->> (vec (range 1000)) 23 | (r/map inc) 24 | (r/filter odd?) 25 | (r/map #(do (println (str (Thread/currentThread))) %)) 26 | (r/fold +)) 27 | 28 | ;; Additional knobs. 29 | ;; combinef can be independent from reducef 30 | (r/fold * / (vec (range 1 600))) 31 | 32 | ;; desired chunk size can change (512 default) 33 | (r/fold 400 * / (vec (range 1 10000))) 34 | 35 | ;; either reducef (but combinef takes precedence) zero-arity is used to define init 36 | (r/fold 37 | (fn combinef 38 | ([] (println "init -500") -500) 39 | ([a b] (+ a b))) 40 | (fn reducef 41 | ([] (println "init 0") 0) 42 | ([a b] (+ a b))) 43 | (r/map inc (r/filter odd? (vec (range 1000))))) 44 | 45 | ;; monoid is an helper for that 46 | (r/fold (r/monoid + (constantly -500)) + (r/map inc (r/filter odd? (vec (range 1000))))) 47 | 48 | ;; when working on maps, reducef has 3 args (like reduce-kv) 49 | (r/fold merge (fn [m k v] (assoc m k (str v))) (zipmap (range 2000) (range 2000))) 50 | 51 | ;; Extended example: calculating word-frequencies 52 | (require '[clojure.string :refer [split]]) 53 | 54 | (defn words [coll] 55 | (let [combinef (r/monoid #(merge-with + %1 %2) (constantly {})) 56 | reducef (fn [m [k cnt]] (assoc m k (+ cnt (get m k 0))))] 57 | (r/fold 58 | combinef 59 | reducef 60 | (r/map #(vector % 1) coll)))) 61 | 62 | (def freqs 63 | (-> "http://www.gutenberg.org/files/2600/2600-0.txt" 64 | slurp 65 | (split #"\s+") 66 | words)) 67 | 68 | (take 10 (sort-by second > freqs)) 69 | 70 | ;; Careful, some reducers are not foldable (r/drop, r/take, r/take-while) 71 | ;; this is going to be sequential even if I'm dropping nothing 72 | (->> (range 1000) 73 | (into []) 74 | (r/map range) 75 | (r/mapcat conj) 76 | (r/drop 0) 77 | (r/map #(do (println (str (Thread/currentThread))) %)) 78 | (r/filter odd?) 79 | (r/fold +)) 80 | 81 | ;; With transducers, just invoke xform on reducef to go parallel: 82 | (r/fold ((comp (map inc) (filter odd?)) +) (vec (range 1000))) 83 | 84 | ;; Careful! Compared to reducers, stateful transducers 85 | ;; are in concurrent access danger: 86 | (distinct 87 | (for [i (range 5000)] 88 | (r/fold ((comp (map inc) (drop 10)) +) (vec (range 1000))))) 89 | 90 | ;; A trick to get them right is explained here: 91 | ;; https://gist.github.com/reborg/6cef0d83a5035363bd242510d50dfd2a 92 | 93 | ;; How it compares to pmap? Completely different models. 94 | ;; * pmap on vectors runs concurrently on a number of threads which is multiple of 32 (the default size of a lazy sequence chunk). 95 | ;; * pmap on a lazy-seq will run num.core + 2 thread concurrently. 96 | ;; * fold run on num-core concurrent threads. But if a thread is free, it goes stealing work from a busy one. 97 | ;; The choice bois down to: task predictability, laziness, concurrency model. 98 | ;; Here's the point about predictability: 99 | 100 | (def clock (atom nil)) 101 | (def assorted-tasks (map vector (range) (shuffle (concat (range 0 90) (range 3500 3510))))) 102 | (defn pi [n] (->> (range) (filter odd?) (take n) (map / (cycle [1 -1])) (reduce +) (* 4.0))) 103 | 104 | (let [start (reset! clock (System/currentTimeMillis))] 105 | (dorun 106 | (pmap (fn [[idx t]] 107 | (println "exec" idx t) 108 | (if (= 99 idx) 109 | (swap! clock #(- (System/currentTimeMillis) %)) 110 | (pi t))) 111 | assorted-tasks)) 112 | @clock) 113 | 114 | (time (r/fold 1 (constantly nil) (fn [_ [idx t]] (pi t)) (vec assorted-tasks))) 115 | 116 | ;; more experiements. 117 | ;; Artificial number of cores up to 32. This is goind to execute 64 concurrent threads (and possibly freeze your system!) 118 | (let [pi (fn [n] (->> (range) (filter odd?) (take n) (map / (cycle [1 -1])) (reduce +) (* 4.0))) 119 | coll (vec (range 2000 2250)) 120 | n (+ 2 32) 121 | rets (map #(future (pi %)) coll) 122 | step (fn step [[x & xs :as vs] fs] 123 | (lazy-seq 124 | (if-let [s (seq fs)] 125 | (cons (deref x) (step xs (rest s))) 126 | (map deref vs))))] 127 | (step rets (drop n rets))) 128 | 129 | ;; by Rich: https://groups.google.com/d/msg/clojure/7pXvjjBhzp8/lvBydUXcaN0J 130 | ;; pmap for vectors very similar to fork-join but with agents. No work stealing. 131 | (defn vector-pmap [f v] 132 | (let [n (+ 2 (.. Runtime getRuntime availableProcessors)) 133 | sectn (quot (count v) n) 134 | agents (map #(agent (subvec v (* sectn %) (min (count v) (+ (* sectn %) sectn)))) (range n))] 135 | (doseq [a agents] (send a #(doall (map f %)))) 136 | (apply await agents) 137 | (into [] (apply concat (map deref agents))))) 138 | 139 | (time (dorun 140 | (vector-pmap 141 | (fn [[idx t]] 142 | (println "exec" idx t) 143 | (if (= 99 idx) 144 | (swap! clock #(- (System/currentTimeMillis) %)) 145 | (pi t))) 146 | (vec assorted-tasks)))) 147 | -------------------------------------------------------------------------------- /src/clojure_pills/getz.clj: -------------------------------------------------------------------------------- 1 | (ns getz) 2 | 3 | (println "014 - get") 4 | 5 | ;; Dedicated to associative data structure, 6 | 7 | (get {:a "a" :b "b"} :a) 8 | (get (transient {:a "a" :b "b"}) :a) 9 | (get (sorted-map :a "a" :b "b") :a) 10 | (get {:a "a" :b "b"} :c ":c not found") 11 | (get nil :k "nope") 12 | (get nil nil "nope") 13 | 14 | ;; but access many other things. 15 | ;; what about sets? 16 | 17 | (get #{"one" "two" "three"} "two") 18 | (get (sorted-set "one" "two" "three") "two") 19 | 20 | ;; vectors? 21 | 22 | (get ["zero" "one" "two"] 1) 23 | (get (transient ["zero" "one" "two"]) 1) 24 | 25 | ;; Wow, what else? 26 | 27 | (get "hello" 0) 28 | (get (int-array [1 2 3]) 0) 29 | (defrecord Address [street city code]) 30 | (get (Address. "High St" "Honolulu" "96805") :city) 31 | (import 'java.util.HashMap) 32 | (get (HashMap. {:a "a" :b "b"}) :b) 33 | 34 | ;; any surprise? 35 | 36 | (get '("one" "two" "three") "one") 37 | (get (transient #{"one" "two" "three"}) "one") 38 | (get (sorted-map 'a "a" 'b "b") :a "not found") 39 | (get [1 2 3 4] 4294967296) 40 | 41 | ;; One typical use 42 | 43 | (defn select-matching [m k] 44 | (let [regex (re-pattern (str ".*" k ".*"))] 45 | (->> (keys m) 46 | (filter #(re-find regex (.toLowerCase %))) 47 | (reduce #(assoc %1 (keyword %2) (get m %2)) {})))) 48 | 49 | (defn search [k] 50 | (merge (select-matching (System/getProperties) k) 51 | (select-matching (System/getenv) k))) 52 | 53 | (search "dir") 54 | 55 | ;; Alternatives 56 | 57 | (find {:a "a" :b "b"} :b) 58 | ({:a "a" :b "b"} :b) 59 | (:b {:a "a" :b "b"}) 60 | (.get {:a "a" :b "b"} :b) 61 | 62 | ;; perf 63 | -------------------------------------------------------------------------------- /src/clojure_pills/into.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-pills.into) 2 | 3 | "Clojure Pills - 010 into" 4 | 5 | ;; "changing" the type of a collection 6 | (into #{} (range 10)) 7 | (into [] (range 10)) 8 | (into (sorted-set) (range 10)) 9 | (into {} [[:a 1] [:b 2] [:c 3]]) 10 | 11 | ;; follows "conj" semantic 12 | (into '() [1 2 3]) 13 | (import 'clojure.lang.PersistentQueue) 14 | (def q (into (PersistentQueue/EMPTY) [1 2 3])) 15 | (peek q) 16 | 17 | ;; merging and nils 18 | (into (range 10 20) (range 10)) 19 | (into nil #{1 2 3}) 20 | (into nil nil) 21 | 22 | ;; transducers enabled! 23 | (into (vector-of :int) 24 | (comp (map inc) (filter even?)) 25 | (range 10)) 26 | 27 | ;; metadata handling 28 | (defn sign [c] (with-meta c {:signature (apply str c)})) 29 | (meta (into (sign [1 2 3]) (sign (range 10)))) 30 | 31 | ;; mutables are not supported 32 | (into (transient []) (range 10)) 33 | (into (int-array []) (range 10)) 34 | (import 'java.util.ArrayList) (into (ArrayList.) (range 10)) 35 | 36 | ;; Example: keeping the original type 37 | 38 | (defn maintain [fx f coll] 39 | (into (empty coll) (fx f coll))) 40 | 41 | (->> #{1 2 3 4 5} (maintain map inc) (maintain filter odd?)) 42 | 43 | (->> {:a 1 :b 2 :c 5} (maintain filter (comp odd? last))) 44 | 45 | ;; Implementation details and performances 46 | 47 | (require '[criterium.core :refer [quick-benchmark]]) 48 | 49 | (defmacro b [expr] 50 | `(str (first (:mean (quick-benchmark ~expr {}))) " secs")) 51 | 52 | (b (into '() (range 1e6))) 53 | (b (into [] (range 1e6))) 54 | -------------------------------------------------------------------------------- /src/clojure_pills/memfnz.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-pills.memfnz) 2 | 3 | "Clojure Pills - 012 memfn" 4 | 5 | ;; ============== contract 6 | 7 | (def toLower (memfn toLowerCase)) 8 | (toLower "ABBA") 9 | 10 | (map toLowerCase ["ONE" "TWO"]) ; 11 | 12 | (map (memfn toLowerCase) ["ONE" "TWO"]) 13 | 14 | (map (memfn indexOf ch) ["abba" "trailer" "dakar"] ["a" "i" "k"]) 15 | (map (memfn indexOf 😱) ["abba" "trailer" "dakar"] ["a" "i" "k"]) 16 | 17 | ;; ============== example 18 | 19 | (import '[java.time Instant Duration]) 20 | 21 | (def instants 22 | (repeatedly (fn [] 23 | (Thread/sleep 100) 24 | (Instant/now)))) 25 | 26 | (take 10 instants) 27 | 28 | (defn durations [instants & [t0]] 29 | (let [start (or t0 (Instant/now))] 30 | (->> instants 31 | (map #(Duration/between % start)) 32 | (map (memfn toMillis)) 33 | (map #(/ % 1000.))))) 34 | 35 | (let [two (doall (take 2 instants))] (durations two)) 36 | 37 | (let [t1 (Instant/now) 38 | times (doall (take 2 instants))] 39 | (Thread/sleep 200) 40 | (first (durations times t1))) 41 | 42 | ;; ============== impl details 43 | 44 | (macroexpand '(memfn toUpperCase)) 45 | (macroexpand '(memfn indexOf ch)) 46 | 47 | ;; ============== perf 48 | 49 | (source memfn) 50 | 51 | (set! *warn-on-reflection* true) 52 | 53 | (time (dotimes [n 100000] 54 | (map (memfn toLowerCase) ["A" "B"]))) 55 | 56 | (time (dotimes [n 100000] 57 | (map (memfn ^String toLowerCase) ["A" "B"]))) 58 | -------------------------------------------------------------------------------- /src/clojure_pills/memo.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-pills.memo) 2 | 3 | "Clojure Pills - 006 Memoize" 4 | 5 | (defn busy [a b] 6 | (println "busy" a b) 7 | (+ a b)) 8 | 9 | (busy 1 2) 10 | (busy 1 2) 11 | 12 | (def busymemo (memoize busy)) 13 | 14 | (busymemo 3 2) 15 | (busymemo 3 2) 16 | 17 | (defn powerset [[head & remaining]] 18 | (if head 19 | (let [subsets (powerset remaining)] 20 | (concat subsets (map (partial cons head) subsets))) 21 | '(()))) 22 | 23 | (powerset "abcd") 24 | (map set (powerset "abcd")) 25 | (count (powerset "abcd")) 26 | (Math/pow 2 4) 27 | 28 | (time (dorun (powerset (range 10)))) 29 | (time (dorun (powerset (range 20)))) 30 | (time (dorun (powerset (range 21)))) 31 | 32 | (def powerset 33 | (memoize 34 | (fn [[head & remaining]] 35 | (if head 36 | (let [subsets (powerset remaining)] 37 | (concat subsets (map (partial cons head) subsets))) 38 | '(()))))) 39 | 40 | (time (dorun (powerset (range 10)))) 41 | (time (dorun (powerset (range 10)))) 42 | (time (dorun (powerset (range 20)))) 43 | (time (dorun (powerset (range 20)))) 44 | (time (dorun (powerset (range 21)))) 45 | (time (dorun (powerset (range 21)))) 46 | 47 | (defn powerset [[head & remaining]] 48 | (println "Called with:" (cons head remaining)) 49 | (if head 50 | (let [subsets (powerset remaining)] 51 | (concat subsets (map (partial cons head) subsets))) 52 | '(()))) 53 | 54 | (dorun (powerset "abc")) 55 | (dorun (powerset "abcd")) 56 | (dorun (powerset "abcde")) 57 | 58 | (defn powerset [xs] 59 | (println "Called with:" xs) 60 | (let [remaining (butlast xs) 61 | head (last xs)] 62 | (if head 63 | (let [subsets (powerset remaining)] 64 | (concat subsets (map (partial cons head) subsets))) 65 | '(())))) 66 | 67 | (dorun (powerset "abc")) 68 | (dorun (powerset "abcd")) 69 | (dorun (powerset "abcde")) 70 | (powerset "abcd") 71 | 72 | (def powerset 73 | (memoize 74 | (fn [xs] 75 | (let [remaining (butlast xs) 76 | head (last xs)] 77 | (if head 78 | (let [subsets (powerset remaining)] 79 | (concat subsets (map (partial cons head) subsets))) 80 | '(())))))) 81 | 82 | (time (dorun (powerset (range 10)))) 83 | (time (dorun (powerset (range 10)))) 84 | (time (dorun (powerset (range 20)))) 85 | (time (dorun (powerset (range 20)))) 86 | (time (dorun (powerset (range 21)))) 87 | (time (dorun (powerset (range 21)))) 88 | 89 | (defn memoize2 [f] 90 | (let [mem (atom {}) 91 | hits (atom 0) 92 | miss (atom 0) 93 | calls (atom 0)] 94 | (fn [& args] 95 | (if (identical? :stats (first args)) 96 | {:calls @calls 97 | :hits @hits 98 | :misses @miss 99 | :keys (count @mem)} 100 | (do 101 | (swap! calls inc) 102 | (if-let [e (find @mem args)] 103 | (do (swap! hits inc) (val e)) 104 | (let [ret (apply f args)] 105 | (swap! miss inc) 106 | (swap! mem assoc args ret) 107 | ret))))))) 108 | 109 | (def powerset 110 | (memoize2 111 | (fn [xs] 112 | (let [remaining (butlast xs) 113 | head (last xs)] 114 | (if head 115 | (let [subsets (powerset remaining)] 116 | (concat subsets (map (partial cons head) subsets))) 117 | '(())))))) 118 | 119 | (time (dorun (powerset (range 20)))) (powerset :stats nil) 120 | (time (dorun (powerset (range 20)))) (powerset :stats nil) 121 | (time (dorun (powerset (range 21)))) (powerset :stats nil) 122 | (time (dorun (powerset (range 22)))) (powerset :stats nil) 123 | -------------------------------------------------------------------------------- /src/clojure_pills/reduc.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-pills.reduc) 2 | 3 | "Clojure Pills - 016 reduce" 4 | 5 | ;; ============== intro: a stack consuming VS iterative recursion 6 | 7 | (defn fibo [n] 8 | (condp = n 9 | 0 0 10 | 1 1 11 | (+ (fibo (- n 1)) (fibo (- n 2))))) 12 | 13 | (fibo 10) 14 | (fibo 10000) ;; stackover 15 | 16 | (defn fibo [n] 17 | (loop [a 1 b 0 cnt n] 18 | (if (zero? cnt) 19 | b 20 | (recur (+' a b) a (dec cnt))))) 21 | 22 | (fibo 10000) 23 | 24 | ;; reduce is the prototypical recursive iterative process: 25 | 26 | (defn fibo [n] 27 | (reduce 28 | (fn [[a b] cnt] 29 | (if (zero? cnt) 30 | b 31 | [(+' a b) a])) 32 | [1 0] 33 | (range n -1 -1))) 34 | 35 | ;; ============== contract 36 | 37 | (instance? clojure.lang.Seqable []) 38 | (instance? clojure.lang.Seqable (transient [])) 39 | (reduce + (transient (into [] (range 10)))) 40 | 41 | (defn dbg 42 | ([] (println "0-arity")) 43 | ([x] (println "1-arity with" x)) 44 | ([x y] (println "2-arity with" x y)) 45 | ([x y & z] (println "var-arg with" x y z))) 46 | 47 | (reduce dbg nil) 48 | (reduce dbg []) 49 | (reduce dbg "val" []) 50 | 51 | ;; ============= Example: counting words (aka frequencies redo) 52 | 53 | (defn count-occurrences [coll] 54 | (->> coll 55 | (map #(vector % 1)) 56 | (reduce (fn [m [k cnt]] 57 | (assoc m k (+ cnt (get m k 0)))) {}))) 58 | 59 | (defn word-count [s] 60 | (count-occurrences (.split #"\s+" s))) 61 | 62 | (word-count "Counting all words, all the words and sentences.") 63 | 64 | ;; ============= See also: 65 | 66 | (reduce-kv (fn [m k v] (assoc m (keyword v) (name k))) {} {:a "1" :b "2"}) 67 | (reductions + 0 (range 10)) 68 | (reductions (fn [acc itm] (if (> itm 5) (reduced (+ itm acc)) (+ itm acc))) (range 10)) 69 | 70 | ;; ============= Performances 71 | 72 | ;; see chart 73 | 74 | (let [xs (range 1e8)] (reduce + xs)) 75 | (take 10 (reduce merge '() (range 1e8))) ;; ops 76 | (let [xs (range 1e8)] (last xs) (reduce + xs)) ;; ops 77 | -------------------------------------------------------------------------------- /src/clojure_pills/subvec.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-pills.subvec) 2 | 3 | "Clojure Pills - 007 subvec" 4 | 5 | ;; ============== contract 6 | 7 | (subvec [1 2 3 4] 1 3) 8 | (subvec [1 2 3 4] 1) 9 | 10 | (def subv (subvec (vector-of :int 1 2 3) 1)) 11 | (conj subv \a) 12 | (conj subv nil) ;; ! 13 | 14 | ;; ============== example 1 15 | 16 | (defn remove-at [v idx] 17 | (into (subvec v 0 idx) 18 | (subvec v (inc idx) (count v)))) 19 | 20 | (remove-at [0 1 2 3 4 5] 3) 21 | 22 | ;; ============== example 2 23 | 24 | (defn firstv [v] (nth v 0)) 25 | (defn restv [v] (subvec v 1)) 26 | 27 | (defn norm [v] 28 | (loop [v v 29 | res 0.] 30 | (if (= 0 (count v)) 31 | (Math/sqrt res) 32 | (recur (restv v) 33 | (+ res (Math/pow (firstv v) 2)))))) 34 | 35 | (norm [-3 4]) 36 | 37 | ;; ============== example 3 38 | 39 | ;; Straight from reducers.clj 40 | 41 | (def pool (delay (java.util.concurrent.ForkJoinPool.))) 42 | (defn fjtask [^Callable f] (java.util.concurrent.ForkJoinTask/adapt f)) 43 | 44 | (defn- fjinvoke [f] 45 | (if (java.util.concurrent.ForkJoinTask/inForkJoinPool) 46 | (f) 47 | (.invoke ^java.util.concurrent.ForkJoinPool 48 | @pool ^java.util.concurrent.ForkJoinTask 49 | (fjtask f)))) 50 | 51 | (defn- fjfork [task] (.fork ^java.util.concurrent.ForkJoinTask task)) 52 | (defn- fjjoin [task] (.join ^java.util.concurrent.ForkJoinTask task)) 53 | 54 | (defn pmapv [f v & [n]] 55 | (let [n (or n (+ 2 (.. Runtime getRuntime availableProcessors)))] 56 | (cond 57 | (empty? v) [] 58 | (<= (count v) n) (mapv f v) 59 | :else 60 | (let [split (quot (count v) 2) 61 | v1 (subvec v 0 split) 62 | v2 (subvec v split (count v)) 63 | fc (fn [child] #(pmapv f child n))] 64 | (fjinvoke 65 | #(let [f1 (fc v1) 66 | t2 (fjtask (fc v2))] 67 | (fjfork t2) 68 | (into (f1) (fjjoin t2)))))))) 69 | 70 | (defn slow-inc [n] (do (Thread/sleep 10) (inc n))) 71 | 72 | (let [v (vec (range 1000))] (dorun (time (mapv slow-inc v)))) 73 | (let [v (vec (range 1000))] (dorun (time (pmapv slow-inc v)))) 74 | 75 | ;; ============== perf 1 76 | 77 | (require '[criterium.core :refer [quick-bench]]) 78 | 79 | (defn norm [v] 80 | (loop [v v 81 | res 0. 82 | idx (dec (count v))] 83 | (if (< idx 0) 84 | (Math/sqrt res) 85 | (recur (subvec v 0 idx) 86 | (+ res (Math/pow (peek v) 2)) 87 | (dec idx))))) 88 | 89 | (let [v (vec (range 1000))] (quick-bench (norm v))) 90 | 91 | (defn norm-idx [v] 92 | (loop [idx (dec (count v)) 93 | res 0.] 94 | (if (< idx 0) 95 | (Math/sqrt res) 96 | (recur (dec idx) 97 | (+ res (Math/pow (nth v idx) 2)))))) 98 | 99 | (let [v (vec (range 1000))] (quick-bench (norm-idx v))) 100 | 101 | ;; ============== perf 2 102 | 103 | (defn bigv [n] (vec (range n))) 104 | 105 | (let [v1 (subvec (bigv 1e7) 0 5) 106 | v2 (subvec (bigv 1e7) 5 10)] 107 | (into v1 v2)) 108 | 109 | (let [v1 (into [] (subvec (bigv 1e7) 0 5)) 110 | v2 (into [] (subvec (bigv 1e7) 5 10))] 111 | (into v1 v2)) 112 | -------------------------------------------------------------------------------- /test/clojure_pills/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns clojure-pills.core-test 2 | (:require [clojure.test :refer :all] 3 | [clojure-pills.core :refer :all])) 4 | --------------------------------------------------------------------------------