├── answers ├── chapter01 │ └── 1_1.clj ├── chapter05 │ ├── 5_1.clj │ └── 5_2.clj ├── chapter06 │ └── om-pm │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── dev-resources │ │ ├── public │ │ │ └── index.html │ │ └── tools │ │ │ ├── http │ │ │ └── ring │ │ │ │ └── server.clj │ │ │ └── repl │ │ │ └── brepl │ │ │ └── connect.cljs │ │ ├── doc │ │ └── intro.md │ │ ├── profiles.clj │ │ ├── project.clj │ │ └── src │ │ └── cljs │ │ └── om_pm │ │ ├── core.cljs │ │ └── util.cljs └── chapter08 │ └── 8_1.clj └── code ├── appendix-the-algebra-of-library-design └── library-design │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── doc │ └── intro.md │ ├── project.clj │ ├── src │ └── library_design │ │ ├── core.clj │ │ └── option.clj │ └── test │ └── library_design │ └── core_test.clj ├── chapter01 ├── calculator │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── doc │ │ └── intro.md │ ├── project.clj │ ├── src │ │ └── calculator │ │ │ └── core.clj │ └── test │ │ └── calculator │ │ └── core_test.clj └── sin-wave │ ├── .gitignore │ ├── LICENSE │ ├── Procfile │ ├── README.md │ ├── env │ ├── dev │ │ └── cljs │ │ │ └── sin_wave │ │ │ └── dev.cljs │ └── prod │ │ └── cljs │ │ └── sin_wave │ │ └── prod.cljs │ ├── project.clj │ ├── resources │ ├── index.html │ └── public │ │ └── css │ │ └── style.css │ ├── src │ ├── clj │ │ └── sin_wave │ │ │ ├── dev.clj │ │ │ └── server.clj │ └── cljs │ │ └── sin_wave │ │ ├── core.cljs │ │ └── repl.cljs │ └── system.properties ├── chapter02 └── rx-playground │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── doc │ └── intro.md │ ├── project.clj │ ├── src │ └── rx_playground │ │ └── core.clj │ └── test │ └── rx_playground │ └── core_test.clj ├── chapter03 └── stock-market-monitor │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── doc │ └── intro.md │ ├── project.clj │ ├── src │ └── stock_market_monitor │ │ ├── 01price_monitor.clj │ │ ├── 02price_monitor_rolling_avg.clj │ │ ├── 03frp_price_monitor.clj │ │ ├── 04buffer.clj │ │ └── 05frp_price_monitor_rolling_avg.clj │ └── test │ └── stock_market_monitor │ └── core_test.clj ├── chapter04 ├── core-async-playground │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── doc │ │ └── intro.md │ ├── project.clj │ ├── src │ │ └── core_async_playground │ │ │ ├── backpressure.clj │ │ │ ├── core.clj │ │ │ ├── error_handling.clj │ │ │ └── stock_market.clj │ └── test │ │ └── core_async_playground │ │ └── core_test.clj ├── core-async-transducers │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── doc │ │ └── intro.md │ ├── project.clj │ ├── src │ │ └── core_async_transducers │ │ │ └── core.clj │ └── test │ │ └── core_async_transducers │ │ └── core_test.clj └── repl.clj ├── chapter05 ├── respondent-app │ ├── .gitignore │ ├── .nrepl-port │ ├── LICENSE │ ├── README.md │ ├── dev-resources │ │ ├── public │ │ │ └── index.html │ │ └── tools │ │ │ ├── http │ │ │ └── ring │ │ │ │ └── server.clj │ │ │ └── repl │ │ │ └── brepl │ │ │ └── connect.cljs │ ├── doc │ │ └── intro.md │ ├── profiles.clj │ ├── project.clj │ ├── src │ │ └── cljs │ │ │ └── respondent_app │ │ │ └── core.cljs │ └── test │ │ └── cljs │ │ └── respondent_app │ │ └── core_test.cljs └── respondent │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── doc │ └── intro.md │ ├── dotimes.perl │ ├── hs_err_pid31450.log │ ├── project.clj │ ├── src │ └── cljx │ │ └── respondent │ │ ├── core.cljx │ │ └── repl.cljx │ └── test │ └── cljx │ └── respondent │ └── core_test.cljx ├── chapter06 └── reagi-game │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── dev-resources │ ├── public │ │ └── index.html │ └── tools │ │ ├── http │ │ └── ring │ │ │ └── server.clj │ │ └── repl │ │ └── brepl │ │ └── connect.cljs │ ├── doc │ └── intro.md │ ├── profiles.clj │ ├── project.clj │ ├── src │ └── cljs │ │ └── reagi_game │ │ ├── core.cljs │ │ ├── entities.cljs │ │ └── util.cljs │ └── test │ └── cljs │ └── reagi_game │ └── core_test.cljs ├── chapter07 ├── contacts │ ├── .gitignore │ ├── .nrepl-port │ ├── LICENSE │ ├── README.md │ ├── dev-resources │ │ ├── public │ │ │ └── index.html │ │ └── tools │ │ │ ├── http │ │ │ └── ring │ │ │ │ └── server.clj │ │ │ └── repl │ │ │ └── brepl │ │ │ └── connect.cljs │ ├── doc │ │ └── intro.md │ ├── profiles.clj │ ├── project.clj │ └── src │ │ └── cljs │ │ └── contacts │ │ └── core.cljs ├── om-pm │ ├── .gitignore │ ├── .repl │ │ └── 76 │ │ │ ├── client.js │ │ │ ├── cljs │ │ │ ├── core.cljs │ │ │ └── core.js │ │ │ ├── clojure │ │ │ └── browser │ │ │ │ ├── event.cljs │ │ │ │ ├── event.js │ │ │ │ ├── net.cljs │ │ │ │ ├── net.js │ │ │ │ ├── repl.cljs │ │ │ │ └── repl.js │ │ │ └── om │ │ │ ├── core.cljs │ │ │ ├── core.js │ │ │ ├── dom.cljs │ │ │ └── dom.js │ ├── LICENSE │ ├── README.md │ ├── dev-resources │ │ ├── public │ │ │ └── index.html │ │ └── tools │ │ │ ├── http │ │ │ └── ring │ │ │ │ └── server.clj │ │ │ └── repl │ │ │ └── brepl │ │ │ └── connect.cljs │ ├── doc │ │ └── intro.md │ ├── profiles.clj │ ├── project.clj │ └── src │ │ └── cljs │ │ └── om_pm │ │ ├── core.cljs │ │ └── util.cljs └── scratch.clj ├── chapter08 ├── clj-futures-playground │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── doc │ │ └── intro.md │ ├── project.clj │ ├── src │ │ └── clj_futures_playground │ │ │ └── core.clj │ └── test │ │ └── clj_futures_playground │ │ └── core_test.clj ├── examples.clj └── imminent-playground │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── doc │ └── intro.md │ ├── project.clj │ ├── src │ └── imminent_playground │ │ ├── core.clj │ │ └── repl.clj │ └── test │ └── imminent_playground │ └── core_test.clj └── chapter09 ├── aws-api-stub ├── .gitignore ├── README.md ├── project.clj ├── src │ └── aws_api_stub │ │ ├── aws.clj │ │ └── core │ │ └── handler.clj └── test │ └── aws_api_stub │ └── core │ └── handler_test.clj └── aws-dash ├── .gitignore ├── LICENSE ├── README.md ├── dev-resources ├── public │ └── index.html └── tools │ ├── http │ └── ring │ │ └── server.clj │ └── repl │ └── brepl │ └── connect.cljs ├── doc └── intro.md ├── profiles.clj ├── project.clj └── src └── cljs └── aws_dash ├── core.cljs └── observables.cljs /answers/chapter01/1_1.clj: -------------------------------------------------------------------------------- 1 | ;; Exercise 1.1 2 | 3 | (def repeat js/Rx.Observable.repeat) 4 | (def rainbow-colours (-> (.scan time 5 | (cycle ["red" 6 | "orange" 7 | "yellow" 8 | "green" 9 | "blue" 10 | "indigo" 11 | "violet"]) 12 | (fn [acc _] (next acc))) 13 | (.map #(first %)) 14 | (.flatMap #(repeat % 20)))) 15 | 16 | 17 | 18 | 19 | 20 | 21 | (-> (.zip sine-wave rainbow-colours #(vector % %2)) 22 | (.take 600) 23 | (.subscribe (fn [[{:keys [x y]} colour]] 24 | (fill-rect x y colour)))) 25 | -------------------------------------------------------------------------------- /answers/chapter05/5_1.clj: -------------------------------------------------------------------------------- 1 | ;; Exercise 5.1 2 | 3 | (defn take [es1 n] 4 | (let [count (atom 0) 5 | out-es (event-stream (chan n)) 6 | dispose-token nil 7 | token (subscribe es1 8 | (fn [item] 9 | (if (< @count n) 10 | (do (deliver out-es item) 11 | (swap! count inc)) 12 | (deliver out-es ::complete))))] 13 | (add-watch count :token (fn [_ _ _ new-state] 14 | (when (>= new-state n) 15 | (dispose token)))) 16 | 17 | out-es)) 18 | 19 | (def es1 (from-interval 500)) 20 | (def take-es (take es1 5)) 21 | 22 | (subscribe take-es #(prn "Take values: " %)) 23 | 24 | ;; "Take values: " 0 25 | ;; "Take values: " 1 26 | ;; "Take values: " 2 27 | ;; "Take values: " 3 28 | ;; "Take values: " 4 29 | -------------------------------------------------------------------------------- /answers/chapter05/5_2.clj: -------------------------------------------------------------------------------- 1 | ;; Exercise 5.2 2 | 3 | (defn zip [es1 es2] 4 | (let [out-vec (ref [nil nil]) 5 | out-es (event-stream) 6 | emit-if-done! (fn [] 7 | (let [vec @out-vec] 8 | (when (every? (comp not nil?) vec) 9 | (deliver out-es vec) 10 | (ref-set out-vec [nil nil])))) 11 | 12 | (subscribe es1 13 | (fn [item] 14 | (dosync 15 | (alter out-vec (fn [[_ b]] 16 | [item b])) 17 | (emit-if-done!)))) 18 | (subscribe es2 19 | (fn [item] 20 | (dosync 21 | (alter out-vec (fn [[a _]] 22 | [a item])) 23 | (emit-if-done!))))] 24 | 25 | out-es)) 26 | 27 | (def es1 (from-interval 500)) 28 | (def es2 (map (from-interval 500) #(* % 2))) 29 | (def zipped (zip es1 es2)) 30 | 31 | (def token (subscribe zipped #(prn "Zipped values: " %))) 32 | 33 | ;; "Zipped values: " [0 0] 34 | ;; "Zipped values: " [1 2] 35 | ;; "Zipped values: " [2 4] 36 | ;; "Zipped values: " [3 6] 37 | ;; "Zipped values: " [4 8] 38 | 39 | (dispose token) 40 | -------------------------------------------------------------------------------- /answers/chapter06/om-pm/.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | /lib/ 4 | /classes/ 5 | /out/ 6 | /target/ 7 | .repl 8 | .lein-deps-sum 9 | .lein-repl-history 10 | .lein-plugins/ 11 | /dev-resources/public/js/* 12 | -------------------------------------------------------------------------------- /answers/chapter06/om-pm/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 tocontrol, 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 Washington 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 | -------------------------------------------------------------------------------- /answers/chapter06/om-pm/README.md: -------------------------------------------------------------------------------- 1 | # om-pm 2 | 3 | An OM project designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2014 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /answers/chapter06/om-pm/dev-resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /answers/chapter06/om-pm/dev-resources/tools/http/ring/server.clj: -------------------------------------------------------------------------------- 1 | ;;; This namespace is used for development and testing purpose only. 2 | (ns ring.server 3 | (:require [cemerick.austin.repls :refer (browser-connected-repl-js)] 4 | [net.cgrand.enlive-html :as enlive] 5 | [compojure.route :refer (resources)] 6 | [compojure.core :refer (GET defroutes)] 7 | [ring.adapter.jetty :as jetty] 8 | [clojure.java.io :as io])) 9 | 10 | (enlive/deftemplate page 11 | (io/resource "public/index.html") 12 | [] 13 | [:body] (enlive/append 14 | (enlive/html [:script (browser-connected-repl-js)]))) 15 | 16 | (defroutes site 17 | (resources "/") 18 | (GET "/*" req (page))) 19 | 20 | (defn run 21 | "Run the ring server. It defines the server symbol with defonce." 22 | [] 23 | (defonce server 24 | (jetty/run-jetty #'site {:port 3000 :join? false})) 25 | server) 26 | -------------------------------------------------------------------------------- /answers/chapter06/om-pm/dev-resources/tools/repl/brepl/connect.cljs: -------------------------------------------------------------------------------- 1 | (ns brepl.connect 2 | (:require [clojure.browser.repl])) 3 | -------------------------------------------------------------------------------- /answers/chapter06/om-pm/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to om-pm 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) 4 | -------------------------------------------------------------------------------- /answers/chapter06/om-pm/profiles.clj: -------------------------------------------------------------------------------- 1 | {:shared {:clean-targets ["out" :target-path]} 2 | 3 | :tdd [:shared 4 | {:cljsbuild 5 | {:builds {:om-pm 6 | {:compiler 7 | {:optimizations :whitespace 8 | :pretty-print true}}}}}] 9 | 10 | :dev [:shared 11 | {:resources-paths ["dev-resources"] 12 | :source-paths ["dev-resources/tools/http" "dev-resources/tools/repl"] 13 | :dependencies [[ring "1.2.1"] 14 | [compojure "1.1.6"] 15 | [enlive "1.1.5"]] 16 | :plugins [[com.cemerick/austin "0.1.3"]] 17 | :cljsbuild 18 | {:builds {:om-pm 19 | {:source-paths ["dev-resources/tools/repl"] 20 | :compiler 21 | {:optimizations :whitespace 22 | :pretty-print true}}}} 23 | 24 | :injections [(require '[ring.server :as http :refer [run]] 25 | 'cemerick.austin.repls) 26 | (defn browser-repl-env [] 27 | (reset! cemerick.austin.repls/browser-repl-env 28 | (cemerick.austin/repl-env))) 29 | (defn browser-repl [] 30 | (cemerick.austin.repls/cljs-repl 31 | (browser-repl-env)))]}]} 32 | -------------------------------------------------------------------------------- /answers/chapter06/om-pm/project.clj: -------------------------------------------------------------------------------- 1 | (defproject om-pm "0.0.1-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License - v 1.0" 5 | :url "http://www.eclipse.org/legal/epl-v10.html" 6 | :distribution :repo} 7 | 8 | :min-lein-version "2.3.4" 9 | 10 | :source-paths ["src/clj" "src/cljs"] 11 | 12 | :dependencies [[org.clojure/clojure "1.6.0"] 13 | [org.clojure/clojurescript "0.0-2511"] 14 | [org.om/om "0.8.1"] 15 | [org.clojure/core.async "0.1.346.0-17112a-alpha"] 16 | [com.facebook/react "0.12.2"]] 17 | 18 | :plugins [[lein-cljsbuild "1.0.3"]] 19 | 20 | :hooks [leiningen.cljsbuild] 21 | 22 | :cljsbuild 23 | {:builds {:om-pm 24 | {:source-paths ["src/cljs"] 25 | :compiler 26 | {:output-to "dev-resources/public/js/om_pm.js" 27 | :optimizations :advanced 28 | :pretty-print false}}}}) 29 | -------------------------------------------------------------------------------- /answers/chapter06/om-pm/src/cljs/om_pm/core.cljs: -------------------------------------------------------------------------------- 1 | (ns om-pm.core 2 | (:require [om.core :as om :include-macros true] 3 | [om.dom :as dom :include-macros true] 4 | [cljs.core.async :refer [put! chan e .-nativeEvent .-dataTransfer) 5 | key value)) 6 | 7 | (defn get-transfer-data! [e key] 8 | (-> (-> e .-nativeEvent .-dataTransfer) 9 | (.getData key))) 10 | 11 | (defn column-idx [title columns] 12 | (first (keep-indexed (fn [idx column] 13 | (when (= title (:title column)) 14 | idx)) 15 | columns))) 16 | 17 | (defn move-card! [columns {:keys [card-id source-column destination-column]}] 18 | (let [from (column-idx source-column columns) 19 | to (column-idx destination-column columns)] 20 | (-> columns 21 | (update-in [from :cards] (fn [cards] 22 | (remove #{card-id} cards))) 23 | (update-in [to :cards] (fn [cards] 24 | (conj cards card-id)))))) 25 | 26 | (defn card-seq [columns] 27 | (mapcat (fn [{:keys [title cards]}] 28 | (map vector cards (repeat title))) columns)) 29 | 30 | (def first-card (comp first card-seq)) 31 | 32 | (defn next-sibling [pred coll] 33 | (first (drop 1 (drop-while pred coll)))) 34 | 35 | (defn previous-sibling [pred coll] 36 | (last (take-while pred coll))) 37 | 38 | (defn next-down [id cards] 39 | (if-let [card (next-sibling (fn [[id' column']] 40 | (not= id' id)) cards)] 41 | card 42 | [])) 43 | 44 | (defn next-up [id cards] 45 | (if-let [card (previous-sibling (fn [[id' column']] 46 | (not= id' id)) cards)] 47 | card 48 | [])) 49 | 50 | (defn next-column [title columns] 51 | (next-sibling #(not= % title) (map :title columns))) 52 | 53 | (defn previous-column [title columns] 54 | (previous-sibling #(not= % title) (map :title columns))) 55 | -------------------------------------------------------------------------------- /answers/chapter08/8_1.clj: -------------------------------------------------------------------------------- 1 | ;; Exercise 8.1 2 | 3 | (defn resouces-stream [_] 4 | (let [resources (obs/stack-resources)] 5 | (-> (.merge (obs/rds-instance-status resources) 6 | (obs/ec2-instance-status resources)) 7 | (.reduce conj [])))) 8 | 9 | (.subscribe (.flatMap (.interval js/Rx.Observable 100) 10 | resources-stream) 11 | #(swap! app-state assoc :instances %)) 12 | -------------------------------------------------------------------------------- /code/appendix-the-algebra-of-library-design/library-design/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /code/appendix-the-algebra-of-library-design/library-design/README.md: -------------------------------------------------------------------------------- 1 | # library-design 2 | 3 | A Clojure library designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2014 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /code/appendix-the-algebra-of-library-design/library-design/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to library-design 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/appendix-the-algebra-of-library-design/library-design/project.clj: -------------------------------------------------------------------------------- 1 | (defproject library-design "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 | :dependencies [[org.clojure/clojure "1.6.0"] 7 | [com.leonardoborges/imminent "0.1.0"] 8 | [com.netflix.rxjava/rxjava-clojure "0.20.7"] 9 | [org.clojure/core.async "0.1.346.0-17112a-alpha"] 10 | [uncomplicate/fluokitten "0.3.0"]]) 11 | -------------------------------------------------------------------------------- /code/appendix-the-algebra-of-library-design/library-design/src/library_design/core.clj: -------------------------------------------------------------------------------- 1 | (ns library-design.core 2 | (:require [imminent.core :as i] 3 | [rx.lang.clojure.core :as rx] 4 | [clojure.core.async :as async])) 5 | 6 | 7 | (def repl-out *out*) 8 | (defn prn-to-repl [& args] 9 | (binding [*out* repl-out] 10 | (apply prn args))) 11 | 12 | (-> (i/const-future 31) 13 | (i/map #(* % 2)) 14 | (i/on-success #(prn-to-repl (str "Value: " %)))) 15 | 16 | 17 | (as-> (rx/return 31) obs 18 | (rx/map #(* % 2) obs) 19 | (rx/subscribe obs #(prn-to-repl (str "Value: " %)))) 20 | 21 | (def c (async/chan)) 22 | (def mapped-c (async/map< #(* % 2) c)) 23 | 24 | (async/go (async/>! c 31)) 25 | (async/go (prn-to-repl (str "Value: " (async/> pirates 18 | (filter #(= name (:name %))) 19 | first)) 20 | 21 | (defn age [{:keys [born died]}] 22 | (- died born)) 23 | 24 | (comment 25 | 26 | (-> (pirate-by-name "Jack Sparrow") 27 | age) ;; 40 28 | 29 | (-> (pirate-by-name "Davy Jones") 30 | age) ;; NullPointerException clojure.lang.Numbers.ops (Numbers.java:961) 31 | 32 | 33 | 34 | 35 | ) 36 | 37 | (defrecord Some [v]) 38 | 39 | (defrecord None []) 40 | 41 | 42 | (defn option [v] 43 | (if v 44 | (Some. v) 45 | (None.))) 46 | 47 | 48 | (comment 49 | 50 | 51 | ;; delete me 52 | (defprotocol Functor 53 | 54 | (fmap [fv g] )) 55 | ;; laws 56 | 57 | 58 | ) 59 | 60 | 61 | 62 | 63 | (extend-protocol fkp/Functor 64 | Some 65 | (fmap [f g] 66 | (Some. (g (:v f)))) 67 | None 68 | (fmap [_ _] 69 | (None.))) 70 | 71 | 72 | (->> (option (pirate-by-name "Jack Sparrow")) 73 | (fkc/fmap age)) ;; #library_design.option.Some{:v 40} 74 | 75 | (->> (option (pirate-by-name "Davy Jones")) 76 | (fkc/fmap age)) ;; #library_design.option.None{} 77 | 78 | 79 | (->> (option (pirate-by-name "Jack Sparrow")) 80 | (fkc/fmap age) 81 | (fkc/fmap inc) 82 | (fkc/fmap #(* 2 %))) ;; #library_design.option.Some{:v 82} 83 | 84 | (->> (option (pirate-by-name "Davy Jones")) 85 | (fkc/fmap age) 86 | (fkc/fmap inc) 87 | (fkc/fmap #(* 2 %))) ;; #library_design.option.None{} 88 | 89 | 90 | 91 | (some-> (pirate-by-name "Davy Jones") 92 | age 93 | inc 94 | (* 2)) ;; nil 95 | 96 | 97 | (->> (i/future (pirate-by-name "Jack Sparrow")) 98 | (fkc/fmap age) 99 | (fkc/fmap inc) 100 | (fkc/fmap #(* 2 %))) ;; #> 101 | 102 | 103 | ;; Functor laws 104 | 105 | ;; Identity 106 | (= (fkc/fmap identity (option 1)) 107 | (identity (option 1))) ;; true 108 | 109 | 110 | ;; Composition 111 | (= (fkc/fmap (comp identity inc) (option 1)) 112 | (fkc/fmap identity (fkc/fmap inc (option 1)))) ;; true 113 | 114 | 115 | (def repl-out *out*) 116 | (defn prn-to-repl [& args] 117 | (binding [*out* repl-out] 118 | (apply prn args))) 119 | 120 | 121 | ;; 122 | ;; Applicative 123 | ;; 124 | 125 | (defn avg [& xs] 126 | (float (/ (apply + xs) (count xs)))) 127 | 128 | (comment 129 | 130 | 131 | 132 | (let [a (some-> (pirate-by-name "Jack Sparrow") age) 133 | b (some-> (pirate-by-name "Blackbeard") age) 134 | c (some-> (pirate-by-name "Hector Barbossa") age)] 135 | (avg a b c)) ;; 56.666668 136 | 137 | (let [a (some-> (pirate-by-name "Jack Sparrow") age) 138 | b (some-> (pirate-by-name "Davy Jones") age) 139 | c (some-> (pirate-by-name "Hector Barbossa") age)] 140 | (avg a b c)) ;; NullPointerException clojure.lang.Numbers.ops (Numbers.java:961) 141 | 142 | (let [a (some-> (pirate-by-name "Jack Sparrow") age) 143 | b (some-> (pirate-by-name "Davy Jones") age) 144 | c (some-> (pirate-by-name "Hector Barbossa") age)] 145 | (when (and a b c) 146 | (avg a b c))) ;; nil 147 | 148 | ) 149 | 150 | 151 | (extend-protocol fkp/Applicative 152 | Some 153 | (pure [_ v] 154 | (Some. v)) 155 | 156 | (fapply [ag av] 157 | (if-let [v (:v av)] 158 | (Some. ((:v ag) v)) 159 | (None.))) 160 | 161 | None 162 | (pure [_ v] 163 | (Some. v)) 164 | 165 | (fapply [ag av] 166 | (None.))) 167 | 168 | 169 | (fkc/fapply (option inc) (option 2)) 170 | ;; #library_design.option.Some{:v 3} 171 | 172 | (fkc/fapply (option nil) (option 2)) 173 | ;; #library_design.option.None{} 174 | 175 | (def age-option (comp (partial fkc/fmap age) option pirate-by-name)) 176 | 177 | (let [a (age-option "Jack Sparrow") 178 | b (age-option "Blackbeard") 179 | c (age-option "Hector Barbossa")] 180 | (fkc/<*> (option (fkj/curry avg 3)) 181 | a b c)) 182 | ;; #library_design.option.Some{:v 56.666668} 183 | 184 | (defn alift 185 | "Lifts a n-ary function `f` into a applicative context" 186 | [f] 187 | (fn [& as] 188 | {:pre [(seq as)]} 189 | (let [curried (fkj/curry f (count as))] 190 | (apply fkc/<*> 191 | (fkc/fmap curried (first as)) 192 | (rest as))))) 193 | 194 | 195 | (let [a (age-option "Jack Sparrow") 196 | b (age-option "Blackbeard") 197 | c (age-option "Hector Barbossa")] 198 | ((alift avg) a b c)) 199 | ;; #library_design.option.Some{:v 56.666668} 200 | 201 | ((alift avg) (age-option "Jack Sparrow") 202 | (age-option "Blackbeard") 203 | (age-option "Hector Barbossa")) 204 | ;; #library_design.option.Some{:v 56.666668} 205 | 206 | ((alift avg) (age-option "Jack Sparrow") 207 | (age-option "Davy Jones") 208 | (age-option "Hector Barbossa")) 209 | ;; #library_design.option.None{} 210 | 211 | 212 | 213 | ((alift avg) (i/future (some-> (pirate-by-name "Jack Sparrow") age)) 214 | (i/future (some-> (pirate-by-name "Blackbeard") age)) 215 | (i/future (some-> (pirate-by-name "Hector Barbossa") age))) 216 | ;; #> 217 | 218 | (->> (i/future (pirate-by-name "Jack Sparrow")) 219 | (fkc/fmap age) 220 | (fkc/fmap inc) 221 | (fkc/fmap #(* 2 %))) 222 | 223 | 224 | ;; 225 | ;; Monad 226 | ;; 227 | 228 | (defn median [& ns] 229 | (let [ns (sort ns) 230 | cnt (count ns) 231 | mid (bit-shift-right cnt 1)] 232 | (if (odd? cnt) 233 | (nth ns mid) 234 | (/ (+ (nth ns mid) (nth ns (dec mid))) 2)))) 235 | 236 | (defn std-dev [& samples] 237 | (let [n (count samples) 238 | mean (/ (reduce + samples) n) 239 | intermediate (map #(Math/pow (- %1 mean) 2) samples)] 240 | (Math/sqrt 241 | (/ (reduce + intermediate) n)))) 242 | 243 | 244 | (comment 245 | 246 | (let [a (some-> (pirate-by-name "Jack Sparrow") age) 247 | b (some-> (pirate-by-name "Blackbeard") age) 248 | c (some-> (pirate-by-name "Hector Barbossa") age) 249 | avg (avg a b c) 250 | median (median a b c) 251 | std-dev (std-dev a b c)] 252 | {:avg avg 253 | :median median 254 | :std-dev std-dev}) 255 | ;; {:avg 56.666668, 256 | ;; :median 60, 257 | ;; :std-dev 12.472191289246473} 258 | 259 | 260 | (let [a (some-> (pirate-by-name "Jack Sparrow") age) 261 | b (some-> (pirate-by-name "Davy Jones") age) 262 | c (some-> (pirate-by-name "Hector Barbossa") age) 263 | avg (avg a b c) 264 | median (median a b c) 265 | std-dev (std-dev a b c)] 266 | {:avg avg 267 | :median median 268 | :std-dev std-dev}) 269 | ;; NullPointerException clojure.lang.Numbers.ops (Numbers.java:961) 270 | 271 | (let [a (some-> (pirate-by-name "Jack Sparrow") age) 272 | b (some-> (pirate-by-name "Davy Jones") age) 273 | c (some-> (pirate-by-name "Hector Barbossa") age) 274 | avg (when (and a b c) (avg a b c)) 275 | median (when (and a b c) (median a b c)) 276 | std-dev (when (and a b c) (std-dev a b c))] 277 | (when (and a b c) 278 | {:avg avg 279 | :median median 280 | :std-dev std-dev})) 281 | ;; nil 282 | 283 | 284 | ) 285 | 286 | 287 | (extend-protocol fkp/Monad 288 | Some 289 | (bind [mv g] 290 | (g (:v mv))) 291 | 292 | None 293 | (bind [_ _] 294 | (None.))) 295 | 296 | 297 | 298 | ;; (fkc/bind (None.) identity) 299 | 300 | (def opt-ctx (None.)) 301 | 302 | (fkc/bind (age-option "Jack Sparrow") 303 | (fn [a] 304 | (fkc/bind (age-option "Blackbeard") 305 | (fn [b] 306 | (fkc/bind (age-option "Hector Barbossa") 307 | (fn [c] 308 | (fkc/pure opt-ctx 309 | (+ a b c)))))))) 310 | ;; #library_design.option.Some{:v 170.0} 311 | 312 | (fkc/mdo [a (age-option "Jack Sparrow") 313 | b (age-option "Blackbeard") 314 | c (age-option "Hector Barbossa")] 315 | (fkc/pure opt-ctx (+ a b c))) 316 | ;; #library_design.option.Some{:v 170.0} 317 | 318 | (require '[clojure.walk :as w]) 319 | 320 | (w/macroexpand-all '(fkc/mdo [a (age-option "Jack Sparrow") 321 | b (age-option "Blackbeard") 322 | c (age-option "Hector Barbossa")] 323 | (fkc/pure opt-ctx (+ a b c)))) 324 | 325 | ;; (uncomplicate.fluokitten.core/bind 326 | ;; (age-option "Jack Sparrow") 327 | ;; (fn* 328 | ;; ([a] 329 | ;; (uncomplicate.fluokitten.core/bind 330 | ;; (age-option "Blackbeard") 331 | ;; (fn* 332 | ;; ([b] 333 | ;; (uncomplicate.fluokitten.core/bind 334 | ;; (age-option "Hector Barbossa") 335 | ;; (fn* ([c] (fkc/pure opt-ctx (+ a b c))))))))))) 336 | 337 | 338 | (def avg-opt (comp option avg)) 339 | (def median-opt (comp option median)) 340 | (def std-dev-opt (comp option std-dev)) 341 | 342 | (fkc/mdo [a (age-option "Jack Sparrow") 343 | b (age-option "Blackbeard") 344 | c (age-option "Hector Barbossa") 345 | avg (avg-opt a b c) 346 | median (median-opt a b c) 347 | std-dev (std-dev-opt a b c)] 348 | (fkc/pure opt-ctx {:avg avg 349 | :median median 350 | :std-dev std-dev})) 351 | ;; #library_design.option.Some{:v {:avg 56.666668, 352 | ;; :median 60, 353 | ;; :std-dev 12.472191289246473}} 354 | 355 | (fkc/mdo [a (age-option "Jack Sparrow") 356 | b (age-option "Davy Jones") 357 | c (age-option "Hector Barbossa") 358 | avg (avg-opt a b c) 359 | median (median-opt a b c) 360 | std-dev (std-dev-opt a b c)] 361 | (fkc/pure opt-ctx {:avg avg 362 | :median median 363 | :std-dev std-dev})) 364 | ;; #library_design.option.None{} 365 | 366 | (def avg-fut (comp i/future-call avg)) 367 | (def median-fut (comp i/future-call median)) 368 | (def std-dev-fut (comp i/future-call std-dev)) 369 | 370 | (fkc/mdo [a (i/future (some-> (pirate-by-name "Jack Sparrow") age)) 371 | b (i/future (some-> (pirate-by-name "Blackbeard") age)) 372 | c (i/future (some-> (pirate-by-name "Hector Barbossa") age)) 373 | avg (avg-fut a b c) 374 | median (median-fut a b c) 375 | std-dev (std-dev-fut a b c)] 376 | (i/const-future {:avg avg 377 | :median median 378 | :std-dev std-dev})) 379 | ;; #> 382 | -------------------------------------------------------------------------------- /code/appendix-the-algebra-of-library-design/library-design/test/library_design/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns library-design.core-test 2 | (:require [clojure.test :refer :all] 3 | [library-design.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /code/chapter01/calculator/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /code/chapter01/calculator/README.md: -------------------------------------------------------------------------------- 1 | # calculator 2 | 3 | A Clojure library designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2014 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /code/chapter01/calculator/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to calculator 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/chapter01/calculator/project.clj: -------------------------------------------------------------------------------- 1 | (defproject calculator "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 | :dependencies [[org.clojure/clojure "1.5.1"] 7 | [seesaw "1.4.4"]]) 8 | -------------------------------------------------------------------------------- /code/chapter01/calculator/src/calculator/core.clj: -------------------------------------------------------------------------------- 1 | (ns calculator.core 2 | (:require [seesaw.core :refer :all])) 3 | 4 | (native!) 5 | 6 | (def main-frame (frame :title "Calculator" :on-close :exit)) 7 | 8 | (def field-x (text "1")) 9 | (def field-y (text "2")) 10 | 11 | (def result-label (label "Type numbers in the boxes to add them up!")) 12 | 13 | (defn update-sum [e] 14 | (try 15 | (text! result-label 16 | (str "Sum is " (+ (Integer/parseInt (text field-x)) 17 | (Integer/parseInt (text field-y))))) 18 | (catch Exception e 19 | (println "Error parsing input.")))) 20 | 21 | (listen field-x :key-released update-sum) 22 | (listen field-y :key-released update-sum) 23 | 24 | (config! main-frame :content 25 | (border-panel 26 | :north (horizontal-panel :items [field-x field-y]) 27 | :center result-label 28 | :border 5)) 29 | 30 | (defn -main [& args] 31 | (-> main-frame pack! show!)) 32 | -------------------------------------------------------------------------------- /code/chapter01/calculator/test/calculator/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns calculator.core-test 2 | (:require [clojure.test :refer :all] 3 | [calculator.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /code/chapter01/sin-wave/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /resources/public/js 11 | /out 12 | /.repl 13 | -------------------------------------------------------------------------------- /code/chapter01/sin-wave/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 tocontrol, 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 | -------------------------------------------------------------------------------- /code/chapter01/sin-wave/Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPTS -cp target/sin-wave.jar clojure.main -m sin-wave.server 2 | -------------------------------------------------------------------------------- /code/chapter01/sin-wave/README.md: -------------------------------------------------------------------------------- 1 | # sin-wave 2 | 3 | 4 | ## Development 5 | 6 | Start a REPL (in a terminal: `lein repl`, or from Emacs: open a 7 | clj/cljs file in the project, then do `M-x cider-jack-in`. Make sure 8 | CIDER is up to date). 9 | 10 | In the REPL do 11 | 12 | ```clojure 13 | (run) 14 | (browser-repl) 15 | ``` 16 | 17 | The call to `(run)` does two things, it starts the webserver at port 18 | 10555, and also the Figwheel server which takes care of live reloading 19 | ClojureScript code and CSS. Give them some time to start. 20 | 21 | Running `(browser-repl)` starts the Weasel REPL server, and drops you 22 | into a ClojureScript REPL. Evaluating expressions here will only work 23 | once you've loaded the page, so the browser can connect to Weasel. 24 | 25 | When you see the line `Successfully compiled "resources/public/app.js" 26 | in 21.36 seconds.`, you're ready to go. Browse to 27 | `http://localhost:10555` and enjoy. 28 | 29 | **Attention: It is not longer needed to run `lein figwheel` 30 | separately. This is now taken care of behind the scenes** 31 | 32 | ## Trying it out 33 | 34 | If all is well you now have a browser window saying 'Hello Chestnut', 35 | and a REPL prompt that looks like `cljs.user=>`. 36 | 37 | Open `resources/public/css/style.css` and change some styling of the 38 | H1 element. Notice how it's updated instantly in the browser. 39 | 40 | Open `src/cljs/sin-wave/core.cljs`, and change `dom/h1` to 41 | `dom/h2`. As soon as you save the file, your browser is updated. 42 | 43 | In the REPL, type 44 | 45 | ``` 46 | (ns sin-wave.core) 47 | (swap! app-state assoc :text "Interactivity FTW") 48 | ``` 49 | 50 | Notice again how the browser updates. 51 | 52 | ## Deploying to Heroku 53 | 54 | This assumes you have a 55 | [Heroku account](https://signup.heroku.com/dc), have installed the 56 | [Heroku toolbelt](https://toolbelt.heroku.com/), and have done a 57 | `heroku login` before. 58 | 59 | ``` sh 60 | git init 61 | git add -A 62 | git commit 63 | heroku create 64 | git push heroku master:master 65 | heroku open 66 | ``` 67 | 68 | ## Running with Foreman 69 | 70 | Heroku uses [Foreman](http://ddollar.github.io/foreman/) to run your 71 | app, which uses the `Procfile` in your repository to figure out which 72 | server command to run. Heroku also compiles and runs your code with a 73 | Leiningen "production" profile, instead of "dev". To locally simulate 74 | what Heroku does you can do: 75 | 76 | ``` sh 77 | lein with-profile -dev,+production uberjar && foreman start 78 | ``` 79 | 80 | Now your app is running at 81 | [http://localhost:5000](http://localhost:5000) in production mode. 82 | 83 | ## License 84 | 85 | Copyright © 2014 FIXME 86 | 87 | Distributed under the Eclipse Public License either version 1.0 or (at 88 | your option) any later version. 89 | -------------------------------------------------------------------------------- /code/chapter01/sin-wave/env/dev/cljs/sin_wave/dev.cljs: -------------------------------------------------------------------------------- 1 | (ns sin-wave.dev 2 | (:require [sin-wave.core :as core] 3 | [figwheel.client :as figwheel :include-macros true] 4 | [cljs.core.async :refer [put!]] 5 | [weasel.repl :as weasel])) 6 | 7 | (enable-console-print!) 8 | 9 | (figwheel/watch-and-reload 10 | :websocket-url "ws://localhost:3449/figwheel-ws" 11 | :jsload-callback (fn [] 12 | (core/main) 13 | )) 14 | 15 | (weasel/connect "ws://localhost:9001" :verbose true) 16 | 17 | (core/main) 18 | -------------------------------------------------------------------------------- /code/chapter01/sin-wave/env/prod/cljs/sin_wave/prod.cljs: -------------------------------------------------------------------------------- 1 | (ns sin-wave.prod 2 | (:require [sin-wave.core :as core])) 3 | 4 | (core/main) 5 | -------------------------------------------------------------------------------- /code/chapter01/sin-wave/project.clj: -------------------------------------------------------------------------------- 1 | (defproject sin-wave "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 | 7 | :source-paths ["src/clj" "src/cljs"] 8 | 9 | :dependencies [[org.clojure/clojure "1.6.0"] 10 | [org.clojure/clojurescript "0.0-2371" :scope "provided"] 11 | [ring "1.3.1"] 12 | [compojure "1.2.0"] 13 | [enlive "1.1.5"] 14 | [om "0.7.3"] 15 | [figwheel "0.1.4-SNAPSHOT"] 16 | [environ "1.0.0"] 17 | [com.cemerick/piggieback "0.1.3"] 18 | [weasel "0.4.0-SNAPSHOT"] 19 | [leiningen "2.5.0"]] 20 | 21 | :plugins [[lein-cljsbuild "1.0.3"] 22 | [lein-environ "1.0.0"]] 23 | 24 | :min-lein-version "2.5.0" 25 | 26 | :uberjar-name "sin-wave.jar" 27 | 28 | :cljsbuild {:builds {:app {:source-paths ["src/cljs"] 29 | :compiler {:output-to "resources/public/js/app.js" 30 | :output-dir "resources/public/js/out" 31 | :source-map "resources/public/js/out.js.map" 32 | :preamble ["react/react.min.js"] 33 | :externs ["react/externs/react.js"] 34 | :optimizations :none 35 | :pretty-print true}}}} 36 | 37 | :profiles {:dev {:repl-options {:init-ns sin-wave.server 38 | :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} 39 | 40 | :plugins [[lein-figwheel "0.1.4-SNAPSHOT"]] 41 | 42 | :figwheel {:http-server-root "public" 43 | :port 3449 44 | :css-dirs ["resources/public/css"]} 45 | 46 | :env {:is-dev true} 47 | 48 | :cljsbuild {:builds {:app {:source-paths ["env/dev/cljs"]}}}} 49 | 50 | :uberjar {:hooks [leiningen.cljsbuild] 51 | :env {:production true} 52 | :omit-source true 53 | :aot :all 54 | :cljsbuild {:builds {:app 55 | {:source-paths ["env/prod/cljs"] 56 | :compiler 57 | {:optimizations :advanced 58 | :pretty-print false}}}}}}) 59 | -------------------------------------------------------------------------------- /code/chapter01/sin-wave/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /code/chapter01/sin-wave/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-decoration: underline; 3 | } 4 | -------------------------------------------------------------------------------- /code/chapter01/sin-wave/src/clj/sin_wave/dev.clj: -------------------------------------------------------------------------------- 1 | (ns sin-wave.dev 2 | (:require [environ.core :refer [env]] 3 | [net.cgrand.enlive-html :refer [set-attr prepend append html]] 4 | [cemerick.piggieback :as piggieback] 5 | [weasel.repl.websocket :as weasel] 6 | [leiningen.core.main :as lein])) 7 | 8 | (def is-dev? (env :is-dev)) 9 | 10 | (def inject-devmode-html 11 | (comp 12 | (set-attr :class "is-dev") 13 | (prepend (html [:script {:type "text/javascript" :src "/js/out/goog/base.js"}])) 14 | (prepend (html [:script {:type "text/javascript" :src "/react/react.js"}])) 15 | (append (html [:script {:type "text/javascript"} "goog.require('sin_wave.dev')"])))) 16 | 17 | (defn browser-repl [] 18 | (piggieback/cljs-repl :repl-env (weasel/repl-env :ip "0.0.0.0" :port 9001))) 19 | 20 | (defn start-figwheel [] 21 | (future 22 | (print "Starting figwheel.\n") 23 | (lein/-main ["figwheel"]))) 24 | -------------------------------------------------------------------------------- /code/chapter01/sin-wave/src/clj/sin_wave/server.clj: -------------------------------------------------------------------------------- 1 | (ns sin-wave.server 2 | (:require [clojure.java.io :as io] 3 | [sin-wave.dev :refer [is-dev? inject-devmode-html browser-repl start-figwheel]] 4 | [compojure.core :refer [GET defroutes]] 5 | [compojure.route :refer [resources]] 6 | [compojure.handler :refer [api]] 7 | [net.cgrand.enlive-html :refer [deftemplate]] 8 | [ring.middleware.reload :as reload] 9 | [environ.core :refer [env]] 10 | [ring.adapter.jetty :refer [run-jetty]])) 11 | 12 | (deftemplate page 13 | (io/resource "index.html") [] [:body] (if is-dev? inject-devmode-html identity)) 14 | 15 | (defroutes routes 16 | (resources "/") 17 | (resources "/react" {:root "react"}) 18 | (GET "/*" req (page))) 19 | 20 | (def http-handler 21 | (if is-dev? 22 | (reload/wrap-reload (api #'routes)) 23 | (api routes))) 24 | 25 | (defn run [& [port]] 26 | (defonce ^:private server 27 | (do 28 | (if is-dev? (start-figwheel)) 29 | (let [port (Integer. (or port (env :port) 10555))] 30 | (print "Starting web server on port" port ".\n") 31 | (run-jetty http-handler {:port port 32 | :join? false})))) 33 | server) 34 | 35 | (defn -main [& [port]] 36 | (run port)) 37 | -------------------------------------------------------------------------------- /code/chapter01/sin-wave/src/cljs/sin_wave/core.cljs: -------------------------------------------------------------------------------- 1 | (ns sin-wave.core 2 | (:refer-clojure :exclude [concat repeat])) 3 | 4 | (defn main []) 5 | 6 | (def canvas (.getElementById js/document "myCanvas")) 7 | (def ctx (.getContext canvas "2d")) 8 | 9 | 10 | ;; Clear canvas before doing anything else 11 | (.clearRect ctx 0 0 (.-width canvas) (.-height canvas)) 12 | 13 | (def interval js/Rx.Observable.interval) 14 | (def time (interval 10)) 15 | 16 | (defn deg-to-rad [n] 17 | (* (/ Math/PI 180) n)) 18 | 19 | (defn sine-coord [x] 20 | (let [sin (Math/sin (deg-to-rad x)) 21 | y (- 100 (* sin 90))] 22 | {:x x 23 | :y y 24 | :sin sin})) 25 | 26 | (def sine-wave 27 | (.map time sine-coord)) 28 | 29 | (defn fill-rect [x y colour] 30 | (set! (.-fillStyle ctx) colour) 31 | (.fillRect ctx x y 2 2)) 32 | 33 | #_(-> sine-wave 34 | (.take 600) 35 | (.subscribe (fn [{:keys [x y]}] 36 | (fill-rect x y "orange")))) 37 | 38 | (def colour (.map sine-wave 39 | (fn [{:keys [sin]}] 40 | (if (< sin 0) 41 | "red" 42 | "blue")))) 43 | 44 | #_(-> (.zip sine-wave colour #(vector % %2)) 45 | (.take 600) 46 | (.subscribe (fn [[{:keys [x y]} colour]] 47 | (fill-rect x y colour)))) 48 | 49 | (def red (.map time (fn [_] "red"))) 50 | (def blue (.map time (fn [_] "blue"))) 51 | 52 | (def concat js/Rx.Observable.concat) 53 | (def defer js/Rx.Observable.defer) 54 | (def from-event js/Rx.Observable.fromEvent) 55 | 56 | 57 | (def mouse-click (from-event canvas "click")) 58 | 59 | (def cycle-colour 60 | (concat (.takeUntil red mouse-click) 61 | (defer #(concat (.takeUntil blue mouse-click) 62 | cycle-colour)))) 63 | 64 | (-> (.zip sine-wave cycle-colour #(vector % %2)) 65 | (.take 600) 66 | (.subscribe (fn [[{:keys [x y]} colour]] 67 | (fill-rect x y colour)))) 68 | -------------------------------------------------------------------------------- /code/chapter01/sin-wave/src/cljs/sin_wave/repl.cljs: -------------------------------------------------------------------------------- 1 | (ns sin-wave.repl) 2 | 3 | (comment 4 | 5 | 6 | (.log js/console "hello clojurescript") 7 | 8 | 9 | (-> time 10 | (.take 5) 11 | (.subscribe (fn [n] 12 | (.log js/console n)))) 13 | 14 | 15 | (.log js/console (str (sine-coord 50))) 16 | 17 | (-> sine-wave 18 | (.take 5) 19 | (.subscribe (fn [xysin] 20 | (.log js/console (str xysin))))) 21 | 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | -------------------------------------------------------------------------------- /code/chapter01/sin-wave/system.properties: -------------------------------------------------------------------------------- 1 | java.runtime.version=1.8 2 | -------------------------------------------------------------------------------- /code/chapter02/rx-playground/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /code/chapter02/rx-playground/README.md: -------------------------------------------------------------------------------- 1 | # rx-playground 2 | 3 | A Clojure library designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2014 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /code/chapter02/rx-playground/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to rx-playground 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/chapter02/rx-playground/project.clj: -------------------------------------------------------------------------------- 1 | (defproject rx-playground "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 | :dependencies [[org.clojure/clojure "1.5.1"] 7 | [io.reactivex/rxclojure "1.0.0"]]) 8 | -------------------------------------------------------------------------------- /code/chapter02/rx-playground/src/rx_playground/core.clj: -------------------------------------------------------------------------------- 1 | (ns rx-playground.core) 2 | 3 | 4 | (require '[rx.lang.clojure.core :as rx]) 5 | (import '(rx Observable)) 6 | 7 | ;; 8 | ;; Creating Observables 9 | ;; 10 | 11 | (def obs (rx/return 10)) 12 | 13 | (rx/subscribe obs 14 | (fn [value] 15 | (prn (str "Got value: " value)))) 16 | 17 | 18 | (-> (rx/seq->o [1 2 3 4 5 6 7 8 9 10]) 19 | (rx/subscribe prn)) 20 | 21 | (-> (rx/range 1 10) 22 | (rx/subscribe prn)) 23 | 24 | 25 | (import '(java.util.concurrent TimeUnit)) 26 | 27 | (def repl-out *out*) 28 | (defn prn-to-repl [& args] 29 | (binding [*out* repl-out] 30 | (apply prn args))) 31 | 32 | 33 | (def subscription (rx/subscribe (Observable/interval 100 TimeUnit/MILLISECONDS) 34 | prn-to-repl)) 35 | 36 | (Thread/sleep 1000) 37 | 38 | (rx/unsubscribe subscription) 39 | 40 | (defn just-obs [v] 41 | (rx/observable* 42 | (fn [observer] 43 | (rx/on-next observer v) 44 | (rx/on-completed observer)))) 45 | 46 | (rx/subscribe (just-obs 20) prn) 47 | 48 | 49 | ;; 50 | ;; Manipulating observables 51 | ;; 52 | 53 | (rx/subscribe (->> (Observable/interval 1 TimeUnit/MICROSECONDS) 54 | (rx/filter even?) 55 | (rx/take 5) 56 | (rx/reduce +)) 57 | prn-to-repl) 58 | 59 | 60 | (defn musicians [] 61 | (rx/seq->o ["James Hetfield" "Dave Mustaine" "Kerry King"])) 62 | 63 | (defn bands [] 64 | (rx/seq->o ["Metallica" "Megadeth" "Slayer"])) 65 | 66 | (defn uppercased-obs [] 67 | (rx/map (fn [s] (.toUpperCase s)) (bands))) 68 | 69 | (-> (rx/map vector 70 | (musicians) 71 | (uppercased-obs)) 72 | (rx/subscribe (fn [[musician band]] 73 | (prn-to-repl (str musician " - from: " band))))) 74 | 75 | 76 | ;; 77 | ;; Mapcatting / Flatmapping 78 | ;; 79 | 80 | (defn factorial [n] 81 | (reduce * (range 1 (inc n)))) 82 | 83 | (defn all-positive-integers [] 84 | (Observable/interval 1 TimeUnit/MICROSECONDS)) 85 | 86 | (defn fact-obs [n] 87 | (rx/observable* 88 | (fn [observer] 89 | (rx/on-next observer (factorial n)) 90 | (rx/on-completed observer)))) 91 | 92 | (rx/subscribe (fact-obs 5) prn-to-repl) 93 | 94 | 95 | (rx/subscribe (->> (all-positive-integers) 96 | (rx/filter even?) 97 | (rx/flatmap fact-obs) 98 | (rx/take 5)) 99 | prn-to-repl) 100 | 101 | (defn repeat-obs [n] 102 | (rx/seq->o (repeat 2 n))) 103 | 104 | (-> (repeat-obs 5) 105 | (rx/subscribe prn-to-repl)) 106 | 107 | 108 | (rx/subscribe (->> (all-positive-integers) 109 | (rx/flatmap repeat-obs) 110 | (rx/take 6)) 111 | prn-to-repl) 112 | 113 | 114 | 115 | ;; 116 | ;; Error handling 117 | ;; 118 | 119 | ;; onError 120 | (defn exceptional-obs [] 121 | (rx/observable* 122 | (fn [observer] 123 | (rx/on-next observer (throw (Exception. "Oops. Something went wrong"))) 124 | (rx/on-completed observer)))) 125 | 126 | (rx/subscribe (->> (exceptional-obs) 127 | (rx/map inc)) 128 | (fn [v] (prn-to-repl "result is " v))) 129 | ;; Exception Oops. Something went wrong rx-playground.core/exceptional-obs/fn--1505 130 | 131 | (rx/subscribe (->> (exceptional-obs) 132 | (rx/map inc)) 133 | (fn [v] (prn-to-repl "result is " v)) 134 | (fn [e] (prn-to-repl "error is " e))) 135 | 136 | ;; "error is " # 137 | 138 | 139 | ;; catch 140 | (rx/subscribe (->> (exceptional-obs) 141 | (rx/catch Exception e 142 | (rx/return 10)) 143 | (rx/map inc)) 144 | (fn [v] (prn-to-repl "result is " v))) 145 | 146 | ;; "result is " 11 147 | 148 | (rx/subscribe (->> (exceptional-obs) 149 | (rx/catch Exception e 150 | (rx/seq->o (range 5))) 151 | (rx/map inc)) 152 | (fn [v] (prn-to-repl "result is " v))) 153 | 154 | ;; "result is " 1 155 | ;; "result is " 2 156 | ;; "result is " 3 157 | ;; "result is " 4 158 | ;; "result is " 5 159 | 160 | ;; retry 161 | (defn retry-obs [] 162 | (let [errored (atom false)] 163 | (rx/observable* 164 | (fn [observer] 165 | (throw (Exception. "Oops. Something went wrong")))))) 166 | 167 | (rx/subscribe (retry-obs) 168 | (fn [v] (prn-to-repl "result is " v))) 169 | 170 | ;; Exception Oops. Something went wrong rx-playground.core/retry-obs/fn--1476 171 | 172 | (rx/subscribe (->> (retry-obs) 173 | (.retry)) 174 | (fn [v] (prn-to-repl "result is " v))) 175 | 176 | ;; "result is " 20 177 | 178 | ;; 179 | ;; Backpressure 180 | ;; 181 | 182 | 183 | (defn fast-producing-obs [] 184 | (rx/map inc (Observable/interval 1 TimeUnit/MILLISECONDS))) 185 | 186 | (defn slow-producing-obs [] 187 | (rx/map inc (Observable/interval 500 TimeUnit/MILLISECONDS))) 188 | 189 | (rx/subscribe (->> (rx/map vector 190 | (fast-producing-obs) 191 | (slow-producing-obs)) 192 | (rx/map (fn [[x y]] 193 | (+ x y))) 194 | (rx/take 10)) 195 | prn-to-repl 196 | (fn [e] (prn-to-repl "error is " e))) 197 | 198 | ;; "error is " # 199 | 200 | ;; throttle 201 | 202 | (rx/subscribe (->> (rx/map vector 203 | (.sample (fast-producing-obs) 200 204 | TimeUnit/MILLISECONDS) 205 | (slow-producing-obs)) 206 | (rx/map (fn [[x y]] 207 | (+ x y))) 208 | (rx/take 10)) 209 | prn-to-repl 210 | (fn [e] (prn-to-repl "error is " e))) 211 | 212 | ;; 204 213 | ;; 404 214 | ;; 604 215 | ;; 807 216 | ;; 1010 217 | ;; 1206 218 | ;; 1407 219 | ;; 1613 220 | ;; 1813 221 | ;; 2012 222 | 223 | 224 | ;; onBackpressureBuffer 225 | 226 | 227 | (rx/subscribe (->> (rx/map vector 228 | (.onBackpressureBuffer (fast-producing-obs)) 229 | (slow-producing-obs)) 230 | (rx/map (fn [[x y]] 231 | (+ x y))) 232 | (rx/take 10)) 233 | prn-to-repl 234 | (fn [e] (prn-to-repl "error is " e))) 235 | 236 | ;; 2 237 | ;; 4 238 | ;; 6 239 | ;; 8 240 | ;; 10 241 | ;; 12 242 | ;; 14 243 | ;; 16 244 | ;; 18 245 | ;; 20 246 | -------------------------------------------------------------------------------- /code/chapter02/rx-playground/test/rx_playground/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns rx-playground.core-test 2 | (:require [clojure.test :refer :all] 3 | [rx-playground.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /code/chapter03/stock-market-monitor/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /code/chapter03/stock-market-monitor/README.md: -------------------------------------------------------------------------------- 1 | # stock-market-monitor 2 | 3 | Sample code for Chapter 3 of 'Clojure Reactive Programming' 4 | 5 | ## Usage 6 | 7 | The examples are divided in sections as they were presented in the book. They start with a number, followed by string 8 | which identified what the example contains. 9 | 10 | For example, in order to run the first example: 11 | 12 | lein trampoline run -m stock-market-monitor.01price-monitor 13 | 14 | and so forth for the remaining examples. 15 | 16 | The only exception is example 04buffers.clj which is meant to be executed at the REPL. 17 | 18 | ## License 19 | 20 | Copyright © 2014 FIXME 21 | 22 | Distributed under the Eclipse Public License either version 1.0 or (at 23 | your option) any later version. 24 | -------------------------------------------------------------------------------- /code/chapter03/stock-market-monitor/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to stock-market-monitor 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/chapter03/stock-market-monitor/project.clj: -------------------------------------------------------------------------------- 1 | (defproject stock-market-monitor "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 | :dependencies [[org.clojure/clojure "1.5.1"] 7 | [seesaw "1.4.4"] 8 | [io.reactivex/rxclojure "1.0.0"]]) 9 | -------------------------------------------------------------------------------- /code/chapter03/stock-market-monitor/src/stock_market_monitor/01price_monitor.clj: -------------------------------------------------------------------------------- 1 | (ns stock-market-monitor.01price-monitor 2 | (:require [seesaw.core :refer :all]) 3 | (:import (java.util.concurrent ScheduledThreadPoolExecutor 4 | TimeUnit))) 5 | 6 | (native!) 7 | 8 | (def main-frame (frame :title "Stock price monitor" 9 | :width 200 :height 100 10 | :on-close :exit)) 11 | 12 | (def price-label (label "Price: -")) 13 | 14 | (config! main-frame :content price-label) 15 | 16 | (def pool (atom nil)) 17 | 18 | (defn init-scheduler [num-threads] 19 | (reset! pool (ScheduledThreadPoolExecutor. num-threads))) 20 | 21 | (defn run-every [pool millis f] 22 | (.scheduleWithFixedDelay pool 23 | f 24 | 0 millis TimeUnit/MILLISECONDS)) 25 | 26 | (defn shutdown [pool] 27 | (println "Shutting down scheduler...") 28 | (.shutdown pool)) 29 | 30 | (defn share-price [company-code] 31 | (Thread/sleep 200) 32 | (rand-int 1000)) 33 | 34 | (defn -main [& args] 35 | (show! main-frame) 36 | (.addShutdownHook (Runtime/getRuntime) 37 | (Thread. #(shutdown @pool))) 38 | (init-scheduler 1) 39 | (run-every @pool 500 40 | #(->> (str "Price: " (share-price "XYZ")) 41 | (text! price-label) 42 | invoke-now))) 43 | -------------------------------------------------------------------------------- /code/chapter03/stock-market-monitor/src/stock_market_monitor/02price_monitor_rolling_avg.clj: -------------------------------------------------------------------------------- 1 | (ns stock-market-monitor.02price-monitor-rolling-avg 2 | (:require [seesaw.core :refer :all]) 3 | (:import (java.util.concurrent ScheduledThreadPoolExecutor 4 | TimeUnit) 5 | (clojure.lang PersistentQueue))) 6 | 7 | (native!) 8 | 9 | (def main-frame (frame :title "Stock price monitor" 10 | :width 200 :height 100 11 | :on-close :exit)) 12 | 13 | (def price-label (label "Price: -")) 14 | (def running-avg-label (label "Running average: -")) 15 | 16 | (config! main-frame :content 17 | (border-panel 18 | :north price-label 19 | :center running-avg-label 20 | :border 5)) 21 | 22 | (def pool (atom nil)) 23 | 24 | (defn init-scheduler [num-threads] 25 | (reset! pool (ScheduledThreadPoolExecutor. num-threads))) 26 | 27 | (defn run-every [pool millis f] 28 | (.scheduleWithFixedDelay pool 29 | f 30 | 0 millis TimeUnit/MILLISECONDS)) 31 | 32 | (defn shutdown [pool] 33 | (println "Shutting down scheduler...") 34 | (.shutdown pool)) 35 | 36 | (defn share-price [company-code] 37 | (Thread/sleep 200) 38 | (rand-int 1000)) 39 | 40 | (defn roll-buffer [buffer num buffer-size] 41 | (let [buffer (conj buffer num)] 42 | (if (> (count buffer) buffer-size) 43 | (pop buffer) 44 | buffer))) 45 | 46 | (defn avg [numbers] 47 | (float (/ (reduce + numbers) 48 | (count numbers)))) 49 | 50 | (defn make-running-avg [buffer-size] 51 | (let [buffer (atom clojure.lang.PersistentQueue/EMPTY)] 52 | (fn [n] 53 | (swap! buffer roll-buffer n buffer-size) 54 | (avg @buffer)))) 55 | 56 | (def running-avg (make-running-avg 5)) 57 | 58 | (defn worker [] 59 | (let [price (share-price "XYZ")] 60 | (->> (str "Price: " price) (text! price-label)) 61 | (->> (str "Running average: " (running-avg price)) 62 | (text! running-avg-label)))) 63 | 64 | (defn -main [& args] 65 | (show! main-frame) 66 | (.addShutdownHook (Runtime/getRuntime) 67 | (Thread. #(shutdown @pool))) 68 | (init-scheduler 1) 69 | (run-every @pool 500 70 | #(invoke-now (worker)))) 71 | -------------------------------------------------------------------------------- /code/chapter03/stock-market-monitor/src/stock_market_monitor/03frp_price_monitor.clj: -------------------------------------------------------------------------------- 1 | (ns stock-market-monitor.03frp-price-monitor 2 | (:require [rx.lang.clojure.core :as rx] 3 | [seesaw.core :refer :all]) 4 | (:import (java.util.concurrent TimeUnit) 5 | (rx Observable))) 6 | 7 | (native!) 8 | 9 | (def main-frame (frame :title "Stock price monitor" 10 | :width 200 :height 100 11 | :on-close :exit)) 12 | 13 | (def price-label (label "Price: -")) 14 | (def running-avg-label (label "Running average: -")) 15 | 16 | (config! main-frame :content 17 | (border-panel 18 | :north price-label 19 | :center running-avg-label 20 | :border 5)) 21 | 22 | (defn share-price [company-code] 23 | (Thread/sleep 200) 24 | (rand-int 1000)) 25 | 26 | (defn avg [numbers] 27 | (float (/ (reduce + numbers) 28 | (count numbers)))) 29 | 30 | (defn make-price-obs [company-code] 31 | (rx/return (share-price company-code))) 32 | 33 | (defn -main [& args] 34 | (show! main-frame) 35 | (let [price-obs (rx/flatmap (fn [_] (make-price-obs "XYZ")) 36 | (Observable/interval 500 TimeUnit/MILLISECONDS))] 37 | (rx/subscribe price-obs 38 | (fn [price] 39 | (text! price-label (str "Price: " price)))))) 40 | -------------------------------------------------------------------------------- /code/chapter03/stock-market-monitor/src/stock_market_monitor/04buffer.clj: -------------------------------------------------------------------------------- 1 | (ns stock-market-monitor.04buffer 2 | (:require [rx.lang.clojure.core :as rx]) 3 | (:import (java.util.concurrent TimeUnit) 4 | (rx Observable))) 5 | 6 | (def values (range 10)) 7 | 8 | (doseq [buffer (partition 5 1 values)] 9 | (prn buffer)) 10 | 11 | (def repl-out *out*) 12 | (defn prn-to-repl [& args] 13 | (binding [*out* repl-out] 14 | (apply prn args))) 15 | 16 | (-> (rx/seq->o (vec (range 10))) 17 | (.buffer 5 1) 18 | (rx/subscribe 19 | (fn [price] 20 | (prn (str "Value: " price))))) 21 | -------------------------------------------------------------------------------- /code/chapter03/stock-market-monitor/src/stock_market_monitor/05frp_price_monitor_rolling_avg.clj: -------------------------------------------------------------------------------- 1 | (ns stock-market-monitor.05frp-price-monitor-rolling-avg 2 | (:require [rx.lang.clojure.core :as rx] 3 | [seesaw.core :refer :all]) 4 | (:import (java.util.concurrent TimeUnit) 5 | (rx Observable))) 6 | 7 | (native!) 8 | 9 | (def main-frame (frame :title "Stock price monitor" 10 | :width 200 :height 100 11 | :on-close :exit)) 12 | 13 | (def price-label (label "Price: -")) 14 | (def running-avg-label (label "Running average: -")) 15 | 16 | (config! main-frame :content 17 | (border-panel 18 | :north price-label 19 | :center running-avg-label 20 | :border 5)) 21 | 22 | (defn share-price [company-code] 23 | (Thread/sleep 200) 24 | (rand-int 1000)) 25 | 26 | (defn avg [numbers] 27 | (float (/ (reduce + numbers) 28 | (count numbers)))) 29 | 30 | (defn make-price-obs [_] 31 | (rx/return (share-price "XYZ"))) 32 | 33 | (defn -main [& args] 34 | (show! main-frame) 35 | (let [price-obs (-> (rx/flatmap make-price-obs 36 | (Observable/interval 500 TimeUnit/MILLISECONDS)) 37 | (.publish)) 38 | sliding-buffer-obs (.buffer price-obs 5 1)] 39 | (rx/subscribe price-obs 40 | (fn [price] 41 | (text! price-label (str "Price: " price)))) 42 | (rx/subscribe sliding-buffer-obs 43 | (fn [buffer] 44 | (text! running-avg-label (str "Running average: " (avg buffer))))) 45 | (.connect price-obs))) 46 | -------------------------------------------------------------------------------- /code/chapter03/stock-market-monitor/test/stock_market_monitor/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns stock-market-monitor.core-test 2 | (:require [clojure.test :refer :all] 3 | [stock-market-monitor.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /code/chapter04/core-async-playground/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /code/chapter04/core-async-playground/README.md: -------------------------------------------------------------------------------- 1 | # core-async-playground 2 | 3 | A Clojure library designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2014 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /code/chapter04/core-async-playground/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to core-async-playground 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/chapter04/core-async-playground/project.clj: -------------------------------------------------------------------------------- 1 | (defproject core-async-playground "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 | :dependencies [[org.clojure/clojure "1.5.1"] 7 | [org.clojure/core.async "0.1.278.0-76b25b-alpha"] 8 | [seesaw "1.4.4"]]) 9 | -------------------------------------------------------------------------------- /code/chapter04/core-async-playground/src/core_async_playground/backpressure.clj: -------------------------------------------------------------------------------- 1 | (ns core-async-playground.backpressure 2 | (:require [clojure.core.async 3 | :refer [go chan ! buffer dropping-buffer sliding-buffer go-loop close!] :as async])) 4 | 5 | 6 | 7 | ;; fixed buffer 8 | 9 | (def result (chan (buffer 5))) 10 | (go-loop [] 11 | (! result n)) 18 | (prn "Done putting values!") 19 | (close! result)) 20 | 21 | ;; "Done putting values!" 22 | ;; "Got value: " 0 23 | ;; "Got value: " 1 24 | ;; "Got value: " 2 25 | ;; "Got value: " 3 26 | ;; "Got value: " 4 27 | 28 | 29 | (def result (chan (buffer 2))) 30 | (go-loop [] 31 | (! result n)) 38 | (prn "Done putting values!") 39 | (close! result)) 40 | 41 | ;; "Got value: " 0 42 | ;; "Got value: " 1 43 | ;; "Got value: " 2 44 | ;; "Done putting values!" 45 | ;; "Got value: " 3 46 | ;; "Got value: " 4 47 | 48 | 49 | ;; dropping buffer 50 | 51 | 52 | (def result (chan (dropping-buffer 2))) 53 | (go-loop [] 54 | (! result n)) 61 | (prn "Done putting values!") 62 | (close! result)) 63 | 64 | ;; "Done putting values!" 65 | ;; "Got value: " 0 66 | ;; "Got value: " 1 67 | 68 | 69 | ;; sliding buffer 70 | 71 | (def result (chan (sliding-buffer 2))) 72 | (go-loop [] 73 | (! result n)) 80 | (prn "Done putting values!") 81 | (close! result)) 82 | 83 | ;; "Done putting values!" 84 | ;; "Got value: " 3 85 | ;; "Got value: " 4 86 | -------------------------------------------------------------------------------- /code/chapter04/core-async-playground/src/core_async_playground/core.clj: -------------------------------------------------------------------------------- 1 | (ns core-async-playground.core 2 | (:require [clojure.core.async :refer [go chan ! timeout ! c "Leo"))) 12 | 13 | (defn consumer [c] 14 | (go (prn-with-thread-id "Attempting to take value from queue now...") 15 | (prn-with-thread-id (str "Got it. Hello " (! c 1) 27 | (>! c 2)) 28 | 29 | (go (prn-with-thread-id (! map>] :as async])) 4 | 5 | 6 | ;; First, without any special treatment 7 | 8 | (defn get-data [] 9 | (throw (Exception. "Bad things happen!"))) 10 | 11 | (defn process [] 12 | (let [result (chan)] 13 | ;; do some processing... 14 | (go (>! result (get-data))) 15 | result)) 16 | 17 | 18 | ;; The go block eats the exception so we never get a chance to handle nor do we know 19 | ;; whether the processing succeeded 20 | 21 | (go (let [result (> (process "data") 22 | (map> #(* % %)) 23 | (map> #(prn %)))] 24 | (prn "result is: " result))) 25 | 26 | 27 | ;; Using the ! result (try (get-data) 43 | (catch Exception e 44 | e)))) 45 | result)) 46 | 47 | 48 | ;; We regain control of the Exceptions and can use normal try catch statements 49 | 50 | (go (try (let [result (> (process "data") 51 | (map> #(* % %)) 52 | (map> #(prn %))))] 53 | (prn "result is: " result)) 54 | (catch Exception e 55 | (prn "Oops, an error happened! We better do something about it here!")))) 56 | 57 | ;; "Oops, an error happened! We better do something about it here!" 58 | -------------------------------------------------------------------------------- /code/chapter04/core-async-playground/src/core_async_playground/stock_market.clj: -------------------------------------------------------------------------------- 1 | (ns core-async-playground.stock-market 2 | (:require [clojure.core.async 3 | :refer [go chan ! timeout go-loop map>] :as async]) 4 | (:require [clojure.core.async.lab :refer [broadcast]]) 5 | (:use [seesaw.core])) 6 | 7 | (native!) 8 | 9 | (def main-frame (frame :title "Stock price monitor" 10 | :width 200 :height 100 11 | :on-close :exit)) 12 | 13 | (def price-label (label "Price: -")) 14 | (def running-avg-label (label "Running average: -")) 15 | 16 | (config! main-frame :content 17 | (border-panel 18 | :north price-label 19 | :center running-avg-label 20 | :border 5)) 21 | 22 | (defn share-price [company-code] 23 | (Thread/sleep 200) 24 | (rand-int 1000)) 25 | 26 | (defn avg [numbers] 27 | (float (/ (reduce + numbers) 28 | (count numbers)))) 29 | 30 | (defn roll-buffer [buffer val buffer-size] 31 | (let [buffer (conj buffer val)] 32 | (if (> (count buffer) buffer-size) 33 | (pop buffer) 34 | buffer))) 35 | 36 | (defn make-sliding-buffer [buffer-size] 37 | (let [buffer (atom clojure.lang.PersistentQueue/EMPTY)] 38 | (fn [n] 39 | (swap! buffer roll-buffer n buffer-size)))) 40 | 41 | (def sliding-buffer (make-sliding-buffer 5)) 42 | 43 | (defn broadcast-at-interval [msecs task & ports] 44 | (go-loop [out (apply broadcast ports)] 45 | (! out (task)) 47 | (recur out))) 48 | 49 | (defn -main [& args] 50 | (show! main-frame) 51 | (let [prices-ch (chan) 52 | sliding-buffer-ch (map> sliding-buffer (chan))] 53 | (broadcast-at-interval 500 #(share-price "XYZ") prices-ch sliding-buffer-ch) 54 | (go-loop [] 55 | (when-let [price (! > (range 10) 10 | (map inc) ;; creates a new sequence 11 | (filter even?) ;; creates a new sequence 12 | (prn "result is ")) 13 | 14 | ;; "result is " (2 4 6 8 10) 15 | 16 | 17 | ;; 18 | ;; transducer sequences 19 | ;; 20 | 21 | (def xform 22 | (comp (map inc) 23 | (filter even?))) ;; no intermediate sequence created 24 | 25 | (->> (range 10) 26 | (sequence xform) 27 | (prn "result is ")) 28 | 29 | ;; "result is " (2 4 6 8 10) 30 | 31 | 32 | ;; non-transducer core.async 33 | 34 | (def result (chan 10)) 35 | 36 | (def transformed 37 | (->> result 38 | (map< inc) ;; creates a new channel 39 | (filter< even?) ;; creates a new channel 40 | (into []))) 41 | 42 | 43 | (go 44 | (prn "result is " (! result n)) 49 | (close! result)) 50 | 51 | ;; "result is " [2 4 6 8 10] 52 | 53 | 54 | 55 | ;; transducer core.async 56 | 57 | (def result (chan 10)) 58 | 59 | (def xform (comp (map inc) 60 | (filter even?))) ;; no intermediate channels created 61 | 62 | (def transformed (->> (pipe result (chan 10 xform)) 63 | (into []))) 64 | 65 | 66 | (go 67 | (prn "result is " (! result n)) 72 | (close! result)) 73 | 74 | ;; "result is " [2 4 6 8 10] 75 | -------------------------------------------------------------------------------- /code/chapter04/core-async-transducers/test/core_async_transducers/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns core-async-transducers.core-test 2 | (:require [clojure.test :refer :all] 3 | [core-async-transducers.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /code/chapter04/repl.clj: -------------------------------------------------------------------------------- 1 | ;; 01 2 | (defn do-something-important [] 3 | (let [f (future (do (prn "Calculating...") 4 | (Thread/sleep 10000)))] 5 | (prn "Perhaps the future has done its job?") 6 | (prn @f) 7 | (prn "You will only see this in about 10 seconds..."))) 8 | 9 | (do-something-important) 10 | 11 | ;; 02 12 | (defn do-something-important [callback] 13 | (let [f (future (let [answer 42] 14 | (Thread/sleep 10000) 15 | (callback answer)))] 16 | (prn "Perhaps the future has done its job?") 17 | (prn "You should see this almost immediately and then in 10 secs...") 18 | f)) 19 | 20 | (do-something-important (fn [answer] 21 | (prn "Future is done. Answer is " answer))) 22 | 23 | 24 | ;; 03 25 | (import 'java.util.concurrent.ArrayBlockingQueue) 26 | 27 | (defn producer [c] 28 | (prn "Taking a nap") 29 | (Thread/sleep 5000) 30 | (prn "Now putting a name in queue...") 31 | (.put c "Leo")) 32 | 33 | (defn consumer [c] 34 | (prn "Attempting to take value from queue now...") 35 | (prn (str "Got it. Hello " (.take c) "!"))) 36 | 37 | (def chan (ArrayBlockingQueue. 10)) 38 | 39 | (future (consumer chan)) 40 | (future (producer chan)) 41 | -------------------------------------------------------------------------------- /code/chapter05/respondent-app/.gitignore: -------------------------------------------------------------------------------- 1 | /repl 2 | /target 3 | /lib 4 | /classes 5 | /checkouts 6 | /dev-resources/public/js/* 7 | /out 8 | pom.xml 9 | *.jar 10 | *.class 11 | .lein-deps-sum 12 | .lein-failures 13 | .lein-plugins 14 | .lein-cljsbuild* 15 | .lein-repl* 16 | .crossover-cljs 17 | .repl 18 | -------------------------------------------------------------------------------- /code/chapter05/respondent-app/.nrepl-port: -------------------------------------------------------------------------------- 1 | 51963 -------------------------------------------------------------------------------- /code/chapter05/respondent-app/README.md: -------------------------------------------------------------------------------- 1 | # respondent-app 2 | 3 | A ClojureScript library designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2014 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /code/chapter05/respondent-app/dev-resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Example: tracking mouse position 6 | 9 | 10 | 11 | 12 |
13 |

ClojureScript

14 |
15 |
16 | (0,0) 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /code/chapter05/respondent-app/dev-resources/tools/http/ring/server.clj: -------------------------------------------------------------------------------- 1 | ;;; This namespace is used for development and testing purpose only. 2 | (ns ring.server 3 | (:require [cemerick.austin.repls :refer (browser-connected-repl-js)] 4 | [net.cgrand.enlive-html :as enlive] 5 | [compojure.route :refer (resources)] 6 | [compojure.core :refer (GET defroutes)] 7 | [ring.adapter.jetty :as jetty] 8 | [clojure.java.io :as io])) 9 | 10 | ;;; We use enlive lib to add to the body of the index.html page the 11 | ;;; script tag containing the JS code which activates the bREPL 12 | ;;; connection. 13 | (enlive/deftemplate page 14 | (io/resource "public/index.html") 15 | [] 16 | [:body] (enlive/append 17 | (enlive/html [:script (browser-connected-repl-js)]))) 18 | 19 | ;;; defroutes macro defines a function that chains individual route 20 | ;;; functions together. The request map is passed to each function in 21 | ;;; turn, until a non-nil response is returned. 22 | (defroutes site 23 | (resources "/") 24 | (GET "/*" req (page))) 25 | 26 | ;;; To run the jetty server. The server symbol is not private to 27 | ;;; allows to start and stop thejetty server from the repl. 28 | (defn run 29 | "Run the ring server. It defines the server symbol with defonce." 30 | [] 31 | (defonce server 32 | (jetty/run-jetty #'site {:port 3000 :join? false})) 33 | server) 34 | -------------------------------------------------------------------------------- /code/chapter05/respondent-app/dev-resources/tools/repl/brepl/connect.cljs: -------------------------------------------------------------------------------- 1 | ;;; This namespace is for creating the connection with the browser. It 2 | ;;; lives in the dev-resources/tools/brepl directory. It is used in the 3 | ;;; :dev profile only. 4 | (ns brepl.connect 5 | (:require [clojure.browser.repl])) 6 | -------------------------------------------------------------------------------- /code/chapter05/respondent-app/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to respondent-app 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/chapter05/respondent-app/profiles.clj: -------------------------------------------------------------------------------- 1 | ;;; ****************************** NOTES ****************************** 2 | ;;; Defines four profiles: 3 | ;;; 4 | ;;; - :shared 5 | ;;; - :dev 6 | ;;; - :simple 7 | ;;; - :advanced 8 | ;;; 9 | ;;; the :dev, :simple and :advanced profiles are composite profiles, 10 | ;;; meaning that they share the content of :shared profile. 11 | ;;; ******************************************************************* 12 | 13 | {:shared {:clean-targets ["out" :target-path] 14 | :test-paths ["test/clj" "test/cljs"] 15 | :resources-paths ["dev-resources"] 16 | :plugins [[com.cemerick/clojurescript.test "0.2.1"]] 17 | :cljsbuild 18 | {:builds {:respondent-app 19 | {:source-paths ["test/cljs"] 20 | :compiler 21 | {:output-dir "dev-resources/public/js" 22 | :source-map "dev-resources/public/js/respondent_app.js.map"}}} 23 | :test-commands {"phantomjs" 24 | ["phantomjs" :runner "dev-resources/public/js/respondent_app.js"]}}} 25 | :dev [:shared 26 | {:source-paths ["dev-resources/tools/http" "dev-resources/tools/repl"] 27 | :dependencies [[ring "1.2.1"] 28 | [compojure "1.1.6"] 29 | [enlive "1.1.4"]] 30 | :plugins [[com.cemerick/austin "0.1.3"]] 31 | :cljsbuild 32 | {:builds {:respondent-app 33 | {:source-paths ["dev-resources/tools/repl"] 34 | :compiler 35 | {:optimizations :whitespace 36 | :pretty-print true}}}} 37 | 38 | :injections [(require '[ring.server :as http :refer [run]] 39 | 'cemerick.austin.repls) 40 | (defn browser-repl [] 41 | (cemerick.austin.repls/cljs-repl (reset! cemerick.austin.repls/browser-repl-env 42 | (cemerick.austin/repl-env))))]}] 43 | ;; simple profile. 44 | :simple [:shared 45 | {:cljsbuild 46 | {:builds {:respondent-app 47 | {:compiler {:optimizations :simple 48 | :pretty-print false}}}}}] 49 | ;; advanced profile 50 | :advanced [:shared 51 | {:cljsbuild 52 | {:builds {:respondent-app 53 | {:source-paths ["test/cljs"] 54 | :compiler 55 | {:optimizations :advanced 56 | :pretty-print false}}}}}]} 57 | 58 | -------------------------------------------------------------------------------- /code/chapter05/respondent-app/project.clj: -------------------------------------------------------------------------------- 1 | (defproject respondent-app "0.0.1-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License - v 1.0" 5 | :url "http://www.eclipse.org/legal/epl-v10.html" 6 | :distribution :repo} 7 | 8 | :min-lein-version "2.3.4" 9 | 10 | ;; We need to add src/cljs too, because cljsbuild does not add its 11 | ;; source-paths to the project source-paths 12 | :source-paths ["src/clj" "src/cljs"] 13 | 14 | :dependencies [[org.clojure/clojure "1.5.1"] 15 | [org.clojure/clojurescript "0.0-2138"] 16 | [prismatic/dommy "0.1.2"] 17 | [clojure-reactive-programming/respondent "0.1.0-SNAPSHOT"]] 18 | 19 | :plugins [[lein-cljsbuild "1.0.1"]] 20 | 21 | :hooks [leiningen.cljsbuild] 22 | 23 | :cljsbuild 24 | {:builds {;; This build is only used for including any cljs source 25 | ;; in the packaged jar when you issue lein jar command and 26 | ;; any other command that depends on it 27 | :respondent-app 28 | {:source-paths ["src/cljs"] 29 | ;; The :jar true option is not needed to include the CLJS 30 | ;; sources in the packaged jar. This is because we added 31 | ;; the CLJS source codebase to the Leiningen 32 | ;; :source-paths 33 | ;:jar true 34 | ;; Compilation Options 35 | :compiler 36 | {:output-to "dev-resources/public/js/respondent_app.js" 37 | :optimizations :advanced 38 | :pretty-print false}}}}) 39 | -------------------------------------------------------------------------------- /code/chapter05/respondent-app/src/cljs/respondent_app/core.cljs: -------------------------------------------------------------------------------- 1 | (ns respondent-app.core 2 | (:require [respondent.core :as r] 3 | [dommy.core :as dommy]) 4 | (:use-macros 5 | [dommy.macros :only [sel1]])) 6 | 7 | 8 | (def mouse-pos-stream (r/event-stream)) 9 | (set! (.-onmousemove js/document) 10 | (fn [e] 11 | (r/deliver mouse-pos-stream [(.-pageX e) (.-pageY e)]))) 12 | 13 | (r/subscribe mouse-pos-stream 14 | (fn [[x y]] 15 | (dommy/set-text! (sel1 :#mouse-xy) 16 | (str "(" x "," y ")")))) 17 | -------------------------------------------------------------------------------- /code/chapter05/respondent-app/test/cljs/respondent_app/core_test.cljs: -------------------------------------------------------------------------------- 1 | ;;; This namespace is used for testing purpose. It use the 2 | ;;; clojurescript.test lib. 3 | (ns respondent-app.core-test 4 | (:require-macros [cemerick.cljs.test :as m :refer (deftest testing are)]) 5 | (:require [cemerick.cljs.test :as t] 6 | [respondent-app.core :refer (foo)])) 7 | 8 | (deftest foo-test 9 | (testing "I don't do a lot\n" 10 | (testing "Edge cases\n" 11 | (testing "(foo str)" 12 | (are [expected actual] (= expected actual) 13 | "ClojureScript!" (foo "") 14 | "Hello, ClojureScript!" (foo nil)))))) 15 | -------------------------------------------------------------------------------- /code/chapter05/respondent/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /code/chapter05/respondent/README.md: -------------------------------------------------------------------------------- 1 | # respondent 2 | 3 | A Clojure library designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2014 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /code/chapter05/respondent/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to respondent 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/chapter05/respondent/dotimes.perl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | for $i (1 .. 10) { 3 | system("lein with-profile test do cljsbuild test"); 4 | } -------------------------------------------------------------------------------- /code/chapter05/respondent/project.clj: -------------------------------------------------------------------------------- 1 | (defproject clojure-reactive-programming/respondent "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 | :dependencies [[org.clojure/clojure "1.5.1"] 7 | [org.clojure/core.async "0.1.303.0-886421-alpha"] 8 | [org.clojure/clojurescript "0.0-2202"]] 9 | 10 | :plugins [[com.keminglabs/cljx "0.3.2"] 11 | [lein-cljsbuild "1.0.3"] 12 | [com.cemerick/clojurescript.test "0.3.0"]] 13 | :cljx {:builds [{:source-paths ["src/cljx"] 14 | :output-path "target/classes" 15 | :rules :clj} 16 | 17 | {:source-paths ["src/cljx"] 18 | :output-path "target/classes" 19 | :rules :cljs}]} 20 | :hooks [cljx.hooks] 21 | 22 | :profiles {:test { 23 | :cljx {:builds [{:source-paths ["test/cljx"] 24 | :output-path "target/test/classes" 25 | :rules :clj} 26 | 27 | {:source-paths ["test/cljx"] 28 | :output-path "target/test/classes" 29 | :rules :cljs}]} 30 | :cljsbuild 31 | {:builds ^:replace [{:source-paths ["target/classes" "target/test/classes"] 32 | :compiler {:output-to "target/test.js"}}] 33 | :test-commands {"unit-tests" ["phantomjs" :runner "target/test.js"]}}}} 34 | 35 | 36 | :cljsbuild 37 | {:builds [{:source-paths ["target/classes"] 38 | :compiler {:output-to "target/main.js"}}]}) 39 | -------------------------------------------------------------------------------- /code/chapter05/respondent/src/cljx/respondent/core.cljx: -------------------------------------------------------------------------------- 1 | (ns respondent.core 2 | (:refer-clojure :exclude [filter map deliver]) 3 | 4 | #+clj 5 | (:import [clojure.lang IDeref]) 6 | 7 | #+clj 8 | (:require [clojure.core.async :as async 9 | :refer [go go-loop chan ! timeout 10 | map> filter> close! mult tap untap]]) 11 | #+cljs 12 | (:require [cljs.core.async :as async 13 | :refer [chan ! timeout map> filter> 14 | close! mult tap untap]]) 15 | 16 | #+cljs 17 | (:require-macros [respondent.core :refer [behavior]] 18 | [cljs.core.async.macros :refer [go go-loop]])) 19 | 20 | (defprotocol IBehavior 21 | (sample [b interval] 22 | "Turns this Behavior into an EventStream from the sampled values at the given interval")) 23 | 24 | (declare from-interval) 25 | 26 | (deftype Behavior [f] 27 | IBehavior 28 | (sample [_ interval] 29 | (from-interval interval (f) (fn [& args] (f)))) 30 | IDeref 31 | (#+clj deref #+cljs -deref [_] 32 | (f))) 33 | 34 | (defmacro behavior [& body] 35 | `(Behavior. #(do ~@body))) 36 | 37 | (defprotocol IEventStream 38 | (map [s f] 39 | "Returns a new stream containing the result of applying f 40 | to the values in s") 41 | (filter [s pred] 42 | "Returns a new stream containing the items from s 43 | for which pred returns true") 44 | (flatmap [s f] 45 | "Takes a function f from values in s to a new EventStream. 46 | Returns an EventStream containing values from all underlying streams combined.") 47 | (deliver [s value] 48 | "Delivers a value to the stream s") 49 | (completed? [s] 50 | "Returns true if this stream has stopped emitting values. False otherwise.")) 51 | 52 | (defprotocol IObservable 53 | (subscribe [obs f] "Register a callback to be invoked when the underlying stream changes. 54 | Returns a token the subscriber can use to cancel the subscription.")) 55 | 56 | (defprotocol IToken 57 | (dispose [tk] 58 | "Called when the subscriber isn't interested in receiving more items")) 59 | 60 | (deftype Token [ch] 61 | IToken 62 | (dispose [_] 63 | (close! ch))) 64 | 65 | (declare event-stream) 66 | 67 | (deftype EventStream [channel multiple completed] 68 | IEventStream 69 | (map [_ f] 70 | (let [out (map> f (chan))] 71 | (tap multiple out) 72 | (event-stream out))) 73 | 74 | (filter [_ pred] 75 | (let [out (filter> pred (chan))] 76 | (tap multiple out) 77 | (event-stream out))) 78 | 79 | (flatmap [_ f] 80 | (let [es (event-stream) 81 | out (chan)] 82 | (tap multiple out) 83 | (go-loop [] 84 | (when-let [a (! channel value) 95 | (close! channel))) 96 | (go (>! channel value)))) 97 | 98 | (completed? [_] @completed) 99 | 100 | 101 | IObservable 102 | (subscribe [this f] 103 | (let [out (chan)] 104 | (tap multiple out) 105 | (go-loop [] 106 | (let [value (! timeout alts!]]) 8 | 9 | #+cljs (:require [cemerick.cljs.test :as t] 10 | [respondent.core :as r] 11 | [cljs.core.async :as async 12 | :refer [chan ! timeout alts!]]) 13 | 14 | #+cljs (:require-macros [cemerick.cljs.test 15 | :refer (is deftest block-or-done testing)] 16 | [respondent.core :refer [behavior]] 17 | [cljs.core.async.macros :refer [go go-loop]])) 18 | 19 | 20 | (deftest event-stream-tests 21 | (testing "deref" 22 | (let [stream (r/event-stream)] 23 | (is (= @stream ::r/empty)))) 24 | 25 | (testing "deliver" 26 | (let [stream (r/event-stream)] 27 | (r/deliver stream 10) 28 | (is (= @stream 10)))) 29 | 30 | (testing "stream completion" 31 | (let [stream (r/event-stream)] 32 | (is (not (r/completed? stream))) 33 | (r/deliver stream ::r/complete) 34 | (is (r/completed? stream))))) 35 | 36 | 37 | (deftest ^:async subscribe-to-event-stream 38 | (let [complete (chan) 39 | result-ch (chan) 40 | stream (r/event-stream)] 41 | 42 | (r/subscribe stream (fn [value] 43 | (go (>! result-ch value)))) 44 | (r/deliver stream 765) 45 | (go 46 | (is (= 765 (! complete true)) 48 | (block-or-done complete))) 49 | 50 | (deftest ^:async mapping 51 | (let [complete (chan) 52 | result-ch (chan) 53 | stream (r/event-stream) 54 | doubled (r/map stream #(+ % %))] 55 | 56 | (r/subscribe doubled (fn [value] 57 | (go (>! result-ch value)))) 58 | (go 59 | (r/deliver stream 2) 60 | (! complete true)) 66 | (block-or-done complete))) 67 | 68 | (deftest ^:async filtering 69 | (let [complete (chan) 70 | result-ch (chan) 71 | stream (r/event-stream) 72 | even (r/filter stream even?)] 73 | 74 | (r/subscribe even (fn [value] 75 | (go (>! result-ch value)))) 76 | 77 | (go 78 | (doseq [n (range 4)] 79 | (r/deliver stream n) 80 | (! complete true)) 84 | (block-or-done complete))) 85 | 86 | (deftest ^:async flatmapping 87 | (let [complete (chan) 88 | result-ch (chan) 89 | stream (r/event-stream) 90 | flatmapped (r/flatmap stream (fn [n] 91 | (let [es (r/event-stream)] 92 | (go (doseq [n' (range n)] 93 | (r/deliver es n') 94 | (! result-ch value)))) 100 | 101 | (go 102 | (r/deliver stream 2) 103 | (! complete true)) 113 | (block-or-done complete))) 114 | 115 | (deftest ^:async event-stream-from-interval 116 | (let [complete (chan) 117 | result-ch (chan) 118 | stream (r/from-interval 10)] 119 | 120 | (r/subscribe stream (fn [value] 121 | (go (>! result-ch value)))) 122 | 123 | (go (! complete true)) 129 | 130 | (block-or-done complete))) 131 | 132 | (deftest ^:async disposing-subscriptions 133 | (let [complete (chan) 134 | result-ch (chan) 135 | stream (r/event-stream) 136 | tk (r/subscribe stream (fn [value] 137 | (go (>! result-ch value))))] 138 | 139 | (go (r/deliver stream 42) 140 | (! complete true)) 153 | 154 | (block-or-done complete))) 155 | 156 | (deftest behavior-tests 157 | (testing "deref" 158 | (let [a (atom 0) 159 | b (behavior @a)] 160 | (is (= @b 0)) 161 | (swap! a inc) 162 | (is (= @b 1))))) 163 | 164 | (deftest ^:async behavior-sampling 165 | (let [complete (chan) 166 | result-ch (chan) 167 | a (atom 0) 168 | b (behavior (swap! a inc)) 169 | stream (r/sample b 10)] 170 | 171 | 172 | (r/subscribe stream (fn [value] 173 | (go (! result-ch value)))) 175 | (go 176 | (is (= 1 (! complete true)) 181 | (block-or-done complete))) 182 | 183 | 184 | 185 | (comment 186 | (clojure.test/run-tests 'respondent.core-test) 187 | ) 188 | -------------------------------------------------------------------------------- /code/chapter06/reagi-game/.gitignore: -------------------------------------------------------------------------------- 1 | /repl 2 | /target 3 | /lib 4 | /classes 5 | /checkouts 6 | /dev-resources/public/js/* 7 | /out 8 | pom.xml 9 | *.jar 10 | *.class 11 | .lein-deps-sum 12 | .lein-failures 13 | .lein-plugins 14 | .lein-cljsbuild* 15 | .lein-repl* 16 | .crossover-cljs 17 | .repl 18 | .nrepl-port 19 | -------------------------------------------------------------------------------- /code/chapter06/reagi-game/README.md: -------------------------------------------------------------------------------- 1 | # reagi-game 2 | 3 | A ClojureScript library designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2014 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /code/chapter06/reagi-game/dev-resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bREPL Connection 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /code/chapter06/reagi-game/dev-resources/tools/http/ring/server.clj: -------------------------------------------------------------------------------- 1 | ;;; This namespace is used for development and testing purpose only. 2 | (ns ring.server 3 | (:require [cemerick.austin.repls :refer (browser-connected-repl-js)] 4 | [net.cgrand.enlive-html :as enlive] 5 | [compojure.route :refer (resources)] 6 | [compojure.core :refer (GET defroutes)] 7 | [ring.adapter.jetty :as jetty] 8 | [clojure.java.io :as io])) 9 | 10 | ;;; We use enlive lib to add to the body of the index.html page the 11 | ;;; script tag containing the JS code which activates the bREPL 12 | ;;; connection. 13 | (enlive/deftemplate page 14 | (io/resource "public/index.html") 15 | [] 16 | [:body] (enlive/append 17 | (enlive/html [:script (browser-connected-repl-js)]))) 18 | 19 | ;;; defroutes macro defines a function that chains individual route 20 | ;;; functions together. The request map is passed to each function in 21 | ;;; turn, until a non-nil response is returned. 22 | (defroutes site 23 | (resources "/") 24 | (GET "/*" req (page))) 25 | 26 | ;;; To run the jetty server. The server symbol is not private to 27 | ;;; allows to start and stop thejetty server from the repl. 28 | (defn run 29 | "Run the ring server. It defines the server symbol with defonce." 30 | [] 31 | (defonce server 32 | (jetty/run-jetty #'site {:port 3000 :join? false})) 33 | server) 34 | -------------------------------------------------------------------------------- /code/chapter06/reagi-game/dev-resources/tools/repl/brepl/connect.cljs: -------------------------------------------------------------------------------- 1 | ;;; This namespace is for creating the connection with the browser. It 2 | ;;; lives in the dev-resources/tools/brepl directory. It is used in the 3 | ;;; :dev profile only. 4 | (ns brepl.connect 5 | (:require [clojure.browser.repl])) 6 | -------------------------------------------------------------------------------- /code/chapter06/reagi-game/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to reagi-game 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/chapter06/reagi-game/profiles.clj: -------------------------------------------------------------------------------- 1 | ;;; ****************************** NOTES ****************************** 2 | ;;; Defines four profiles: 3 | ;;; 4 | ;;; - :shared 5 | ;;; - :dev 6 | ;;; - :simple 7 | ;;; - :advanced 8 | ;;; 9 | ;;; the :dev, :simple and :advanced profiles are composite profiles, 10 | ;;; meaning that they share the content of :shared profile. 11 | ;;; ******************************************************************* 12 | 13 | {:shared {:clean-targets ["out" :target-path] 14 | :test-paths ["test/clj" "test/cljs"] 15 | :resources-paths ["dev-resources"] 16 | :plugins [[com.cemerick/clojurescript.test "0.2.1"]] 17 | :cljsbuild 18 | {:builds {:reagi-game 19 | {:source-paths ["test/cljs"] 20 | :compiler 21 | {:output-dir "dev-resources/public/js" 22 | :source-map "dev-resources/public/js/reagi_game.js.map"}}} 23 | :test-commands {"phantomjs" 24 | ["phantomjs" :runner "dev-resources/public/js/reagi_game.js"]}}} 25 | :dev [:shared 26 | {:source-paths ["dev-resources/tools/http" "dev-resources/tools/repl"] 27 | :dependencies [[ring "1.2.1"] 28 | [compojure "1.1.6"] 29 | [enlive "1.1.4"]] 30 | :plugins [[com.cemerick/austin "0.1.3"]] 31 | :cljsbuild 32 | {:builds {:reagi-game 33 | {:source-paths ["dev-resources/tools/repl"] 34 | :compiler 35 | {:optimizations :whitespace 36 | :pretty-print true}}}} 37 | 38 | :injections [(require '[ring.server :as http :refer [run]] 39 | 'cemerick.austin.repls) 40 | (defn browser-repl [] 41 | (cemerick.austin.repls/cljs-repl (reset! cemerick.austin.repls/browser-repl-env 42 | (cemerick.austin/repl-env))))]}] 43 | ;; simple profile. 44 | :simple [:shared 45 | {:cljsbuild 46 | {:builds {:reagi-game 47 | {:compiler {:optimizations :simple 48 | :pretty-print false}}}}}] 49 | ;; advanced profile 50 | :advanced [:shared 51 | {:cljsbuild 52 | {:builds {:reagi-game 53 | {:source-paths ["test/cljs"] 54 | :compiler 55 | {:optimizations :advanced 56 | :pretty-print false}}}}}]} 57 | 58 | -------------------------------------------------------------------------------- /code/chapter06/reagi-game/project.clj: -------------------------------------------------------------------------------- 1 | (defproject reagi-game "0.0.1-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License - v 1.0" 5 | :url "http://www.eclipse.org/legal/epl-v10.html" 6 | :distribution :repo} 7 | 8 | :min-lein-version "2.3.4" 9 | 10 | ;; We need to add src/cljs too, because cljsbuild does not add its 11 | ;; source-paths to the project source-paths 12 | :source-paths ["src/clj" "src/cljs"] 13 | 14 | :dependencies [[org.clojure/clojure "1.5.1"] 15 | [org.clojure/clojurescript "0.0-2138"] 16 | [rm-hull/monet "0.1.12"] 17 | [reagi "0.10.0"]] 18 | 19 | :plugins [[lein-cljsbuild "1.0.1"]] 20 | 21 | :hooks [leiningen.cljsbuild] 22 | 23 | :cljsbuild 24 | {:builds {;; This build is only used for including any cljs source 25 | ;; in the packaged jar when you issue lein jar command and 26 | ;; any other command that depends on it 27 | :reagi-game 28 | {:source-paths ["src/cljs"] 29 | ;; The :jar true option is not needed to include the CLJS 30 | ;; sources in the packaged jar. This is because we added 31 | ;; the CLJS source codebase to the Leiningen 32 | ;; :source-paths 33 | ;:jar true 34 | ;; Compilation Options 35 | :compiler 36 | {:output-to "dev-resources/public/js/reagi_game.js" 37 | :optimizations :whitespace 38 | :pretty-print false}}}}) 39 | -------------------------------------------------------------------------------- /code/chapter06/reagi-game/src/cljs/reagi_game/core.cljs: -------------------------------------------------------------------------------- 1 | (ns reagi-game.core 2 | (:require [monet.canvas :as canvas] 3 | [reagi.core :as r] 4 | [clojure.set :as set] 5 | [reagi-game.entities :as entities 6 | :refer [move-forward! move-backward! rotate-left! rotate-right! fire!]])) 7 | 8 | (def canvas-dom (.getElementById js/document "canvas")) 9 | 10 | (def monet-canvas (canvas/init canvas-dom "2d")) 11 | 12 | (def ship (entities/shape-data (/ (.-width (:canvas monet-canvas)) 2) 13 | (/ (.-height (:canvas monet-canvas)) 2) 14 | 0)) 15 | 16 | (def ship-entity (entities/ship-entity ship)) 17 | 18 | (canvas/add-entity monet-canvas :ship-entity ship-entity) 19 | (canvas/draw-loop monet-canvas) 20 | 21 | (def UP 38) 22 | (def RIGHT 39) 23 | (def DOWN 40) 24 | (def LEFT 37) 25 | (def FIRE 32) ;; space 26 | (def PAUSE 80) ;; lower-case P 27 | 28 | (defn keydown-stream [] 29 | (let [out (r/events)] 30 | (set! (.-onkeydown js/document) #(r/deliver out [::down (.-keyCode %)])) 31 | out)) 32 | 33 | (defn keyup-stream [] 34 | (let [out (r/events)] 35 | (set! (.-onkeyup js/document) #(r/deliver out [::up (.-keyCode %)])) 36 | out)) 37 | 38 | (def active-keys-stream 39 | (->> (r/merge (keydown-stream) (keyup-stream)) 40 | (r/reduce (fn [acc [event-type key-code]] 41 | (condp = event-type 42 | ::down (conj acc key-code) 43 | ::up (disj acc key-code) 44 | acc)) 45 | #{}) 46 | (r/sample 25))) 47 | 48 | (defn filter-map [pred f & args] 49 | (->> active-keys-stream 50 | (r/filter (partial some pred)) 51 | (r/map (fn [_] (apply f args))))) 52 | 53 | (filter-map #{FIRE} fire! monet-canvas ship) 54 | (filter-map #{UP} move-forward! ship) 55 | (filter-map #{DOWN} move-backward! ship) 56 | (filter-map #{RIGHT} rotate-right! ship) 57 | (filter-map #{LEFT} rotate-left! ship) 58 | 59 | (defn pause! [_] 60 | (if @(:updating? monet-canvas) 61 | (canvas/stop-updating monet-canvas) 62 | (canvas/start-updating monet-canvas))) 63 | 64 | (->> active-keys-stream 65 | (r/filter (partial some #{PAUSE})) 66 | (r/throttle 100) 67 | (r/map pause!)) 68 | -------------------------------------------------------------------------------- /code/chapter06/reagi-game/src/cljs/reagi_game/entities.cljs: -------------------------------------------------------------------------------- 1 | (ns reagi-game.entities 2 | (:require [monet.canvas :as canvas] 3 | [monet.geometry :as geom])) 4 | 5 | (defn shape-x [shape] 6 | (-> shape :pos deref :x)) 7 | 8 | (defn shape-y [shape] 9 | (-> shape :pos deref :y)) 10 | 11 | (defn shape-angle [shape] 12 | @(:angle shape)) 13 | 14 | (defn shape-data [x y angle] 15 | {:pos (atom {:x x :y y}) 16 | :angle (atom angle)}) 17 | 18 | (defn ship-entity [ship] 19 | (canvas/entity {:x (shape-x ship) :y (shape-y ship) :angle (shape-angle ship)} 20 | (fn [value] 21 | (-> value 22 | (assoc :x (shape-x ship)) 23 | (assoc :y (shape-y ship)) 24 | (assoc :angle (shape-angle ship)))) 25 | (fn [ctx val] 26 | (-> ctx 27 | canvas/save 28 | (canvas/translate (:x val) (:y val)) 29 | (canvas/rotate (:angle val)) 30 | (canvas/begin-path) 31 | (canvas/move-to 50 0) 32 | (canvas/line-to 0 -15) 33 | (canvas/line-to 0 15) 34 | (canvas/fill) 35 | canvas/restore)))) 36 | 37 | (declare move-forward!) 38 | 39 | (defn make-bullet-entity [monet-canvas key shape] 40 | (canvas/entity {:x (shape-x shape) :y (shape-y shape) :angle (shape-angle shape)} 41 | (fn [value] 42 | (when (not 43 | (geom/contained? 44 | {:x 0 :y 0 45 | :w (.-width (:canvas monet-canvas)) 46 | :h (.-height (:canvas monet-canvas))} 47 | {:x (shape-x shape) :y (shape-y shape) :r 5})) 48 | (canvas/remove-entity monet-canvas key)) 49 | (move-forward! shape) 50 | (-> value 51 | (assoc :x (shape-x shape)) 52 | (assoc :y (shape-y shape)) 53 | (assoc :angle (shape-angle shape)))) 54 | (fn [ctx val] 55 | (-> ctx 56 | canvas/save 57 | (canvas/translate (:x val) (:y val)) 58 | (canvas/rotate (:angle val)) 59 | (canvas/fill-style "red") 60 | (canvas/circle {:x 10 :y 0 :r 5}) 61 | canvas/restore)))) 62 | 63 | (def speed 200) 64 | 65 | ;;x' = x cos f - y sin f 66 | 67 | (defn calculate-x [angle] 68 | (* speed (/ (* (Math/cos angle) 69 | Math/PI) 70 | 180))) 71 | 72 | (defn calculate-y [angle] 73 | (* speed (/ (* (Math/sin angle) 74 | Math/PI) 75 | 180))) 76 | 77 | (defn move! [shape f] 78 | (let [pos (:pos shape)] 79 | (swap! pos (fn [xy] 80 | (-> xy 81 | (update-in [:x] 82 | #(f % (calculate-x 83 | (shape-angle shape)))) 84 | (update-in [:y] 85 | #(f % (calculate-y 86 | (shape-angle shape))))))))) 87 | 88 | 89 | (defn move-forward! [shape] 90 | (move! shape +)) 91 | 92 | (defn move-backward! [shape] 93 | (move! shape -)) 94 | 95 | (defn rotate! [shape f] 96 | (swap! (:angle shape) #(f % (/ (/ Math/PI 3) 20)))) 97 | 98 | (defn rotate-right! [shape] 99 | (rotate! shape +)) 100 | 101 | (defn rotate-left! [shape] 102 | (rotate! shape -)) 103 | 104 | (defn fire! [monet-canvas ship] 105 | (let [entity-key (keyword (gensym "bullet")) 106 | data (shape-data (shape-x ship) 107 | (shape-y ship) 108 | (shape-angle ship)) 109 | bullet (make-bullet-entity monet-canvas 110 | entity-key 111 | data)] 112 | (canvas/add-entity monet-canvas entity-key bullet))) 113 | -------------------------------------------------------------------------------- /code/chapter06/reagi-game/src/cljs/reagi_game/util.cljs: -------------------------------------------------------------------------------- 1 | (ns reagi-game.util) 2 | 3 | (defn flip [f] 4 | (comp (partial apply f) reverse list)) 5 | -------------------------------------------------------------------------------- /code/chapter06/reagi-game/test/cljs/reagi_game/core_test.cljs: -------------------------------------------------------------------------------- 1 | ;;; This namespace is used for testing purpose. It use the 2 | ;;; clojurescript.test lib. 3 | (ns reagi-game.core-test 4 | (:require-macros [cemerick.cljs.test :as m :refer (deftest testing are)]) 5 | (:require [cemerick.cljs.test :as t] 6 | [reagi-game.core :refer (foo)])) 7 | 8 | (deftest foo-test 9 | (testing "I don't do a lot\n" 10 | (testing "Edge cases\n" 11 | (testing "(foo str)" 12 | (are [expected actual] (= expected actual) 13 | "ClojureScript!" (foo "") 14 | "Hello, ClojureScript!" (foo nil)))))) 15 | -------------------------------------------------------------------------------- /code/chapter07/contacts/.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | /lib/ 4 | /classes/ 5 | /out/ 6 | /target/ 7 | .lein-deps-sum 8 | .lein-repl-history 9 | .lein-plugins/ 10 | /dev-resources/public/js/* 11 | -------------------------------------------------------------------------------- /code/chapter07/contacts/.nrepl-port: -------------------------------------------------------------------------------- 1 | 62297 -------------------------------------------------------------------------------- /code/chapter07/contacts/README.md: -------------------------------------------------------------------------------- 1 | # contacts 2 | 3 | An OM project designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2014 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | 16 | 17 | ## Book notes 18 | 19 | - update Om version 20 | - update React version 21 | - update CLJS version 22 | - update lein cljsbuild 23 | -------------------------------------------------------------------------------- /code/chapter07/contacts/dev-resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /code/chapter07/contacts/dev-resources/tools/http/ring/server.clj: -------------------------------------------------------------------------------- 1 | ;;; This namespace is used for development and testing purpose only. 2 | (ns ring.server 3 | (:require [cemerick.austin.repls :refer (browser-connected-repl-js)] 4 | [compojure.route :refer (resources)] 5 | [compojure.core :refer (GET defroutes)] 6 | [ring.adapter.jetty :as jetty] 7 | [clojure.java.io :as io] 8 | [clojure.zip :as zip] 9 | [hickory.core :as hc] 10 | [hickory.render :as hr] 11 | [hickory.select :as hs] 12 | [hickory.zip :as hz])) 13 | 14 | (defn repl-js-script [] 15 | (-> (str "") 16 | hc/parse-fragment 17 | first 18 | hc/as-hickory)) 19 | 20 | (defn original-page [] 21 | (-> "public/index.html" 22 | io/resource 23 | slurp 24 | hc/parse 25 | hc/as-hickory)) 26 | 27 | (defn page [] 28 | (->> (original-page) 29 | hz/hickory-zip 30 | (hs/select-next-loc (hs/tag :body)) 31 | (#(zip/append-child % (repl-js-script))) 32 | zip/root 33 | hr/hickory-to-html)) 34 | 35 | (defroutes site 36 | (resources "/") 37 | (GET "/*" req (page))) 38 | 39 | (defn run 40 | "Run the ring server. It defines the server symbol with defonce." 41 | [] 42 | (defonce server 43 | (jetty/run-jetty #'site {:port 3000 :join? false})) 44 | server) 45 | -------------------------------------------------------------------------------- /code/chapter07/contacts/dev-resources/tools/repl/brepl/connect.cljs: -------------------------------------------------------------------------------- 1 | (ns brepl.connect 2 | (:require [clojure.browser.repl])) 3 | -------------------------------------------------------------------------------- /code/chapter07/contacts/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to contacts 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/chapter07/contacts/profiles.clj: -------------------------------------------------------------------------------- 1 | {:shared {:clean-targets ["out" :target-path]} 2 | 3 | :tdd [:shared 4 | {:cljsbuild 5 | {:builds {:contacts 6 | {:compiler 7 | {:optimizations :whitespace 8 | :pretty-print true}}}}}] 9 | 10 | :dev [:shared 11 | {:resources-paths ["dev-resources"] 12 | :source-paths ["dev-resources/tools/http" "dev-resources/tools/repl"] 13 | :dependencies [[ring "1.2.1"] 14 | [compojure "1.1.6"] 15 | [hickory "0.5.3"]] 16 | :plugins [[com.cemerick/austin "0.1.4"]] 17 | :cljsbuild 18 | {:builds {:contacts 19 | {:source-paths ["dev-resources/tools/repl"] 20 | :compiler 21 | {:optimizations :whitespace 22 | :pretty-print true}}}} 23 | 24 | :injections [(require '[ring.server :as http :refer [run]] 25 | 'cemerick.austin.repls) 26 | (defn browser-repl-env [] 27 | (reset! cemerick.austin.repls/browser-repl-env 28 | (cemerick.austin/repl-env))) 29 | (defn browser-repl [] 30 | (cemerick.austin.repls/cljs-repl 31 | (browser-repl-env)))]}]} 32 | -------------------------------------------------------------------------------- /code/chapter07/contacts/project.clj: -------------------------------------------------------------------------------- 1 | (defproject contacts "0.0.1-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License - v 1.0" 5 | :url "http://www.eclipse.org/legal/epl-v10.html" 6 | :distribution :repo} 7 | 8 | :min-lein-version "2.3.4" 9 | 10 | :source-paths ["src/clj" "src/cljs"] 11 | 12 | :dependencies [[org.clojure/clojure "1.6.0"] 13 | [org.clojure/clojurescript "0.0-2277"] 14 | [org.clojure/core.async "0.1.338.0-5c5012-alpha"] 15 | [om "0.7.1"] 16 | [com.facebook/react "0.11.1"]] 17 | 18 | :plugins [[lein-cljsbuild "1.0.3"]] 19 | 20 | :hooks [leiningen.cljsbuild] 21 | 22 | :cljsbuild 23 | {:builds {:contacts 24 | {:source-paths ["src/cljs"] 25 | :compiler 26 | {:output-to "dev-resources/public/js/contacts.js" 27 | :optimizations :advanced 28 | :pretty-print false}}}}) 29 | -------------------------------------------------------------------------------- /code/chapter07/contacts/src/cljs/contacts/core.cljs: -------------------------------------------------------------------------------- 1 | (ns contacts.core 2 | (:require [om.core :as om :include-macros true] 3 | [om.dom :as dom :include-macros true])) 4 | 5 | (defn update-contact! [e contact key] 6 | (om/update! contact key (.. e -target -value))) 7 | 8 | (defn contact-details-form-view [{{:keys [name phone email id] :as contact} :contact 9 | editing :editing-cursor} 10 | owner] 11 | (reify 12 | om/IRender 13 | (render [_] 14 | (dom/div #js {:style #js {:display (if (get editing 0) "" "none")}} 15 | (dom/h2 nil "Contact details") 16 | (if contact 17 | (dom/div nil 18 | (dom/input #js {:type "text" 19 | :value name 20 | :onChange #(update-contact! % contact :name)}) 21 | (dom/input #js {:type "text" 22 | :value phone 23 | :onChange #(update-contact! % contact :phone)}) 24 | (dom/input #js {:type "text" 25 | :value email 26 | :onChange #(update-contact! % contact :email)}) 27 | (dom/button #js {:onClick #(om/update! editing 0 false)} 28 | "Save")) 29 | (dom/div nil "No contact selected")))))) 30 | 31 | 32 | (defn contact-details-view [{{:keys [name phone email id] :as contact} :contact 33 | editing :editing-cursor} 34 | owner] 35 | (reify 36 | om/IRender 37 | (render [_] 38 | (dom/div #js {:style #js {:display (if (get editing 0) "none" "")}} 39 | (dom/h2 nil "Contact details") 40 | (if contact 41 | (dom/div nil 42 | (dom/h3 #js {:style #js {:margin-bottom "0px"}} (:name contact)) 43 | (dom/span nil (:phone contact)) (dom/br nil) 44 | (dom/span nil (:email contact)) (dom/br nil) 45 | (dom/button #js {:onClick #(om/update! editing 0 true)} 46 | "Edit")) 47 | (dom/span nil "No contact selected")))))) 48 | 49 | (defn details-panel-view [data owner] 50 | (reify 51 | om/IRender 52 | (render [_] 53 | (dom/div #js {:style #js {:float "right" 54 | :width "50%"}} 55 | (om/build contact-details-view data) 56 | (om/build contact-details-form-view data))))) 57 | 58 | (defn select-contact! [contact selected-id-cursor] 59 | (om/update! selected-id-cursor 0 (:id contact))) 60 | 61 | (defn contact-summary-view [{:keys [name phone] :as contact} owner] 62 | (reify 63 | om/IRender 64 | (render [_] 65 | (dom/li #js {:onClick #(select-contact! @contact 66 | (om/get-shared owner :selected-id-cursor))} 67 | (dom/span nil name) 68 | (dom/span nil phone))))) 69 | 70 | (defn contacts-view [{:keys [contacts selected-id-cursor]} owner] 71 | (reify 72 | om/IRender 73 | (render [_] 74 | (dom/div #js {:style #js {:float "left" 75 | :width "50%"}} 76 | (apply dom/ul nil 77 | (om/build-all contact-summary-view (vals contacts) 78 | {:shared {:selected-id-cursor selected-id-cursor}})))))) 79 | 80 | (defn contacts-app [data owner] 81 | (reify 82 | om/IRender 83 | (render [_] 84 | (let [[selected-id :as selected-id-cursor] (:selected-contact-id data)] 85 | (dom/div nil 86 | (om/build contacts-view 87 | {:contacts (:contacts data) 88 | :selected-id-cursor selected-id-cursor}) 89 | (om/build details-panel-view 90 | {:contact (get-in data [:contacts selected-id]) 91 | :editing-cursor (:editing data)})))))) 92 | 93 | (def app-state 94 | (atom {:contacts {1 {:id 1 95 | :name "James Hetfield" 96 | :email "james@metallica.com" 97 | :phone "+1 XXX XXX XXX"} 98 | 2 {:id 2 99 | :name "Adam Darski" 100 | :email "the.nergal@behemoth.pl" 101 | :phone "+48 XXX XXX XXX"}} 102 | :selected-contact-id [] 103 | :editing [false]})) 104 | 105 | (om/root 106 | contacts-app 107 | app-state 108 | {:target (. js/document (getElementById "app"))}) 109 | -------------------------------------------------------------------------------- /code/chapter07/om-pm/.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | /lib/ 4 | /classes/ 5 | /out/ 6 | /target/ 7 | .lein-deps-sum 8 | .lein-repl-history 9 | .lein-plugins/ 10 | /dev-resources/public/js/* 11 | -------------------------------------------------------------------------------- /code/chapter07/om-pm/.repl/76/clojure/browser/event.cljs: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Rich Hickey. All rights reserved. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^{:doc "This namespace contains functions to work with browser 10 | events. It is based on the Google Closure Library event system." 11 | :author "Bobby Calderwood"} 12 | clojure.browser.event 13 | (:require [goog.events :as events] 14 | [goog.events.EventTarget :as gevent-target] 15 | [goog.events.EventType :as gevent-type])) 16 | 17 | (defprotocol EventType 18 | (event-types [this])) 19 | 20 | (extend-protocol EventType 21 | 22 | goog.events.EventTarget 23 | (event-types 24 | [this] 25 | (into {} 26 | (map 27 | (fn [[k v]] 28 | [(keyword (. k (toLowerCase))) 29 | v]) 30 | (merge 31 | (js->clj goog.events.EventType))))) 32 | 33 | js/Element 34 | (event-types 35 | [this] 36 | (into {} 37 | (map 38 | (fn [[k v]] 39 | [(keyword (. k (toLowerCase))) 40 | v]) 41 | (merge 42 | (js->clj goog.events.EventType)))))) 43 | 44 | (defn listen 45 | ([src type fn] 46 | (listen src type fn false)) 47 | ([src type fn capture?] 48 | (goog.events/listen src 49 | (get (event-types src) type type) 50 | fn 51 | capture?))) 52 | 53 | (defn listen-once 54 | ([src type fn] 55 | (listen-once src type fn false)) 56 | ([src type fn capture?] 57 | (goog.events/listenOnce src 58 | (get (event-types src) type type) 59 | fn 60 | capture?))) 61 | 62 | (defn unlisten 63 | ([src type fn] 64 | (unlisten src type fn false)) 65 | ([src type fn capture?] 66 | (goog.events/unlisten src 67 | (get (event-types src) type type) 68 | fn 69 | capture?))) 70 | 71 | (defn unlisten-by-key 72 | [key] 73 | (goog.events/unlistenByKey key)) 74 | 75 | (defn dispatch-event 76 | [src event] 77 | (goog.events/dispatchEvent src event)) 78 | 79 | (defn expose [e] 80 | (goog.events/expose e)) 81 | 82 | (defn fire-listeners 83 | [obj type capture event]) 84 | 85 | (defn total-listener-count [] 86 | (goog.events/getTotalListenerCount)) 87 | 88 | ;; TODO 89 | (defn get-listener [src type listener opt_capt opt_handler]); ⇒ ?Listener 90 | (defn all-listeners [obj type capture]); ⇒ Array. 91 | 92 | (defn unique-event-id [event-type]); ⇒ string 93 | 94 | (defn has-listener [obj opt_type opt_capture]); ⇒ boolean 95 | ;; TODO? (defn listen-with-wrapper [src wrapper listener opt_capt opt_handler]) 96 | ;; TODO? (defn protect-browser-event-entry-point [errorHandler]) 97 | 98 | (defn remove-all [opt_obj opt_type opt_capt]); ⇒ number 99 | ;; TODO? (defn unlisten-with-wrapper [src wrapper listener opt_capt opt_handler]) 100 | 101 | -------------------------------------------------------------------------------- /code/chapter07/om-pm/.repl/76/clojure/browser/event.js: -------------------------------------------------------------------------------- 1 | // Compiled by ClojureScript 0.0-2156 2 | goog.provide('clojure.browser.event'); 3 | goog.require('cljs.core'); 4 | goog.require('goog.events.EventType'); 5 | goog.require('goog.events.EventType'); 6 | goog.require('goog.events.EventTarget'); 7 | goog.require('goog.events.EventTarget'); 8 | goog.require('goog.events'); 9 | goog.require('goog.events'); 10 | clojure.browser.event.EventType = (function (){var obj10891 = {};return obj10891; 11 | })(); 12 | clojure.browser.event.event_types = (function event_types(this$){if((function (){var and__6515__auto__ = this$;if(and__6515__auto__) 13 | {return this$.clojure$browser$event$EventType$event_types$arity$1; 14 | } else 15 | {return and__6515__auto__; 16 | } 17 | })()) 18 | {return this$.clojure$browser$event$EventType$event_types$arity$1(this$); 19 | } else 20 | {var x__7154__auto__ = (((this$ == null))?null:this$);return (function (){var or__6527__auto__ = (clojure.browser.event.event_types[goog.typeOf(x__7154__auto__)]);if(or__6527__auto__) 21 | {return or__6527__auto__; 22 | } else 23 | {var or__6527__auto____$1 = (clojure.browser.event.event_types["_"]);if(or__6527__auto____$1) 24 | {return or__6527__auto____$1; 25 | } else 26 | {throw cljs.core.missing_protocol.call(null,"EventType.event-types",this$); 27 | } 28 | } 29 | })().call(null,this$); 30 | } 31 | }); 32 | Element.prototype.clojure$browser$event$EventType$ = true; 33 | Element.prototype.clojure$browser$event$EventType$event_types$arity$1 = (function (this$){var this$__$1 = this;return cljs.core.into.call(null,cljs.core.PersistentArrayMap.EMPTY,cljs.core.map.call(null,(function (p__10892){var vec__10893 = p__10892;var k = cljs.core.nth.call(null,vec__10893,0,null);var v = cljs.core.nth.call(null,vec__10893,1,null);return new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [cljs.core.keyword.call(null,k.toLowerCase()),v], null); 34 | }),cljs.core.merge.call(null,cljs.core.js__GT_clj.call(null,goog.events.EventType)))); 35 | }); 36 | goog.events.EventTarget.prototype.clojure$browser$event$EventType$ = true; 37 | goog.events.EventTarget.prototype.clojure$browser$event$EventType$event_types$arity$1 = (function (this$){var this$__$1 = this;return cljs.core.into.call(null,cljs.core.PersistentArrayMap.EMPTY,cljs.core.map.call(null,(function (p__10894){var vec__10895 = p__10894;var k = cljs.core.nth.call(null,vec__10895,0,null);var v = cljs.core.nth.call(null,vec__10895,1,null);return new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [cljs.core.keyword.call(null,k.toLowerCase()),v], null); 38 | }),cljs.core.merge.call(null,cljs.core.js__GT_clj.call(null,goog.events.EventType)))); 39 | }); 40 | clojure.browser.event.listen = (function() { 41 | var listen = null; 42 | var listen__3 = (function (src,type,fn){return listen.call(null,src,type,fn,false); 43 | }); 44 | var listen__4 = (function (src,type,fn,capture_QMARK_){return goog.events.listen(src,cljs.core.get.call(null,clojure.browser.event.event_types.call(null,src),type,type),fn,capture_QMARK_); 45 | }); 46 | listen = function(src,type,fn,capture_QMARK_){ 47 | switch(arguments.length){ 48 | case 3: 49 | return listen__3.call(this,src,type,fn); 50 | case 4: 51 | return listen__4.call(this,src,type,fn,capture_QMARK_); 52 | } 53 | throw(new Error('Invalid arity: ' + arguments.length)); 54 | }; 55 | listen.cljs$core$IFn$_invoke$arity$3 = listen__3; 56 | listen.cljs$core$IFn$_invoke$arity$4 = listen__4; 57 | return listen; 58 | })() 59 | ; 60 | clojure.browser.event.listen_once = (function() { 61 | var listen_once = null; 62 | var listen_once__3 = (function (src,type,fn){return listen_once.call(null,src,type,fn,false); 63 | }); 64 | var listen_once__4 = (function (src,type,fn,capture_QMARK_){return goog.events.listenOnce(src,cljs.core.get.call(null,clojure.browser.event.event_types.call(null,src),type,type),fn,capture_QMARK_); 65 | }); 66 | listen_once = function(src,type,fn,capture_QMARK_){ 67 | switch(arguments.length){ 68 | case 3: 69 | return listen_once__3.call(this,src,type,fn); 70 | case 4: 71 | return listen_once__4.call(this,src,type,fn,capture_QMARK_); 72 | } 73 | throw(new Error('Invalid arity: ' + arguments.length)); 74 | }; 75 | listen_once.cljs$core$IFn$_invoke$arity$3 = listen_once__3; 76 | listen_once.cljs$core$IFn$_invoke$arity$4 = listen_once__4; 77 | return listen_once; 78 | })() 79 | ; 80 | clojure.browser.event.unlisten = (function() { 81 | var unlisten = null; 82 | var unlisten__3 = (function (src,type,fn){return unlisten.call(null,src,type,fn,false); 83 | }); 84 | var unlisten__4 = (function (src,type,fn,capture_QMARK_){return goog.events.unlisten(src,cljs.core.get.call(null,clojure.browser.event.event_types.call(null,src),type,type),fn,capture_QMARK_); 85 | }); 86 | unlisten = function(src,type,fn,capture_QMARK_){ 87 | switch(arguments.length){ 88 | case 3: 89 | return unlisten__3.call(this,src,type,fn); 90 | case 4: 91 | return unlisten__4.call(this,src,type,fn,capture_QMARK_); 92 | } 93 | throw(new Error('Invalid arity: ' + arguments.length)); 94 | }; 95 | unlisten.cljs$core$IFn$_invoke$arity$3 = unlisten__3; 96 | unlisten.cljs$core$IFn$_invoke$arity$4 = unlisten__4; 97 | return unlisten; 98 | })() 99 | ; 100 | clojure.browser.event.unlisten_by_key = (function unlisten_by_key(key){return goog.events.unlistenByKey(key); 101 | }); 102 | clojure.browser.event.dispatch_event = (function dispatch_event(src,event){return goog.events.dispatchEvent(src,event); 103 | }); 104 | clojure.browser.event.expose = (function expose(e){return goog.events.expose(e); 105 | }); 106 | clojure.browser.event.fire_listeners = (function fire_listeners(obj,type,capture,event){return null; 107 | }); 108 | clojure.browser.event.total_listener_count = (function total_listener_count(){return goog.events.getTotalListenerCount(); 109 | }); 110 | clojure.browser.event.get_listener = (function get_listener(src,type,listener,opt_capt,opt_handler){return null; 111 | }); 112 | clojure.browser.event.all_listeners = (function all_listeners(obj,type,capture){return null; 113 | }); 114 | clojure.browser.event.unique_event_id = (function unique_event_id(event_type){return null; 115 | }); 116 | clojure.browser.event.has_listener = (function has_listener(obj,opt_type,opt_capture){return null; 117 | }); 118 | clojure.browser.event.remove_all = (function remove_all(opt_obj,opt_type,opt_capt){return null; 119 | }); 120 | -------------------------------------------------------------------------------- /code/chapter07/om-pm/.repl/76/clojure/browser/net.cljs: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Rich Hickey. All rights reserved. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^{:doc "Network communication library, wrapping goog.net. 10 | Includes a common API over XhrIo, CrossPageChannel, and Websockets." 11 | :author "Bobby Calderwood and Alex Redington"} 12 | clojure.browser.net 13 | (:require [clojure.browser.event :as event] 14 | [goog.net.XhrIo :as gxhrio] 15 | [goog.net.EventType :as gnet-event-type] 16 | [goog.net.xpc.CfgFields :as gxpc-config-fields] 17 | [goog.net.xpc.CrossPageChannel :as xpc] 18 | #_[goog.net.WebSocket :as gwebsocket] 19 | [goog.json :as gjson])) 20 | 21 | (def *timeout* 10000) 22 | 23 | (def event-types 24 | (into {} 25 | (map 26 | (fn [[k v]] 27 | [(keyword (. k (toLowerCase))) 28 | v]) 29 | (merge 30 | (js->clj goog.net.EventType))))) 31 | 32 | (defprotocol IConnection 33 | (connect 34 | [this] 35 | [this opt1] 36 | [this opt1 opt2] 37 | [this opt1 opt2 opt3]) 38 | (transmit 39 | [this opt] 40 | [this opt opt2] 41 | [this opt opt2 opt3] 42 | [this opt opt2 opt3 opt4] 43 | [this opt opt2 opt3 opt4 opt5]) 44 | (close [this])) 45 | 46 | (extend-type goog.net.XhrIo 47 | 48 | IConnection 49 | (transmit 50 | ([this uri] 51 | (transmit this uri "GET" nil nil *timeout*)) 52 | ([this uri method] 53 | (transmit this uri method nil nil *timeout*)) 54 | ([this uri method content] 55 | (transmit this uri method content nil *timeout*)) 56 | ([this uri method content headers] 57 | (transmit this uri method content headers *timeout*)) 58 | ([this uri method content headers timeout] 59 | (.setTimeoutInterval this timeout) 60 | (.send this uri method content headers))) 61 | 62 | 63 | event/EventType 64 | (event-types [this] 65 | (into {} 66 | (map 67 | (fn [[k v]] 68 | [(keyword (. k (toLowerCase))) 69 | v]) 70 | (merge 71 | (js->clj goog.net.EventType)))))) 72 | 73 | ;; TODO jQuery/sinatra/RestClient style API: (get [uri]), (post [uri payload]), (put [uri payload]), (delete [uri]) 74 | 75 | (def xpc-config-fields 76 | (into {} 77 | (map 78 | (fn [[k v]] 79 | [(keyword (. k (toLowerCase))) 80 | v]) 81 | (js->clj goog.net.xpc.CfgFields)))) 82 | 83 | (defn xhr-connection 84 | "Returns an XhrIo connection" 85 | [] 86 | (goog.net.XhrIo.)) 87 | 88 | (defprotocol ICrossPageChannel 89 | (register-service [this service-name fn] [this service-name fn encode-json?])) 90 | 91 | (extend-type goog.net.xpc.CrossPageChannel 92 | 93 | ICrossPageChannel 94 | (register-service 95 | ([this service-name fn] 96 | (register-service this service-name fn false)) 97 | ([this service-name fn encode-json?] 98 | (.registerService this (name service-name) fn encode-json?))) 99 | 100 | IConnection 101 | (connect 102 | ([this] 103 | (connect this nil)) 104 | ([this on-connect-fn] 105 | (.connect this on-connect-fn)) 106 | ([this on-connect-fn config-iframe-fn] 107 | (connect this on-connect-fn config-iframe-fn (.-body js/document))) 108 | ([this on-connect-fn config-iframe-fn iframe-parent] 109 | (.createPeerIframe this iframe-parent config-iframe-fn) 110 | (.connect this on-connect-fn))) 111 | 112 | (transmit [this service-name payload] 113 | (.send this (name service-name) payload)) 114 | 115 | (close [this] 116 | (.close this ()))) 117 | 118 | (defn xpc-connection 119 | "When passed with a config hash-map, returns a parent 120 | CrossPageChannel object. Keys in the config hash map are downcased 121 | versions of the goog.net.xpc.CfgFields enum keys, 122 | e.g. goog.net.xpc.CfgFields.PEER_URI becomes :peer_uri in the config 123 | hash. 124 | 125 | When passed with no args, creates a child CrossPageChannel object, 126 | and the config is automatically taken from the URL param 'xpc', as 127 | per the CrossPageChannel API." 128 | ([] 129 | (when-let [config (.getParameterValue 130 | (goog.Uri. (.-href (.-location js/window))) 131 | "xpc")] 132 | (goog.net.xpc.CrossPageChannel. (gjson/parse config)))) 133 | ([config] 134 | (goog.net.xpc.CrossPageChannel. 135 | (reduce (fn [sum [k v]] 136 | (if-let [field (get xpc-config-fields k)] 137 | (doto sum (aset field v)) 138 | sum)) 139 | (js-obj) 140 | config)))) 141 | 142 | ;; WebSocket is not supported in the 3/23/11 release of Google 143 | ;; Closure, but will be included in the next release. 144 | 145 | #_(defprotocol IWebSocket 146 | (open? [this])) 147 | 148 | #_(extend-type goog.net.WebSocket 149 | 150 | IWebSocket 151 | (open? [this] 152 | (.isOpen this ())) 153 | 154 | IConnection 155 | (connect 156 | ([this url] 157 | (connect this url nil)) 158 | ([this url protocol] 159 | (.open this url protocol))) 160 | 161 | (transmit [this message] 162 | (.send this message)) 163 | 164 | (close [this] 165 | (.close this ())) 166 | 167 | event/EventType 168 | (event-types [this] 169 | (into {} 170 | (map 171 | (fn [[k v]] 172 | [(keyword (. k (toLowerCase))) 173 | v]) 174 | (merge 175 | (js->clj goog.net.WebSocket/EventType)))))) 176 | 177 | #_(defn websocket-connection 178 | ([] 179 | (websocket-connection nil nil)) 180 | ([auto-reconnect?] 181 | (websocket-connection auto-reconnect? nil)) 182 | ([auto-reconnect? next-reconnect-fn] 183 | (goog.net.WebSocket. auto-reconnect? next-reconnect-fn))) -------------------------------------------------------------------------------- /code/chapter07/om-pm/.repl/76/clojure/browser/repl.cljs: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Rich Hickey. All rights reserved. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^{:doc "Receive - Eval - Print - Loop 10 | 11 | Receive a block of JS (presumably generated by a ClojureScript compiler) 12 | Evaluate it naively 13 | Print the result of evaluation to a string 14 | Send the resulting string back to the server Loop!" 15 | 16 | :author "Bobby Calderwood and Alex Redington"} 17 | clojure.browser.repl 18 | (:require [clojure.browser.net :as net] 19 | [clojure.browser.event :as event])) 20 | 21 | (def xpc-connection (atom nil)) 22 | 23 | (defn repl-print [data] 24 | (if-let [conn @xpc-connection] 25 | (net/transmit conn :print (pr-str data)))) 26 | 27 | (defn evaluate-javascript 28 | "Process a single block of JavaScript received from the server" 29 | [conn block] 30 | (let [result (try {:status :success :value (str (js* "eval(~{block})"))} 31 | (catch js/Error e 32 | {:status :exception :value (pr-str e) 33 | :stacktrace (if (.hasOwnProperty e "stack") 34 | (.-stack e) 35 | "No stacktrace available.")}))] 36 | (pr-str result))) 37 | 38 | (defn send-result [connection url data] 39 | (net/transmit connection url "POST" data nil 0)) 40 | 41 | (defn send-print 42 | "Send data to be printed in the REPL. If there is an error, try again 43 | up to 10 times." 44 | ([url data] 45 | (send-print url data 0)) 46 | ([url data n] 47 | (let [conn (net/xhr-connection)] 48 | (event/listen conn :error 49 | (fn [_] 50 | (if (< n 10) 51 | (send-print url data (inc n)) 52 | (.log js/console (str "Could not send " data " after " n " attempts."))))) 53 | (net/transmit conn url "POST" data nil 0)))) 54 | 55 | (def order (atom 0)) 56 | 57 | (defn wrap-message [t data] 58 | (pr-str {:type t :content data :order (swap! order inc)})) 59 | 60 | (defn start-evaluator 61 | "Start the REPL server connection." 62 | [url] 63 | (if-let [repl-connection (net/xpc-connection)] 64 | (let [connection (net/xhr-connection)] 65 | (event/listen connection 66 | :success 67 | (fn [e] 68 | (net/transmit 69 | repl-connection 70 | :evaluate-javascript 71 | (.getResponseText (.-currentTarget e) 72 | ())))) 73 | 74 | (net/register-service repl-connection 75 | :send-result 76 | (fn [data] 77 | (send-result connection url (wrap-message :result data)))) 78 | 79 | (net/register-service repl-connection 80 | :print 81 | (fn [data] 82 | (send-print url (wrap-message :print data)))) 83 | 84 | (net/connect repl-connection 85 | (constantly nil)) 86 | 87 | (js/setTimeout #(send-result connection url (wrap-message :ready "ready")) 50)) 88 | (js/alert "No 'xpc' param provided to child iframe."))) 89 | 90 | (defn connect 91 | "Connects to a REPL server from an HTML document. After the 92 | connection is made, the REPL will evaluate forms in the context of 93 | the document that called this function." 94 | [repl-server-url] 95 | (let [repl-connection (net/xpc-connection 96 | {:peer_uri repl-server-url})] 97 | (swap! xpc-connection (constantly repl-connection)) 98 | (net/register-service repl-connection 99 | :evaluate-javascript 100 | (fn [js] 101 | (net/transmit 102 | repl-connection 103 | :send-result 104 | (evaluate-javascript repl-connection js)))) 105 | (net/connect repl-connection 106 | (constantly nil) 107 | (fn [iframe] 108 | (set! (.-display (.-style iframe)) 109 | "none"))))) 110 | -------------------------------------------------------------------------------- /code/chapter07/om-pm/.repl/76/clojure/browser/repl.js: -------------------------------------------------------------------------------- 1 | // Compiled by ClojureScript 0.0-2156 2 | goog.provide('clojure.browser.repl'); 3 | goog.require('cljs.core'); 4 | goog.require('clojure.browser.event'); 5 | goog.require('clojure.browser.event'); 6 | goog.require('clojure.browser.net'); 7 | goog.require('clojure.browser.net'); 8 | clojure.browser.repl.xpc_connection = cljs.core.atom.call(null,null); 9 | clojure.browser.repl.repl_print = (function repl_print(data){var temp__4090__auto__ = cljs.core.deref.call(null,clojure.browser.repl.xpc_connection);if(cljs.core.truth_(temp__4090__auto__)) 10 | {var conn = temp__4090__auto__;return clojure.browser.net.transmit.call(null,conn,new cljs.core.Keyword(null,"print","print",1120839199),cljs.core.pr_str.call(null,data)); 11 | } else 12 | {return null; 13 | } 14 | }); 15 | /** 16 | * Process a single block of JavaScript received from the server 17 | */ 18 | clojure.browser.repl.evaluate_javascript = (function evaluate_javascript(conn,block){var result = (function (){try{return new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"status","status",4416389988),new cljs.core.Keyword(null,"success","success",3441701749),new cljs.core.Keyword(null,"value","value",1125876963),[cljs.core.str(eval(block))].join('')], null); 19 | }catch (e10869){if((e10869 instanceof Error)) 20 | {var e = e10869;return new cljs.core.PersistentArrayMap(null, 3, [new cljs.core.Keyword(null,"status","status",4416389988),new cljs.core.Keyword(null,"exception","exception",2495529921),new cljs.core.Keyword(null,"value","value",1125876963),cljs.core.pr_str.call(null,e),new cljs.core.Keyword(null,"stacktrace","stacktrace",3069736751),(cljs.core.truth_(e.hasOwnProperty("stack"))?e.stack:"No stacktrace available.")], null); 21 | } else 22 | {if(new cljs.core.Keyword(null,"else","else",1017020587)) 23 | {throw e10869; 24 | } else 25 | {return null; 26 | } 27 | } 28 | }})();return cljs.core.pr_str.call(null,result); 29 | }); 30 | clojure.browser.repl.send_result = (function send_result(connection,url,data){return clojure.browser.net.transmit.call(null,connection,url,"POST",data,null,0); 31 | }); 32 | /** 33 | * Send data to be printed in the REPL. If there is an error, try again 34 | * up to 10 times. 35 | */ 36 | clojure.browser.repl.send_print = (function() { 37 | var send_print = null; 38 | var send_print__2 = (function (url,data){return send_print.call(null,url,data,0); 39 | }); 40 | var send_print__3 = (function (url,data,n){var conn = clojure.browser.net.xhr_connection.call(null);clojure.browser.event.listen.call(null,conn,new cljs.core.Keyword(null,"error","error",1110689146),(function (_){if((n < 10)) 41 | {return send_print.call(null,url,data,(n + 1)); 42 | } else 43 | {return console.log([cljs.core.str("Could not send "),cljs.core.str(data),cljs.core.str(" after "),cljs.core.str(n),cljs.core.str(" attempts.")].join('')); 44 | } 45 | })); 46 | return clojure.browser.net.transmit.call(null,conn,url,"POST",data,null,0); 47 | }); 48 | send_print = function(url,data,n){ 49 | switch(arguments.length){ 50 | case 2: 51 | return send_print__2.call(this,url,data); 52 | case 3: 53 | return send_print__3.call(this,url,data,n); 54 | } 55 | throw(new Error('Invalid arity: ' + arguments.length)); 56 | }; 57 | send_print.cljs$core$IFn$_invoke$arity$2 = send_print__2; 58 | send_print.cljs$core$IFn$_invoke$arity$3 = send_print__3; 59 | return send_print; 60 | })() 61 | ; 62 | clojure.browser.repl.order = cljs.core.atom.call(null,0); 63 | clojure.browser.repl.wrap_message = (function wrap_message(t,data){return cljs.core.pr_str.call(null,new cljs.core.PersistentArrayMap(null, 3, [new cljs.core.Keyword(null,"type","type",1017479852),t,new cljs.core.Keyword(null,"content","content",1965434859),data,new cljs.core.Keyword(null,"order","order",1119910592),cljs.core.swap_BANG_.call(null,clojure.browser.repl.order,cljs.core.inc)], null)); 64 | }); 65 | /** 66 | * Start the REPL server connection. 67 | */ 68 | clojure.browser.repl.start_evaluator = (function start_evaluator(url){var temp__4090__auto__ = clojure.browser.net.xpc_connection.call(null);if(cljs.core.truth_(temp__4090__auto__)) 69 | {var repl_connection = temp__4090__auto__;var connection = clojure.browser.net.xhr_connection.call(null);clojure.browser.event.listen.call(null,connection,new cljs.core.Keyword(null,"success","success",3441701749),(function (e){return clojure.browser.net.transmit.call(null,repl_connection,new cljs.core.Keyword(null,"evaluate-javascript","evaluate-javascript",2953437843),e.currentTarget.getResponseText(cljs.core.List.EMPTY)); 70 | })); 71 | clojure.browser.net.register_service.call(null,repl_connection,new cljs.core.Keyword(null,"send-result","send-result",3729280372),(function (data){return clojure.browser.repl.send_result.call(null,connection,url,clojure.browser.repl.wrap_message.call(null,new cljs.core.Keyword(null,"result","result",4374444943),data)); 72 | })); 73 | clojure.browser.net.register_service.call(null,repl_connection,new cljs.core.Keyword(null,"print","print",1120839199),(function (data){return clojure.browser.repl.send_print.call(null,url,clojure.browser.repl.wrap_message.call(null,new cljs.core.Keyword(null,"print","print",1120839199),data)); 74 | })); 75 | clojure.browser.net.connect.call(null,repl_connection,cljs.core.constantly.call(null,null)); 76 | return setTimeout((function (){return clojure.browser.repl.send_result.call(null,connection,url,clojure.browser.repl.wrap_message.call(null,new cljs.core.Keyword(null,"ready","ready",1122290965),"ready")); 77 | }),50); 78 | } else 79 | {return alert("No 'xpc' param provided to child iframe."); 80 | } 81 | }); 82 | /** 83 | * Connects to a REPL server from an HTML document. After the 84 | * connection is made, the REPL will evaluate forms in the context of 85 | * the document that called this function. 86 | */ 87 | clojure.browser.repl.connect = (function connect(repl_server_url){var repl_connection = clojure.browser.net.xpc_connection.call(null,new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"peer_uri","peer_uri",1083496577),repl_server_url], null));cljs.core.swap_BANG_.call(null,clojure.browser.repl.xpc_connection,cljs.core.constantly.call(null,repl_connection)); 88 | clojure.browser.net.register_service.call(null,repl_connection,new cljs.core.Keyword(null,"evaluate-javascript","evaluate-javascript",2953437843),(function (js){return clojure.browser.net.transmit.call(null,repl_connection,new cljs.core.Keyword(null,"send-result","send-result",3729280372),clojure.browser.repl.evaluate_javascript.call(null,repl_connection,js)); 89 | })); 90 | return clojure.browser.net.connect.call(null,repl_connection,cljs.core.constantly.call(null,null),(function (iframe){return iframe.style.display = "none"; 91 | })); 92 | }); 93 | -------------------------------------------------------------------------------- /code/chapter07/om-pm/.repl/76/om/dom.cljs: -------------------------------------------------------------------------------- 1 | (ns om.dom 2 | (:refer-clojure :exclude [map meta time]) 3 | (:require-macros [om.dom :as dom])) 4 | 5 | (dom/gen-react-dom-fns) 6 | 7 | (defn wrap-form-element [ctor] 8 | (js/React.createClass 9 | #js 10 | {:getInitialState 11 | (fn [] 12 | (this-as this 13 | #js {:value (aget (.-props this) "value")})) 14 | :onChange 15 | (fn [e] 16 | (this-as this 17 | (let [handler (aget (.-props this) "onChange")] 18 | (when-not (nil? handler) 19 | (handler e) 20 | (.setState this #js {:value (.. e -target -value)}))))) 21 | :componentWillReceiveProps 22 | (fn [new-props] 23 | (this-as this 24 | (.setState this #js {:value (aget new-props "value")}))) 25 | :render 26 | (fn [] 27 | (this-as this 28 | (.transferPropsTo this 29 | ;; NOTE: if switch to macro we remove a closure allocation 30 | (ctor #js {:value (aget (.-state this) "value") 31 | :onChange (aget this "onChange") 32 | :children (aget (.-props this) "children")}))))})) 33 | 34 | (def input (wrap-form-element js/React.DOM.input)) 35 | 36 | (def textarea (wrap-form-element js/React.DOM.textarea)) 37 | 38 | (def option (wrap-form-element js/React.DOM.option)) 39 | 40 | (defn render [component el] 41 | (js/React.renderComponent component el)) 42 | -------------------------------------------------------------------------------- /code/chapter07/om-pm/README.md: -------------------------------------------------------------------------------- 1 | # om-pm 2 | 3 | An OM project designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2014 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /code/chapter07/om-pm/dev-resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /code/chapter07/om-pm/dev-resources/tools/http/ring/server.clj: -------------------------------------------------------------------------------- 1 | ;;; This namespace is used for development and testing purpose only. 2 | (ns ring.server 3 | (:require [cemerick.austin.repls :refer (browser-connected-repl-js)] 4 | [net.cgrand.enlive-html :as enlive] 5 | [compojure.route :refer (resources)] 6 | [compojure.core :refer (GET defroutes)] 7 | [ring.adapter.jetty :as jetty] 8 | [clojure.java.io :as io])) 9 | 10 | (enlive/deftemplate page 11 | (io/resource "public/index.html") 12 | [] 13 | [:body] (enlive/append 14 | (enlive/html [:script (browser-connected-repl-js)]))) 15 | 16 | (defroutes site 17 | (resources "/") 18 | (GET "/*" req (page))) 19 | 20 | (defn run 21 | "Run the ring server. It defines the server symbol with defonce." 22 | [] 23 | (defonce server 24 | (jetty/run-jetty #'site {:port 3000 :join? false})) 25 | server) 26 | -------------------------------------------------------------------------------- /code/chapter07/om-pm/dev-resources/tools/repl/brepl/connect.cljs: -------------------------------------------------------------------------------- 1 | (ns brepl.connect 2 | (:require [clojure.browser.repl])) 3 | -------------------------------------------------------------------------------- /code/chapter07/om-pm/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to om-pm 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/chapter07/om-pm/profiles.clj: -------------------------------------------------------------------------------- 1 | {:shared {:clean-targets ["out" :target-path]} 2 | 3 | :tdd [:shared 4 | {:cljsbuild 5 | {:builds {:om-pm 6 | {:compiler 7 | {:optimizations :whitespace 8 | :pretty-print true}}}}}] 9 | 10 | :dev [:shared 11 | {:resources-paths ["dev-resources"] 12 | :source-paths ["dev-resources/tools/http" "dev-resources/tools/repl"] 13 | :dependencies [[ring "1.2.1"] 14 | [compojure "1.1.6"] 15 | [enlive "1.1.5"]] 16 | :plugins [[com.cemerick/austin "0.1.3"]] 17 | :cljsbuild 18 | {:builds {:om-pm 19 | {:source-paths ["dev-resources/tools/repl"] 20 | :compiler 21 | {:optimizations :whitespace 22 | :pretty-print true}}}} 23 | 24 | :injections [(require '[ring.server :as http :refer [run]] 25 | 'cemerick.austin.repls) 26 | (defn browser-repl-env [] 27 | (reset! cemerick.austin.repls/browser-repl-env 28 | (cemerick.austin/repl-env))) 29 | (defn browser-repl [] 30 | (cemerick.austin.repls/cljs-repl 31 | (browser-repl-env)))]}]} 32 | -------------------------------------------------------------------------------- /code/chapter07/om-pm/project.clj: -------------------------------------------------------------------------------- 1 | (defproject om-pm "0.0.1-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License - v 1.0" 5 | :url "http://www.eclipse.org/legal/epl-v10.html" 6 | :distribution :repo} 7 | 8 | :min-lein-version "2.3.4" 9 | 10 | :source-paths ["src/clj" "src/cljs"] 11 | 12 | :dependencies [[org.clojure/clojure "1.6.0"] 13 | [org.clojure/clojurescript "0.0-2511"] 14 | [org.om/om "0.8.1"] 15 | [org.clojure/core.async "0.1.346.0-17112a-alpha"] 16 | [com.facebook/react "0.12.2"]] 17 | 18 | :plugins [[lein-cljsbuild "1.0.3"]] 19 | 20 | :hooks [leiningen.cljsbuild] 21 | 22 | :cljsbuild 23 | {:builds {:om-pm 24 | {:source-paths ["src/cljs"] 25 | :compiler 26 | {:output-to "dev-resources/public/js/om_pm.js" 27 | :optimizations :advanced 28 | :pretty-print false}}}}) 29 | -------------------------------------------------------------------------------- /code/chapter07/om-pm/src/cljs/om_pm/core.cljs: -------------------------------------------------------------------------------- 1 | (ns om-pm.core 2 | (:require [om.core :as om :include-macros true] 3 | [om.dom :as dom :include-macros true] 4 | [cljs.core.async :refer [put! chan e .-nativeEvent .-dataTransfer) 5 | key value)) 6 | 7 | (defn get-transfer-data! [e key] 8 | (-> (-> e .-nativeEvent .-dataTransfer) 9 | (.getData key))) 10 | 11 | (defn column-idx [title columns] 12 | (first (keep-indexed (fn [idx column] 13 | (when (= title (:title column)) 14 | idx)) 15 | columns))) 16 | 17 | (defn move-card! [columns {:keys [card-id source-column destination-column]}] 18 | (let [from (column-idx source-column columns) 19 | to (column-idx destination-column columns)] 20 | (-> columns 21 | (update-in [from :cards] (fn [cards] 22 | (remove #{card-id} cards))) 23 | (update-in [to :cards] (fn [cards] 24 | (conj cards card-id)))))) 25 | -------------------------------------------------------------------------------- /code/chapter07/scratch.clj: -------------------------------------------------------------------------------- 1 | (ns example 2 | (:require [om.core :as om :include-macros true] 3 | [om.dom :as dom :include-macros true])) 4 | -------------------------------------------------------------------------------- /code/chapter08/clj-futures-playground/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /code/chapter08/clj-futures-playground/README.md: -------------------------------------------------------------------------------- 1 | # clj-futures-playground 2 | 3 | A Clojure library designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2014 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /code/chapter08/clj-futures-playground/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to clj-futures-playground 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/chapter08/clj-futures-playground/project.clj: -------------------------------------------------------------------------------- 1 | (defproject clj-futures-playground "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 | :dependencies [[org.clojure/clojure "1.6.0"]]) 7 | -------------------------------------------------------------------------------- /code/chapter08/clj-futures-playground/src/clj_futures_playground/core.clj: -------------------------------------------------------------------------------- 1 | (ns clj-futures-playground.core 2 | (:require [clojure.pprint :refer [pprint]])) 3 | 4 | (def movie 5 | {:name "Lord of The Rings: The Fellowship of The Ring" 6 | :cast ["Cate Blanchett" 7 | "Elijah Wood" 8 | "Liv Tyler" 9 | "Orlando Bloom"]}) 10 | 11 | (def actor-movies 12 | [{:name "Cate Blanchett" 13 | :movies ["Lord of The Rings: The Fellowship of The Ring" 14 | "Lord of The Rings: The Return of The King" 15 | "The Curious Case of Benjamin Button"]} 16 | 17 | {:name "Elijah Wood" 18 | :movies ["Eternal Sunshine of the Spotless Mind" 19 | "Green Street Hooligans" 20 | "The Hobbit: An Unexpected Journey"]} 21 | 22 | {:name "Liv Tyler" 23 | :movies ["Lord of The Rings: The Fellowship of The Ring" 24 | "Lord of The Rings: The Return of The King" 25 | "Armageddon"]} 26 | 27 | {:name "Orlando Bloom" 28 | :movies ["Lord of The Rings: The Fellowship of The Ring" 29 | "Lord of The Rings: The Return of The King" 30 | "Pirates of the Caribbean: The Curse of the Black Pearl"]}]) 31 | 32 | (def actor-spouse 33 | [{:name "Cate Blanchett" :spouse "Andrew Upton"} 34 | {:name "Elijah Wood" :spouse "Unknown"} 35 | {:name "Liv Tyler" :spouse "Royston Langdon"} 36 | {:name "Orlando Bloom" :spouse "Miranda Kerr"}]) 37 | 38 | (def top-5-movies 39 | ["Lord of The Rings: The Fellowship of The Ring" 40 | "The Matrix" 41 | "The Matrix Reloaded" 42 | "Pirates of the Caribbean: The Curse of the Black Pearl" 43 | "Terminator"]) 44 | 45 | (defn cast-by-movie [name] 46 | (future (do (Thread/sleep 5000) 47 | (:cast movie)))) 48 | 49 | (defn movies-by-actor [name] 50 | (do (Thread/sleep 2000) 51 | (->> actor-movies 52 | (filter #(= name (:name %))) 53 | first))) 54 | 55 | (defn spouse-of [name] 56 | (do (Thread/sleep 2000) 57 | (->> actor-spouse 58 | (filter #(= name (:name %))) 59 | first))) 60 | 61 | (defn top-5 [] 62 | (future (do (Thread/sleep 5000) 63 | top-5-movies))) 64 | 65 | (defn aggregate-actor-data [spouses movies top-5] 66 | (map (fn [{:keys [name spouse]} {:keys [movies]}] 67 | {:name name 68 | :spouse spouse 69 | :movies (map (fn [m] 70 | (if (some #{m} top-5) 71 | (str m " - (top 5)") 72 | m)) 73 | movies)}) 74 | spouses 75 | movies)) 76 | 77 | (defn -main [& args] 78 | (time (let [cast (cast-by-movie "Lord of The Rings: The Fellowship of The Ring") 79 | movies (pmap movies-by-actor @cast) 80 | spouses (pmap spouse-of @cast) 81 | top-5 (top-5)] 82 | (prn "Fetching data...") 83 | (pprint (aggregate-actor-data spouses movies @top-5)) 84 | (shutdown-agents)))) 85 | -------------------------------------------------------------------------------- /code/chapter08/clj-futures-playground/test/clj_futures_playground/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-futures-playground.core-test 2 | (:require [clojure.test :refer :all] 3 | [clj-futures-playground.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /code/chapter08/examples.clj: -------------------------------------------------------------------------------- 1 | (def f (clojure.core/future 2 | (println "doing some expensive work...") 3 | (Thread/sleep 5000) 4 | (println "done") 5 | 10)) 6 | (println "You'll see me before the future finishes") 7 | @f 8 | (println "I could be doing something else. Instead I'm waiting.") 9 | 10 | ;; doing some expensive work... 11 | ;; You'll see me before the future finishes 12 | ;; done 13 | 14 | ;; done 15 | ;; I could be doing something else. Instead I'm waiting. 16 | -------------------------------------------------------------------------------- /code/chapter08/imminent-playground/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /code/chapter08/imminent-playground/README.md: -------------------------------------------------------------------------------- 1 | # imminent-playground 2 | 3 | A Clojure library designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2014 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /code/chapter08/imminent-playground/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to imminent-playground 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/chapter08/imminent-playground/project.clj: -------------------------------------------------------------------------------- 1 | (defproject imminent-playground "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 | :dependencies [[org.clojure/clojure "1.6.0"] 7 | [com.leonardoborges/imminent "0.1.0"]]) 8 | -------------------------------------------------------------------------------- /code/chapter08/imminent-playground/src/imminent_playground/core.clj: -------------------------------------------------------------------------------- 1 | (ns imminent-playground.core 2 | (:require [clojure.pprint :refer [pprint]] 3 | [imminent.core :as i])) 4 | 5 | (def movie {:name "Lord of The Rings: The Fellowship of The Ring" 6 | :cast ["Cate Blanchett" 7 | "Elijah Wood" 8 | "Liv Tyler" 9 | "Orlando Bloom"]}) 10 | 11 | (def actor-movies [{:name "Cate Blanchett" 12 | :movies ["Lord of The Rings: The Fellowship of The Ring" 13 | "Lord of The Rings: The Return of The King" 14 | "The Curious Case of Benjamin Button"]} 15 | 16 | {:name "Elijah Wood" 17 | :movies ["Eternal Sunshine of the Spotless Mind" 18 | "Green Street Hooligans" 19 | "The Hobbit: An Unexpected Journey"]} 20 | 21 | {:name "Liv Tyler" 22 | :movies ["Lord of The Rings: The Fellowship of The Ring" 23 | "Lord of The Rings: The Return of The King" 24 | "Armageddon"]} 25 | 26 | {:name "Orlando Bloom" 27 | :movies ["Lord of The Rings: The Fellowship of The Ring" 28 | "Lord of The Rings: The Return of The King" 29 | "Pirates of the Caribbean: The Curse of the Black Pearl"]}]) 30 | 31 | (def actor-spouse [{:name "Cate Blanchett" :spouse "Andrew Upton"} 32 | {:name "Elijah Wood" :spouse "Unknown"} 33 | {:name "Liv Tyler" :spouse "Royston Langdon"} 34 | {:name "Orlando Bloom" :spouse "Miranda Kerr"}]) 35 | 36 | (def top-5-movies 37 | ["Lord of The Rings: The Fellowship of The Ring" 38 | "The Matrix" 39 | "The Matrix Reloaded" 40 | "Pirates of the Caribbean: The Curse of the Black Pearl" 41 | "Terminator"]) 42 | 43 | (defn cast-by-movie [name] 44 | (i/future (do (Thread/sleep 5000) 45 | (:cast movie)))) 46 | 47 | (defn movies-by-actor [name] 48 | (i/future (do (Thread/sleep 2000) 49 | (->> actor-movies 50 | (filter #(= name (:name %))) 51 | first)))) 52 | 53 | (defn spouse-of [name] 54 | (i/future (do (Thread/sleep 2000) 55 | (->> actor-spouse 56 | (filter #(= name (:name %))) 57 | first)))) 58 | 59 | (defn top-5 [] 60 | (i/future (do (Thread/sleep 5000) 61 | top-5-movies))) 62 | 63 | (defn aggregate-actor-data [spouses movies top-5] 64 | (map (fn [{:keys [name spouse]} {:keys [movies]}] 65 | {:name name 66 | :spouse spouse 67 | :movies (map (fn [m] 68 | (if (some #{m} top-5) 69 | (str m " - (top 5)") 70 | m)) 71 | movies)}) 72 | spouses 73 | movies)) 74 | 75 | (defn -main [& args] 76 | (time (let [cast (cast-by-movie "Lord of The Rings: The Fellowship of The Ring") 77 | movies (i/flatmap cast #(i/map-future movies-by-actor %)) 78 | spouses (i/flatmap cast #(i/map-future spouse-of %)) 79 | result (i/sequence [spouses movies (top-5)])] 80 | (prn "Fetching data...") 81 | (pprint (apply aggregate-actor-data 82 | (i/dderef (i/await result))))))) 83 | -------------------------------------------------------------------------------- /code/chapter08/imminent-playground/src/imminent_playground/repl.clj: -------------------------------------------------------------------------------- 1 | (ns imminent-playground.repl 2 | (:require [imminent.core :as i])) 3 | 4 | (def repl-out *out*) 5 | (defn prn-to-repl [& args] 6 | (binding [*out* repl-out] 7 | (apply prn args))) 8 | 9 | (def age (i/future 31)) 10 | 11 | ;; #> 12 | 13 | (def failed-computation (i/future (throw (Exception. "Error")))) 14 | ;; #>> 15 | 16 | (def failed-computation-1 (i/failed-future :invalid-data)) 17 | ;; #> 18 | 19 | @age ;; # 20 | (deref @age) ;; 31 21 | (i/dderef age) ;; 31 22 | 23 | 24 | @(i/future (do (Thread/sleep 500) 25 | "hello")) 26 | ;; :imminent.future/unresolved 27 | 28 | 29 | (def double-age (i/map age #(* % 2))) 30 | ;; #> 31 | 32 | (i/on-success age #(prn-to-repl (str "Age is: " %))) 33 | ;; "Age is: 31" 34 | 35 | (-> failed-computation 36 | (i/map #(* % 2))) 37 | ;; #>> 38 | 39 | (i/map (i/success "hello") 40 | #(str % " world")) 41 | ;; # 42 | 43 | (i/map (i/failure "error") 44 | #(str % " world")) 45 | ;; # 46 | 47 | (defn range-future [n] 48 | (i/const-future (range n))) 49 | 50 | (def age-range (i/map age range-future)) 51 | 52 | ;; #>>> 53 | 54 | (def age-range (i/flatmap age range-future)) 55 | 56 | ;; #> 57 | 58 | (def name (i/future (do (Thread/sleep 500) 59 | "Leo"))) 60 | (def genres (i/future (do (Thread/sleep 500) 61 | ["Heavy Metal" "Black Metal" "Death Metal" "Rock 'n Roll"]))) 62 | 63 | (-> (i/sequence [name age genres]) 64 | (i/on-success 65 | (fn [[name age genres]] 66 | (prn-to-repl (format "%s is %s years old and enjoys %s" 67 | name 68 | age 69 | (clojure.string/join "," genres)))))) 70 | 71 | ;; "Leo is 31 years old and enjoys Heavy Metal,Black Metal,Death Metal,Rock 'n Roll" 72 | 73 | (defn calculate-double [n] 74 | (i/const-future (* n 2))) 75 | 76 | (-> (i/map-future calculate-double [1 2 3 4]) 77 | i/await 78 | i/dderef) 79 | 80 | ;; [2 4 6 8] 81 | -------------------------------------------------------------------------------- /code/chapter08/imminent-playground/test/imminent_playground/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns imminent-playground.core-test 2 | (:require [clojure.test :refer :all] 3 | [imminent-playground.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | -------------------------------------------------------------------------------- /code/chapter09/aws-api-stub/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | -------------------------------------------------------------------------------- /code/chapter09/aws-api-stub/README.md: -------------------------------------------------------------------------------- 1 | # aws-api-stub 2 | 3 | FIXME 4 | 5 | ## Prerequisites 6 | 7 | You will need [Leiningen][] 2.0.0 or above installed. 8 | 9 | [leiningen]: https://github.com/technomancy/leiningen 10 | 11 | ## Running 12 | 13 | To start a web server for the application, run: 14 | 15 | lein ring server 16 | 17 | ## License 18 | 19 | Copyright © 2014 FIXME 20 | -------------------------------------------------------------------------------- /code/chapter09/aws-api-stub/project.clj: -------------------------------------------------------------------------------- 1 | (defproject aws-api-stub "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :min-lein-version "2.0.0" 5 | :dependencies [[org.clojure/clojure "1.6.0"] 6 | [compojure "1.2.0"] 7 | [ring/ring-defaults "0.1.2"] 8 | [cheshire "5.3.1"]] 9 | :plugins [[lein-ring "0.8.13"]] 10 | :ring {:handler aws-api-stub.core.handler/app} 11 | :profiles 12 | {:dev {:dependencies [[javax.servlet/servlet-api "2.5"] 13 | [ring-mock "0.1.5"]]}}) 14 | -------------------------------------------------------------------------------- /code/chapter09/aws-api-stub/src/aws_api_stub/aws.clj: -------------------------------------------------------------------------------- 1 | (ns aws-api-stub.aws) 2 | 3 | ;; CloudFormation 4 | (defn describe-stacks [] 5 | {"Stacks" 6 | [{"StackId" 7 | "arn:aws:cloudformation:ap-southeast-2:337944750480:stack/DevStack-62031/1", 8 | "StackStatus" "CREATE_IN_PROGRESS", 9 | "StackName" "DevStack-62031", 10 | "Parameters" [{"ParameterKey" "DevDB", "ParameterValue" nil}]}]}) 11 | 12 | (defn describe-stack-resources [] 13 | {"StackResources" 14 | [{"PhysicalResourceId" "EC2123", 15 | "ResourceType" "AWS::EC2::Instance"}, 16 | {"PhysicalResourceId" "EC2456", 17 | "ResourceType" "AWS::EC2::Instance"} 18 | {"PhysicalResourceId" "EC2789", 19 | "ResourceType" "AWS::EC2::Instance"} 20 | {"PhysicalResourceId" "RDS123", 21 | "ResourceType" "AWS::RDS::DBInstance"} 22 | {"PhysicalResourceId" "RDS456", 23 | "ResourceType" "AWS::RDS::DBInstance"}]}) 24 | 25 | ;; EC2 26 | (defn describe-instances [] 27 | {"Reservations" 28 | [{"Instances" 29 | [{"InstanceId" "EC2123", 30 | "Tags" 31 | [{"Key" "StackType", "Value" "Dev"} 32 | {"Key" "junkTag", "Value" "should not be included"} 33 | {"Key" "aws:cloudformation:logical-id", "Value" "theDude"}], 34 | "State" {"Name" "running"}} 35 | {"InstanceId" "EC2456", 36 | "Tags" 37 | [{"Key" "StackType", "Value" "Dev"} 38 | {"Key" "junkTag", "Value" "should not be included"} 39 | {"Key" "aws:cloudformation:logical-id", "Value" "theDude"}], 40 | "State" {"Name" "running"}} 41 | {"InstanceId" "EC2789", 42 | "Tags" 43 | [{"Key" "StackType", "Value" "Dev"} 44 | {"Key" "junkTag", "Value" "should not be included"} 45 | {"Key" "aws:cloudformation:logical-id", "Value" "theDude"}], 46 | "State" {"Name" "running"}}]}]}) 47 | 48 | ;; RDS 49 | 50 | (def db-instances {"RDS123" {"DBInstanceIdentifier" "RDS123", "DBInstanceStatus" "available"} 51 | "RDS456" {"DBInstanceIdentifier" "RDS456", "DBInstanceStatus" "available"}}) 52 | 53 | (defn describe-db-instances [instance-id] 54 | {"DBInstances" 55 | [(get db-instances instance-id)]}) 56 | -------------------------------------------------------------------------------- /code/chapter09/aws-api-stub/src/aws_api_stub/core/handler.clj: -------------------------------------------------------------------------------- 1 | (ns aws-api-stub.core.handler 2 | (:require [compojure.core :refer :all] 3 | [compojure.route :as route] 4 | [ring.middleware.defaults :refer [wrap-defaults site-defaults]] 5 | [aws-api-stub.aws :as aws] 6 | [cheshire.core :refer [generate-string]])) 7 | 8 | 9 | (defn allow-cross-origin 10 | "middleware function to allow crosss origin" 11 | [handler] 12 | (fn [request] 13 | (let [response (handler request)] 14 | (assoc-in response [:headers "Access-Control-Allow-Origin"] 15 | "*")))) 16 | 17 | (defroutes app-routes 18 | (GET "/cloudFormation/describeStacks" [] (generate-string (aws/describe-stacks))) 19 | (GET "/cloudFormation/describeStackResources" [] (generate-string (aws/describe-stack-resources))) 20 | (GET "/ec2/describeInstances" [] (generate-string (aws/describe-instances))) 21 | (GET "/rds/describeDBInstances/:instance-id" [instance-id] (generate-string (aws/describe-db-instances instance-id))) 22 | (route/not-found "Not Found")) 23 | 24 | (def app 25 | (-> (wrap-defaults app-routes site-defaults) 26 | allow-cross-origin)) 27 | -------------------------------------------------------------------------------- /code/chapter09/aws-api-stub/test/aws_api_stub/core/handler_test.clj: -------------------------------------------------------------------------------- 1 | (ns aws-api-stub.core.handler-test 2 | (:require [clojure.test :refer :all] 3 | [ring.mock.request :as mock] 4 | [aws-api-stub.core.handler :refer :all])) 5 | 6 | (deftest test-app 7 | (testing "main route" 8 | (let [response (app (mock/request :get "/"))] 9 | (is (= (:status response) 200)) 10 | (is (= (:body response) "Hello World")))) 11 | 12 | (testing "not-found route" 13 | (let [response (app (mock/request :get "/invalid"))] 14 | (is (= (:status response) 404))))) 15 | -------------------------------------------------------------------------------- /code/chapter09/aws-dash/.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | /lib/ 4 | /classes/ 5 | /out/ 6 | /target/ 7 | .lein-deps-sum 8 | .lein-repl-history 9 | .lein-plugins/ 10 | /dev-resources/public/js/* 11 | -------------------------------------------------------------------------------- /code/chapter09/aws-dash/README.md: -------------------------------------------------------------------------------- 1 | # aws-dash 2 | 3 | An OM project designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2014 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /code/chapter09/aws-dash/dev-resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /code/chapter09/aws-dash/dev-resources/tools/http/ring/server.clj: -------------------------------------------------------------------------------- 1 | ;;; This namespace is used for development and testing purpose only. 2 | (ns ring.server 3 | (:require [cemerick.austin.repls :refer (browser-connected-repl-js)] 4 | [compojure.route :refer (resources)] 5 | [compojure.core :refer (GET defroutes)] 6 | [ring.adapter.jetty :as jetty] 7 | [clojure.java.io :as io] 8 | [clojure.zip :as zip] 9 | [hickory.core :as hc] 10 | [hickory.render :as hr] 11 | [hickory.select :as hs] 12 | [hickory.zip :as hz])) 13 | 14 | (defn repl-js-script [] 15 | (-> (str "") 16 | hc/parse-fragment 17 | first 18 | hc/as-hickory)) 19 | 20 | (defn original-page [] 21 | (-> "public/index.html" 22 | io/resource 23 | slurp 24 | hc/parse 25 | hc/as-hickory)) 26 | 27 | (defn page [] 28 | (->> (original-page) 29 | hz/hickory-zip 30 | (hs/select-next-loc (hs/tag :body)) 31 | (#(zip/append-child % (repl-js-script))) 32 | zip/root 33 | hr/hickory-to-html)) 34 | 35 | (defroutes site 36 | (resources "/") 37 | (GET "/*" req (page))) 38 | 39 | (defn run 40 | "Run the ring server. It defines the server symbol with defonce." 41 | [] 42 | (defonce server 43 | (jetty/run-jetty #'site {:port 3000 :join? false})) 44 | server) 45 | -------------------------------------------------------------------------------- /code/chapter09/aws-dash/dev-resources/tools/repl/brepl/connect.cljs: -------------------------------------------------------------------------------- 1 | (ns brepl.connect 2 | (:require [clojure.browser.repl])) 3 | -------------------------------------------------------------------------------- /code/chapter09/aws-dash/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to aws-dash 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) 4 | -------------------------------------------------------------------------------- /code/chapter09/aws-dash/profiles.clj: -------------------------------------------------------------------------------- 1 | {:shared {:clean-targets ["out" :target-path]} 2 | 3 | :tdd [:shared 4 | {:cljsbuild 5 | {:builds {:aws-dash 6 | {:compiler 7 | {:optimizations :whitespace 8 | :pretty-print true}}}}}] 9 | 10 | :dev [:shared 11 | {:resources-paths ["dev-resources"] 12 | :source-paths ["dev-resources/tools/http" "dev-resources/tools/repl"] 13 | :dependencies [[ring "1.2.1"] 14 | [compojure "1.1.6"] 15 | [hickory "0.5.3"]] 16 | :plugins [[com.cemerick/austin "0.1.4"]] 17 | :cljsbuild 18 | {:builds {:aws-dash 19 | {:source-paths ["dev-resources/tools/repl"] 20 | :compiler 21 | {:optimizations :whitespace 22 | :pretty-print true}}}} 23 | 24 | :injections [(require '[ring.server :as http :refer [run]] 25 | 'cemerick.austin.repls) 26 | (defn browser-repl-env [] 27 | (reset! cemerick.austin.repls/browser-repl-env 28 | (cemerick.austin/repl-env))) 29 | (defn browser-repl [] 30 | (cemerick.austin.repls/cljs-repl 31 | (browser-repl-env)))]}]} 32 | -------------------------------------------------------------------------------- /code/chapter09/aws-dash/project.clj: -------------------------------------------------------------------------------- 1 | (defproject aws-dash "0.0.1-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License - v 1.0" 5 | :url "http://www.eclipse.org/legal/epl-v10.html" 6 | :distribution :repo} 7 | 8 | :min-lein-version "2.3.4" 9 | 10 | :source-paths ["src/clj" "src/cljs"] 11 | 12 | :dependencies [[org.clojure/clojure "1.6.0"] 13 | [org.clojure/clojurescript "0.0-2371"] 14 | [org.clojure/core.async "0.1.346.0-17112a-alpha"] 15 | [om "0.5.0"] 16 | [com.facebook/react "0.9.0"] 17 | [cljs-http "0.1.20"] 18 | [com.cognitect/transit-cljs "0.8.192"]] 19 | 20 | :plugins [[lein-cljsbuild "1.0.3"]] 21 | 22 | :hooks [leiningen.cljsbuild] 23 | 24 | :cljsbuild 25 | {:builds {:aws-dash 26 | {:source-paths ["src/cljs"] 27 | :compiler 28 | {:output-to "dev-resources/public/js/aws_dash.js" 29 | :optimizations :advanced 30 | :pretty-print false}}}}) 31 | -------------------------------------------------------------------------------- /code/chapter09/aws-dash/src/cljs/aws_dash/core.cljs: -------------------------------------------------------------------------------- 1 | (ns aws-dash.core 2 | (:require [aws-dash.observables :as obs] 3 | [om.core :as om :include-macros true] 4 | [om.dom :as dom :include-macros true])) 5 | 6 | (enable-console-print!) 7 | 8 | (def app-state (atom {:instances []})) 9 | 10 | (defn instance-view [{:keys [instance-id type status]} owner] 11 | (reify 12 | om/IRender 13 | (render [this] 14 | (dom/tr nil 15 | (dom/td nil instance-id) 16 | (dom/td nil type) 17 | (dom/td nil status))))) 18 | 19 | (defn instances-view [instances owner] 20 | (reify 21 | om/IRender 22 | (render [this] 23 | (apply dom/table #js {:style #js {:border "1px solid black;"}} 24 | (dom/tr nil 25 | (dom/th nil "Id") 26 | (dom/th nil "Type") 27 | (dom/th nil "Status")) 28 | (om/build-all instance-view instances))))) 29 | 30 | (om/root 31 | (fn [app owner] 32 | (dom/div nil 33 | (dom/h1 nil "Stack Resource Statuses") 34 | (om/build instances-view (:instances app)))) 35 | app-state 36 | {:target (. js/document (getElementById "app"))}) 37 | 38 | 39 | 40 | (def resources (obs/stack-resources)) 41 | 42 | (.subscribe (-> (.merge (obs/rds-instance-status resources) 43 | (obs/ec2-instance-status resources)) 44 | (.reduce conj [])) 45 | #(swap! app-state assoc :instances %)) 46 | -------------------------------------------------------------------------------- /code/chapter09/aws-dash/src/cljs/aws_dash/observables.cljs: -------------------------------------------------------------------------------- 1 | (ns aws-dash.observables 2 | (:require-macros [cljs.core.async.macros :refer [go]]) 3 | (:require [cljs-http.client :as http] 4 | [cljs.core.async :refer [ (describe-stacks) 63 | (.map #(:stack-name %)) 64 | (.flatMap describe-stack-resources))) 65 | 66 | (defn ec2-instance-status [resources] 67 | (-> resources 68 | (.filter #(= (:resource-type %) "AWS::EC2::Instance")) 69 | (.map #(:resource-id %)) 70 | (.reduce conj []) 71 | (.flatMap describe-instances))) 72 | 73 | (defn rds-instance-status [resources] 74 | (-> resources 75 | (.filter #(= (:resource-type %) "AWS::RDS::DBInstance")) 76 | (.map #(:resource-id %)) 77 | (.flatMap describe-db-instances))) 78 | --------------------------------------------------------------------------------