├── .gitignore ├── README.markdown ├── bin ├── repl ├── repl.bat └── run.clj ├── data └── words ├── epl-v10.html ├── pom.xml ├── project.clj ├── public ├── 404.html ├── javascripts │ ├── application.js │ ├── jquery.js │ ├── shBrushClojure.js │ └── shCore.js └── stylesheets │ ├── application.css │ ├── clojure_logo.jpg │ ├── help.png │ ├── magnifier.png │ ├── page_white_code.png │ ├── page_white_copy.png │ ├── printer.png │ ├── shCore.css │ └── shThemeDefault.css ├── resources └── log4j.properties ├── src ├── handlers.clj ├── labrepl.clj ├── labrepl │ ├── layout.clj │ └── util.clj ├── labs │ ├── cellular_automata.clj │ ├── defstrict.clj │ ├── intro.clj │ ├── its_all_data.clj │ ├── looping.clj │ ├── mini_browser.clj │ ├── names_and_places.clj │ ├── project_euler.clj │ ├── rock_paper_scissors.clj │ ├── unified_update_model.clj │ └── zero_sum.clj ├── solutions │ ├── accounts_1.clj │ ├── accounts_2.clj │ ├── accounts_3.clj │ ├── apple_pie.clj │ ├── atom_cache.clj │ ├── automaton.clj │ ├── browser_mockup.clj │ ├── defstrict.clj │ ├── dialect.clj │ ├── fight.clj │ ├── looping.clj │ ├── mini_browser.clj │ ├── project_euler.clj │ ├── ref_cache.clj │ └── rock_paper_scissors.clj └── student │ └── README ├── test ├── labrepl │ ├── apple_pie_bench.clj │ ├── render_test.clj │ └── util_test.clj └── solutions │ ├── accounts_1_test.clj │ ├── accounts_2_test.clj │ ├── accounts_3_test.clj │ ├── atom_cache_test.clj │ ├── browser_mockup_test.clj │ ├── defstrict_test.clj │ ├── looping_test.clj │ └── ref_cache_test.clj └── todo.org /.gitignore: -------------------------------------------------------------------------------- 1 | classes/* 2 | lib/* 3 | autodoc/* 4 | .circumspec/* 5 | log 6 | *~ 7 | target 8 | /logs/labrepl.log 9 | .lein-failures 10 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # labrepl for Clojure 2 | 3 | Copyright (c) Relevance, Inc. All rights reserved. 4 | 5 | The use and distribution terms for this software are covered by the 6 | [Eclipse Public License 1.0](http://opensource.org/licenses/eclipse-1.0.php) 7 | which can be found in the file epl-v10.html at the root of this distribution. 8 | By using this software in any fashion, you are agreeing to be bound by 9 | the terms of this license. 10 | 11 | You must not remove this notice, or any other, from this software. 12 | 13 | # What is it? 14 | 15 | Labrepl is an environment for exploring the Clojure language. It 16 | includes: 17 | 18 | * a web application that presents a set of lab exercises with 19 | step-by-step instructions 20 | * an interactive repl for working with the lab exercises 21 | * solutions with passing tests 22 | * up-to-date versions of Clojure, contrib, incanter, compojure and a bunch of other libraries to explore 23 | 24 | See the Wiki for getting started with NetBeans/Enclojure, Eclipse/Counterclockwise, Maven, Mac/Linux command line, Windows command line, IDEA/La Clojure, and Emacs. 25 | 26 | ## Getting Started 27 | 28 | Quick start: 29 | 30 | bin/repl 31 | 32 | Then follow the instructions at the REPL. 33 | 34 | Setup instructions for most popular editors/IDEs can be found [on the wiki](https://github.com/relevance/labrepl/wiki) 35 | 36 | ## Running the Tests 37 | 38 | * Leiningen: `lein test` 39 | * Maven: `mvn clojure:test` 40 | 41 | ## Thanks for contributions and reviews from 42 | 43 | * Aaron Bedra 44 | * Mike Clark 45 | * Daniel Solano Gómez 46 | * Rich Hickey 47 | * Shawn Hoover 48 | * Larry Karnowski 49 | * Michael Kohl 50 | * Jess Martin 51 | * Alex Ott 52 | * Laurent Petit 53 | * Seth Schroeder 54 | * Matthew Wizeman 55 | -------------------------------------------------------------------------------- /bin/repl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | CLASSPATH=src:test:resources:data:`lein classpath` 3 | 4 | for f in lib/*.jar; do 5 | CLASSPATH=$CLASSPATH:$f 6 | done 7 | 8 | java -Xmx4G -cp $CLASSPATH jline.ConsoleRunner clojure.main -i bin/run.clj -r 9 | -------------------------------------------------------------------------------- /bin/repl.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setLocal EnableDelayedExpansion 3 | set CLASSPATH=" 4 | for /R ./lib %%a in (*.jar) do ( 5 | set CLASSPATH=!CLASSPATH!;%%a 6 | ) 7 | set CLASSPATH=!CLASSPATH!" 8 | set CLASSPATH=%CLASSPATH%;src;test;resources;data 9 | echo CLASSPATH=%CLASSPATH% 10 | 11 | @rem jline breaks inferior-lisp. 12 | if not defined LABREPL_SWANK set JLINE=jline.ConsoleRunner 13 | 14 | java -Xmx1G -cp %CLASSPATH% %JLINE% clojure.main -i bin/run.clj -r 15 | -------------------------------------------------------------------------------- /bin/run.clj: -------------------------------------------------------------------------------- 1 | (defn load-common-libs [] 2 | (use '[clojure.java.io :only (reader writer)] 3 | 'clojure.pprint)) 4 | 5 | (when-let [run-swank (System/getenv "LABREPL_SWANK")] 6 | (println "Starting swank...") 7 | (load-string (if-let [found (re-find #"^\"(.*)\"$" run-swank)] 8 | (second found) 9 | run-swank))) 10 | 11 | (require 'labrepl) 12 | (set! *print-length* 100) 13 | (load-common-libs) 14 | (labrepl/-main) 15 | -------------------------------------------------------------------------------- /epl-v10.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Eclipse Public License - Version 1.0 8 | 25 | 26 | 27 | 28 | 29 | 30 |

Eclipse Public License - v 1.0

31 | 32 |

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.

36 | 37 |

1. DEFINITIONS

38 | 39 |

"Contribution" means:

40 | 41 |

a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and

43 |

b) in the case of each subsequent Contributor:

44 |

i) changes to the Program, and

45 |

ii) additions to the Program;

46 |

where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.

54 | 55 |

"Contributor" means any person or entity that distributes 56 | the Program.

57 | 58 |

"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.

61 | 62 |

"Program" means the Contributions distributed in accordance 63 | with this Agreement.

64 | 65 |

"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.

67 | 68 |

2. GRANT OF RIGHTS

69 | 70 |

a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.

76 | 77 |

b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.

88 | 89 |

c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.

101 | 102 |

d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.

105 | 106 |

3. REQUIREMENTS

107 | 108 |

A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:

110 | 111 |

a) it complies with the terms and conditions of this 112 | Agreement; and

113 | 114 |

b) its license agreement:

115 | 116 |

i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;

120 | 121 |

ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;

124 | 125 |

iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and

128 | 129 |

iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.

133 | 134 |

When the Program is made available in source code form:

135 | 136 |

a) it must be made available under this Agreement; and

137 | 138 |

b) a copy of this Agreement must be included with each 139 | copy of the Program.

140 | 141 |

Contributors may not remove or alter any copyright notices contained 142 | within the Program.

143 | 144 |

Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.

147 | 148 |

4. COMMERCIAL DISTRIBUTION

149 | 150 |

Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.

172 | 173 |

For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.

183 | 184 |

5. NO WARRANTY

185 | 186 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.

197 | 198 |

6. DISCLAIMER OF LIABILITY

199 | 200 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

208 | 209 |

7. GENERAL

210 | 211 |

If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.

216 | 217 |

If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.

223 | 224 |

All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.

232 | 233 |

Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.

252 | 253 |

This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.

258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | labrepl 5 | labrepl 6 | jar 7 | 0.0.2-SNAPSHOT 8 | labrepl 9 | Clojure exercises, with integrated repl and webapp 10 | 11 | scm:git:git://github.com/relevance/labrepl.git 12 | scm:git:ssh://git@github.com/relevance/labrepl.git 13 | 3435cee6dfb6378c58eb50c083d1e0054c190247 14 | https://github.com/relevance/labrepl 15 | 16 | 17 | src 18 | test 19 | 20 | 21 | resources 22 | 23 | 24 | 25 | 26 | dev-resources 27 | 28 | 29 | resources 30 | 31 | 32 | target 33 | target/classes 34 | 35 | 36 | 37 | central 38 | http://repo1.maven.org/maven2/ 39 | 40 | false 41 | 42 | 43 | true 44 | 45 | 46 | 47 | clojars 48 | https://clojars.org/repo/ 49 | 50 | true 51 | 52 | 53 | true 54 | 55 | 56 | 57 | 58 | 59 | org.clojure 60 | clojure 61 | 1.5.0-RC15 62 | 63 | 64 | org.clojure 65 | tools.logging 66 | 0.2.3 67 | 68 | 69 | org.clojure 70 | data.json 71 | 0.2.1 72 | 73 | 74 | ring 75 | ring-jetty-adapter 76 | 1.0.0-RC1 77 | 78 | 79 | org.clojure 80 | clojure 81 | 82 | 83 | org.clojure 84 | clojure-contrib 85 | 86 | 87 | 88 | 89 | compojure 90 | compojure 91 | 0.6.5 92 | 93 | 94 | org.clojure 95 | clojure 96 | 97 | 98 | 99 | 100 | hiccup 101 | hiccup 102 | 0.3.7 103 | 104 | 105 | org.clojure 106 | clojure 107 | 108 | 109 | 110 | 111 | log4j 112 | log4j 113 | 1.2.16 114 | 115 | 116 | javax.mail 117 | mail 118 | 119 | 120 | javax.jms 121 | jms 122 | 123 | 124 | com.sun.jdmk 125 | jmxtools 126 | 127 | 128 | com.sun.jmx 129 | jmxri 130 | 131 | 132 | 133 | 134 | jline 135 | jline 136 | 0.9.94 137 | 138 | 139 | 140 | 141 | 145 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject labrepl "0.0.2-SNAPSHOT" 2 | :description "Clojure exercises, with integrated repl and webapp" 3 | :dependencies [[org.clojure/clojure "1.7.0-alpha1"] 4 | [org.clojure/tools.logging "0.2.3"] 5 | [org.clojure/data.json "0.2.1"] 6 | [org.clojure/test.generative "0.3.0"] 7 | [org.clojure/math.combinatorics "0.0.3"] 8 | [com.datomic/datomic-free "0.8.3848"] 9 | [org.codehaus.jsr166-mirror/jsr166y "1.7.0"] 10 | [ring/ring-jetty-adapter "1.0.0-RC1" :exclusions [org.clojure/clojure 11 | org.clojure/clojure-contrib]] 12 | [compojure "0.6.5" :exclusions [org.clojure/clojure]] 13 | [hiccup "0.3.7" :exclusions [org.clojure/clojure]] 14 | [log4j "1.2.16" :exclusions [javax.mail/mail 15 | javax.jms/jms 16 | com.sun.jdmk/jmxtools 17 | com.sun.jmx/jmxri]] 18 | [jline "0.9.94"] 19 | [criterium/criterium "0.3.1"]] 20 | :dev-dependencies [[swank-clojure "1.3.0" :exclusions [org.clojure/clojure]]]) 21 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 |

Page Not Found

-------------------------------------------------------------------------------- /public/javascripts/application.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | SyntaxHighlighter.all(); 3 | $(".toggle a").click(function(e) { 4 | $(this).parents(".toggle").children().toggle(); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /public/javascripts/shBrushClojure.js: -------------------------------------------------------------------------------- 1 | // Copyright © 2010 Sattvik Software & Technology Resources, Ltd. Co. 2 | // All rights reserved. 3 | // 4 | // This script is made available under the terms of the Eclipse Public License 5 | // v1.0 which accompanies this distribution, and is available at 6 | // http://www.eclipse.org/legal/epl-v10.html 7 | // 8 | // Written by Daniel Solano Gómez 9 | // 10 | // Version 0.9 - 7 Apr 2010 11 | 12 | function ClojureRegExp(pattern) { 13 | pattern = pattern + '(?=[[\\]{}(),\\s])'; 14 | this.regex=new RegExp(pattern,'g'); 15 | this.lookBehind=/[[\]{}(),\s]$/; 16 | } 17 | 18 | ClojureRegExp.prototype.exec=function(str) { 19 | var match, leftContext; 20 | while(match=this.regex.exec(str)) { 21 | leftContext=str.substring(0,match.index); 22 | if(this.lookBehind.test(leftContext)) { 23 | return match; 24 | } 25 | else { 26 | this.regex.lastIndex=match.index+1; 27 | } 28 | } 29 | return null; 30 | }; 31 | 32 | SyntaxHighlighter.brushes.Clojure = function() { 33 | var special_forms = 34 | '. def do fn if let loop monitor-enter monitor-exit new quote recur set! '+ 35 | 'throw try var'; 36 | 37 | var clojure_core = 38 | '* *1 *2 *3 *agent* *allow-unresolved-vars* *assert* *clojure-version* ' + 39 | '*command-line-args* *compile-files* *compile-path* *e *err* *file* ' + 40 | '*flush-on-newline* *in* *macro-meta* *math-context* *ns* *out* ' + 41 | '*print-dup* *print-length* *print-level* *print-meta* *print-readably* ' + 42 | '*read-eval* *source-path* *use-context-classloader* ' + 43 | '*warn-on-reflection* + - -> -> ->> ->> .. / < < <= <= = ' + 44 | '== > > >= >= accessor aclone ' + 45 | 'add-classpath add-watch agent agent-errors aget alength alias all-ns ' + 46 | 'alter alter-meta! alter-var-root amap ancestors and apply areduce ' + 47 | 'array-map aset aset-boolean aset-byte aset-char aset-double aset-float ' + 48 | 'aset-int aset-long aset-short assert assoc assoc! assoc-in associative? ' + 49 | 'atom await await-for await1 bases bean bigdec bigint binding bit-and ' + 50 | 'bit-and-not bit-clear bit-flip bit-not bit-or bit-set bit-shift-left ' + 51 | 'bit-shift-right bit-test bit-xor boolean boolean-array booleans ' + 52 | 'bound-fn bound-fn* butlast byte byte-array bytes cast char char-array ' + 53 | 'char-escape-string char-name-string char? chars chunk chunk-append ' + 54 | 'chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? ' + 55 | 'class class? clear-agent-errors clojure-version coll? comment commute ' + 56 | 'comp comparator compare compare-and-set! compile complement concat cond ' + 57 | 'condp conj conj! cons constantly construct-proxy contains? count ' + 58 | 'counted? create-ns create-struct cycle dec decimal? declare definline ' + 59 | 'defmacro defmethod defmulti defn defn- defonce defstruct delay delay? ' + 60 | 'deliver deref derive descendants destructure disj disj! dissoc dissoc! ' + 61 | 'distinct distinct? doall doc dorun doseq dosync dotimes doto double ' + 62 | 'double-array doubles drop drop-last drop-while empty empty? ensure ' + 63 | 'enumeration-seq eval even? every? false? ffirst file-seq filter find ' + 64 | 'find-doc find-ns find-var first float float-array float? floats flush ' + 65 | 'fn fn? fnext for force format future future-call future-cancel ' + 66 | 'future-cancelled? future-done? future? gen-class gen-interface gensym ' + 67 | 'get get-in get-method get-proxy-class get-thread-bindings get-validator ' + 68 | 'hash hash-map hash-set identical? identity if-let if-not ifn? import ' + 69 | 'in-ns inc init-proxy instance? int int-array integer? interleave intern ' + 70 | 'interpose into into-array ints io! isa? iterate iterator-seq juxt key ' + 71 | 'keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list ' + 72 | 'list* list? load load-file load-reader load-string loaded-libs locking ' + 73 | 'long long-array longs loop macroexpand macroexpand-1 make-array ' + 74 | 'make-hierarchy map map? mapcat max max-key memfn memoize merge ' + 75 | 'merge-with meta method-sig methods min min-key mod name namespace neg? ' + 76 | 'newline next nfirst nil? nnext not not-any? not-empty not-every? not= ' + 77 | ' ns ns-aliases ns-imports ns-interns ns-map ns-name ns-publics ' + 78 | 'ns-refers ns-resolve ns-unalias ns-unmap nth nthnext num number? odd? ' + 79 | 'or parents partial partition pcalls peek persistent! pmap pop pop! ' + 80 | 'pop-thread-bindings pos? pr pr-str prefer-method prefers ' + 81 | 'primitives-classnames print print-ctor print-doc print-dup print-method ' + 82 | 'print-namespace-doc print-simple print-special-doc print-str printf ' + 83 | 'println println-str prn prn-str promise proxy proxy-call-with-super ' + 84 | 'proxy-mappings proxy-name proxy-super push-thread-bindings pvalues quot ' + 85 | 'rand rand-int range ratio? rational? rationalize re-find re-groups ' + 86 | 're-matcher re-matches re-pattern re-seq read read-line read-string ' + 87 | 'reduce ref ref-history-count ref-max-history ref-min-history ref-set ' + 88 | 'refer refer-clojure release-pending-sends rem remove remove-method ' + 89 | 'remove-ns remove-watch repeat repeatedly replace replicate require ' + 90 | 'reset! reset-meta! resolve rest resultset-seq reverse reversible? rseq ' + 91 | 'rsubseq second select-keys send send-off seq seq? seque sequence ' + 92 | 'sequential? set set-validator! set? short short-array shorts ' + 93 | 'shutdown-agents slurp some sort sort-by sorted-map sorted-map-by ' + 94 | 'sorted-set sorted-set-by sorted? special-form-anchor special-symbol? ' + 95 | 'split-at split-with str stream? string? struct struct-map subs subseq ' + 96 | 'subvec supers swap! symbol symbol? sync syntax-symbol-anchor take ' + 97 | 'take-last take-nth take-while test the-ns time to-array to-array-2d ' + 98 | 'trampoline transient tree-seq true? type unchecked-add unchecked-dec ' + 99 | 'unchecked-divide unchecked-inc unchecked-multiply unchecked-negate ' + 100 | 'unchecked-remainder unchecked-subtract underive unquote ' + 101 | 'unquote-splicing update-in update-proxy use val vals var-get var-set ' + 102 | 'var? vary-meta vec vector vector? when when-first when-let when-not ' + 103 | 'while with-bindings with-bindings* with-in-str with-loading-context ' + 104 | 'with-local-vars with-meta with-open with-out-str with-precision xml-seq ' + 105 | 'zero? zipmap '; 106 | 107 | this.getKeywords = function(keywordStr) { 108 | // quote special characters 109 | keywordStr = keywordStr.replace(/[-[\]{}()*+?.\\^$|,#]/g, "\\$&"); 110 | // trim whitespace and convert to alternatives 111 | keywordStr = keywordStr.replace(/^\s+|\s+$/g,'').replace(/\s+/g,'|'); 112 | // create pattern 113 | return '(?:' + keywordStr + ')'; 114 | } 115 | 116 | this.regexList = [ 117 | // comments 118 | { regex: new RegExp(';.*$', 'gm'), 119 | css: 'comments' }, 120 | // strings 121 | { regex: SyntaxHighlighter.regexLib.multiLineDoubleQuotedString, 122 | css: 'string' }, 123 | // regular expressions 124 | { regex: /#"(?:\.|(\\\")|[^\""\n])*"/g, 125 | css: 'string' }, 126 | // vectors 127 | { regex: /\[|\]/g, 128 | css: 'keyword' }, 129 | // amperstands 130 | { regex: /&(amp;)?/g, 131 | css: 'keyword' }, 132 | // sets and maps 133 | { regex: /#?{|}/g, 134 | css: 'keyword' }, 135 | // anonymous fn syntactic sugar 136 | { regex: /#\(|%/g, 137 | css: 'keyword' }, 138 | // (un)quoted sexprs 139 | { regex: /(['`]|~@?)[[({]/g, 140 | css: 'keyword' }, 141 | // lists 142 | { regex: /\(|\)/g, 143 | css: 'keyword' }, 144 | // character literals 145 | { regex: /\\.\b/g, 146 | css: 'value' }, 147 | // hexadecimal literals 148 | { regex: /[+-]?\b0x[0-9A-F]+\b/gi, 149 | css: 'value' }, 150 | // integer/octal/float/bigdecimal literals 151 | { regex: new ClojureRegExp("[+-]?\\b\\d+(\\.\\d*)?([eE][+-]?\\d+|M)?\\b"), 152 | css: 'value' }, 153 | { regex: /^[+-]?\b\d+(\.\d*)?([eE][+-]?\d+|M)?\b/g, 154 | css: 'value' }, 155 | // booleans+nil 156 | { regex: /\b(true|false|nil)\b/g, 157 | css: 'value' }, 158 | // (un)quoted symbols 159 | { regex: /(`|#?'|~@?)[\w-.\/]+/g, 160 | css: 'color1' }, 161 | // keywords 162 | { regex: /:[A-Za-z0-9_-]+/g, 163 | css: 'constants' }, 164 | // special forms 165 | { regex: new ClojureRegExp(this.getKeywords(special_forms)), 166 | css: 'preprocessor' }, 167 | // type hints 168 | { regex: /\#\^[A-Za-z]\w*/g, 169 | css: 'preprocessor' }, 170 | // clojure.core 171 | { regex: new ClojureRegExp(this.getKeywords(clojure_core)), 172 | css: 'functions' } 173 | ]; 174 | 175 | this.forHtmlScript(SyntaxHighlighter.regexLib.scriptScriptTags); 176 | } 177 | 178 | SyntaxHighlighter.brushes.Clojure.prototype = new SyntaxHighlighter.Highlighter(); 179 | SyntaxHighlighter.brushes.Clojure.aliases = ['clojure', 'Clojure', 'clj']; 180 | 181 | // vim: ts=2 sw=2 noet 182 | -------------------------------------------------------------------------------- /public/javascripts/shCore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('K M;I(M)1S 2U("2a\'t 4k M 4K 2g 3l 4G 4H");(6(){6 r(f,e){I(!M.1R(f))1S 3m("3s 15 4R");K a=f.1w;f=M(f.1m,t(f)+(e||""));I(a)f.1w={1m:a.1m,19:a.19?a.19.1a(0):N};H f}6 t(f){H(f.1J?"g":"")+(f.4s?"i":"")+(f.4p?"m":"")+(f.4v?"x":"")+(f.3n?"y":"")}6 B(f,e,a,b){K c=u.L,d,h,g;v=R;5K{O(;c--;){g=u[c];I(a&g.3r&&(!g.2p||g.2p.W(b))){g.2q.12=e;I((h=g.2q.X(f))&&h.P===e){d={3k:g.2b.W(b,h,a),1C:h};1N}}}}5v(i){1S i}5q{v=11}H d}6 p(f,e,a){I(3b.Z.1i)H f.1i(e,a);O(a=a||0;a-1},3d:6(g){e+=g}};c1&&p(e,"")>-1){a=15(J.1m,n.Q.W(t(J),"g",""));n.Q.W(f.1a(e.P),a,6(){O(K c=1;c<14.L-2;c++)I(14[c]===1d)e[c]=1d})}I(J.1w&&J.1w.19)O(K b=1;be.P&&J.12--}H e};I(!D)15.Z.1A=6(f){(f=n.X.W(J,f))&&J.1J&&!f[0].L&&J.12>f.P&&J.12--;H!!f};1r.Z.1C=6(f){M.1R(f)||(f=15(f));I(f.1J){K e=n.1C.1p(J,14);f.12=0;H e}H f.X(J)};1r.Z.Q=6(f,e){K a=M.1R(f),b,c;I(a&&1j e.58()==="3f"&&e.1i("${")===-1&&y)H n.Q.1p(J,14);I(a){I(f.1w)b=f.1w.19}Y f+="";I(1j e==="6")c=n.Q.W(J,f,6(){I(b){14[0]=1f 1r(14[0]);O(K d=0;dd.L-3;){i=1r.Z.1a.W(g,-1)+i;g=1Q.3i(g/10)}H(g?d[g]||"":"$")+i}Y{g=+i;I(g<=d.L-3)H d[g];g=b?p(b,i):-1;H g>-1?d[g+1]:h}})})}I(a&&f.1J)f.12=0;H c};1r.Z.1e=6(f,e){I(!M.1R(f))H n.1e.1p(J,14);K a=J+"",b=[],c=0,d,h;I(e===1d||+e<0)e=5D;Y{e=1Q.3i(+e);I(!e)H[]}O(f=M.3c(f);d=f.X(a);){I(f.12>c){b.U(a.1a(c,d.P));d.L>1&&d.P=e)1N}f.12===d.P&&f.12++}I(c===a.L){I(!n.1A.W(f,"")||h)b.U("")}Y b.U(a.1a(c));H b.L>e?b.1a(0,e):b};M.1h(/\\(\\?#[^)]*\\)/,6(f){H n.1A.W(A,f.2S.1a(f.P+f[0].L))?"":"(?:)"});M.1h(/\\((?!\\?)/,6(){J.19.U(N);H"("});M.1h(/\\(\\?<([$\\w]+)>/,6(f){J.19.U(f[1]);J.2N=R;H"("});M.1h(/\\\\k<([\\w$]+)>/,6(f){K e=p(J.19,f[1]);H e>-1?"\\\\"+(e+1)+(3R(f.2S.3a(f.P+f[0].L))?"":"(?:)"):f[0]});M.1h(/\\[\\^?]/,6(f){H f[0]==="[]"?"\\\\b\\\\B":"[\\\\s\\\\S]"});M.1h(/^\\(\\?([5A]+)\\)/,6(f){J.3d(f[1]);H""});M.1h(/(?:\\s+|#.*)+/,6(f){H n.1A.W(A,f.2S.1a(f.P+f[0].L))?"":"(?:)"},M.1B,6(){H J.2K("x")});M.1h(/\\./,6(){H"[\\\\s\\\\S]"},M.1B,6(){H J.2K("s")})})();1j 2e!="1d"&&(2e.M=M);K 1v=6(){6 r(a,b){a.1l.1i(b)!=-1||(a.1l+=" "+b)}6 t(a){H a.1i("3e")==0?a:"3e"+a}6 B(a){H e.1Y.2A[t(a)]}6 p(a,b,c){I(a==N)H N;K d=c!=R?a.3G:[a.2G],h={"#":"1c",".":"1l"}[b.1o(0,1)]||"3h",g,i;g=h!="3h"?b.1o(1):b.5u();I((a[h]||"").1i(g)!=-1)H a;O(a=0;d&&a\'+c+""});H a}6 n(a,b){a.1e("\\n");O(K c="",d=0;d<50;d++)c+=" ";H a=v(a,6(h){I(h.1i("\\t")==-1)H h;O(K g=0;(g=h.1i("\\t"))!=-1;)h=h.1o(0,g)+c.1o(0,b-g%b)+h.1o(g+1,h.L);H h})}6 x(a){H a.Q(/^\\s+|\\s+$/g,"")}6 D(a,b){I(a.Pb.P)H 1;Y I(a.Lb.L)H 1;H 0}6 y(a,b){6 c(k){H k[0]}O(K d=N,h=[],g=b.2D?b.2D:c;(d=b.1I.X(a))!=N;){K i=g(d,b);I(1j i=="3f")i=[1f e.2L(i,d.P,b.23)];h=h.1O(i)}H h}6 E(a){K b=/(.*)((&1G;|&1y;).*)/;H a.Q(e.3A.3M,6(c){K d="",h=N;I(h=b.X(c)){c=h[1];d=h[2]}H\'\'+c+""+d})}6 z(){O(K a=1E.36("1k"),b=[],c=0;c<1z 4I="1Z://2y.3L.3K/4L/5L"><3J><4N 1Z-4M="5G-5M" 6K="2O/1z; 6J=6I-8" /><1t>6L 1v<3B 1L="25-6M:6Q,6P,6O,6N-6F;6y-2f:#6x;2f:#6w;25-22:6v;2O-3D:3C;">1v3v 3.0.76 (72 73 3x)1Z://3u.2w/1v70 17 6U 71.6T 6X-3x 6Y 6D.6t 61 60 J 1k, 5Z 5R 5V <2R/>5U 5T 5S!\'}},1Y:{2j:N,2A:{}},1U:{},3A:{6n:/\\/\\*[\\s\\S]*?\\*\\//2c,6m:/\\/\\/.*$/2c,6l:/#.*$/2c,6k:/"([^\\\\"\\n]|\\\\.)*"/g,6o:/\'([^\\\\\'\\n]|\\\\.)*\'/g,6p:1f M(\'"([^\\\\\\\\"]|\\\\\\\\.)*"\',"3z"),6s:1f M("\'([^\\\\\\\\\']|\\\\\\\\.)*\'","3z"),6q:/(&1y;|<)!--[\\s\\S]*?--(&1G;|>)/2c,3M:/\\w+:\\/\\/[\\w-.\\/?%&=:@;]*/g,6a:{18:/(&1y;|<)\\?=?/g,1b:/\\?(&1G;|>)/g},69:{18:/(&1y;|<)%=?/g,1b:/%(&1G;|>)/g},6d:{18:/(&1y;|<)\\s*1k.*?(&1G;|>)/2T,1b:/(&1y;|<)\\/\\s*1k\\s*(&1G;|>)/2T}},16:{1H:6(a){6 b(i,k){H e.16.2o(i,k,e.13.1x[k])}O(K c=\'\',d=e.16.2x,h=d.2X,g=0;g";H c},2o:6(a,b,c){H\'<2W>\'+c+""},2b:6(a){K b=a.1F,c=b.1l||"";b=B(p(b,".20",R).1c);K d=6(h){H(h=15(h+"6f(\\\\w+)").X(c))?h[1]:N}("6g");b&&d&&e.16.2x[d].2B(b);a.3N()},2x:{2X:["21","2P"],21:{1H:6(a){I(a.V("2l")!=R)H"";K b=a.V("1t");H e.16.2o(a,"21",b?b:e.13.1x.21)},2B:6(a){a=1E.6j(t(a.1c));a.1l=a.1l.Q("47","")}},2P:{2B:6(){K a="68=0";a+=", 18="+(31.30-33)/2+", 32="+(31.2Z-2Y)/2+", 30=33, 2Z=2Y";a=a.Q(/^,/,"");a=1P.6Z("","38",a);a.2C();K b=a.1E;b.6W(e.13.1x.37);b.6V();a.2C()}}}},35:6(a,b){K c;I(b)c=[b];Y{c=1E.36(e.13.34);O(K d=[],h=0;h(.*?))\\\\]$"),s=1f M("(?<27>[\\\\w-]+)\\\\s*:\\\\s*(?<1T>[\\\\w-%#]+|\\\\[.*?\\\\]|\\".*?\\"|\'.*?\')\\\\s*;?","g");(j=s.X(k))!=N;){K o=j.1T.Q(/^[\'"]|[\'"]$/g,"");I(o!=N&&m.1A(o)){o=m.X(o);o=o.2V.L>0?o.2V.1e(/\\s*,\\s*/):[]}l[j.27]=o}g={1F:g,1n:C(i,l)};g.1n.1D!=N&&d.U(g)}H d},1M:6(a,b){K c=J.35(a,b),d=N,h=e.13;I(c.L!==0)O(K g=0;g")==o-3){m=m.4h(0,o-3);s=R}l=s?m:l}I((i.1t||"")!="")k.1t=i.1t;k.1D=j;d.2Q(k);b=d.2F(l);I((i.1c||"")!="")b.1c=i.1c;i.2G.74(b,i)}}},2E:6(a){w(1P,"4k",6(){e.1M(a)})}};e.2E=e.2E;e.1M=e.1M;e.2L=6(a,b,c){J.1T=a;J.P=b;J.L=a.L;J.23=c;J.1V=N};e.2L.Z.1q=6(){H J.1T};e.4l=6(a){6 b(j,l){O(K m=0;md)1N;Y I(g.P==c.P&&g.L>c.L)a[b]=N;Y I(g.P>=c.P&&g.P\'+c+""},3Q:6(a,b){K c="",d=a.1e("\\n").L,h=2u(J.V("2i-1s")),g=J.V("2z-1s-2t");I(g==R)g=(h+d-1).1q().L;Y I(3R(g)==R)g=0;O(K i=0;i\'+j+"":"")+i)}H a},4f:6(a){H a?"<4a>"+a+"":""},4b:6(a,b){6 c(l){H(l=l?l.1V||g:g)?l+" ":""}O(K d=0,h="",g=J.V("1D",""),i=0;i|&1y;2R\\s*\\/?&1G;/2T;I(e.13.46==R)b=b.Q(h,"\\n");I(e.13.44==R)b=b.Q(h,"");b=b.1e("\\n");h=/^\\s*/;g=4Q;O(K i=0;i0;i++){K k=b[i];I(x(k).L!=0){k=h.X(k);I(k==N){a=a;1N a}g=1Q.4q(k[0].L,g)}}I(g>0)O(i=0;i\'+(J.V("16")?e.16.1H(J):"")+\'<3Z 5z="0" 5H="0" 5J="0">\'+J.4f(J.V("1t"))+"<3T><3P>"+(1u?\'<2d 1g="1u">\'+J.3Q(a)+"":"")+\'<2d 1g="17">\'+b+""},2F:6(a){I(a===N)a="";J.17=a;K b=J.3Y("T");b.3X=J.1H(a);J.V("16")&&w(p(b,".16"),"5c",e.16.2b);J.V("3V-17")&&w(p(b,".17"),"56",f);H b},2Q:6(a){J.1c=""+1Q.5d(1Q.5n()*5k).1q();e.1Y.2A[t(J.1c)]=J;J.1n=C(e.2v,a||{});I(J.V("2k")==R)J.1n.16=J.1n.1u=11},5j:6(a){a=a.Q(/^\\s+|\\s+$/g,"").Q(/\\s+/g,"|");H"\\\\b(?:"+a+")\\\\b"},5f:6(a){J.28={18:{1I:a.18,23:"1k"},1b:{1I:a.1b,23:"1k"},17:1f M("(?<18>"+a.18.1m+")(?<17>.*?)(?<1b>"+a.1b.1m+")","5o")}}};H e}();1j 2e!="1d"&&(2e.1v=1v);',62,441,'||||||function|||||||||||||||||||||||||||||||||||||return|if|this|var|length|XRegExp|null|for|index|replace|true||div|push|getParam|call|exec|else|prototype||false|lastIndex|config|arguments|RegExp|toolbar|code|left|captureNames|slice|right|id|undefined|split|new|class|addToken|indexOf|typeof|script|className|source|params|substr|apply|toString|String|line|title|gutter|SyntaxHighlighter|_xregexp|strings|lt|html|test|OUTSIDE_CLASS|match|brush|document|target|gt|getHtml|regex|global|join|style|highlight|break|concat|window|Math|isRegExp|throw|value|brushes|brushName|space|alert|vars|http|syntaxhighlighter|expandSource|size|css|case|font|Fa|name|htmlScript|dA|can|handler|gm|td|exports|color|in|href|first|discoveredBrushes|light|collapse|object|cache|getButtonHtml|trigger|pattern|getLineHtml|nbsp|numbers|parseInt|defaults|com|items|www|pad|highlighters|execute|focus|func|all|getDiv|parentNode|navigator|INSIDE_CLASS|regexList|hasFlag|Match|useScriptTags|hasNamedCapture|text|help|init|br|input|gi|Error|values|span|list|250|height|width|screen|top|500|tagName|findElements|getElementsByTagName|aboutDialog|_blank|appendChild|charAt|Array|copyAsGlobal|setFlag|highlighter_|string|attachEvent|nodeName|floor|backref|output|the|TypeError|sticky|Za|iterate|freezeTokens|scope|type|textarea|alexgorbatchev|version|margin|2010|005896|gs|regexLib|body|center|align|noBrush|require|childNodes|DTD|xhtml1|head|org|w3|url|preventDefault|container|tr|getLineNumbersHtml|isNaN|userAgent|tbody|isLineHighlighted|quick|void|innerHTML|create|table|links|auto|smart|tab|stripBrs|tabs|bloggerMode|collapsed|plain|getCodeLinesHtml|caption|getMatchesHtml|findMatches|figureOutLineNumbers|removeNestedMatches|getTitleHtml|brushNotHtmlScript|substring|createElement|Highlighter|load|HtmlScript|Brush|pre|expand|multiline|min|Can|ignoreCase|find|blur|extended|toLowerCase|aliases|addEventListener|innerText|textContent|wasn|select|createTextNode|removeChild|option|same|frame|xmlns|dtd|twice|1999|equiv|meta|htmlscript|transitional|1E3|expected|PUBLIC|DOCTYPE|on|W3C|XHTML|TR|EN|Transitional||configured|srcElement|Object|after|run|dblclick|matchChain|valueOf|constructor|default|switch|click|round|execAt|forHtmlScript|token|gimy|functions|getKeywords|1E6|escape|within|random|sgi|another|finally|supply|MSIE|ie|toUpperCase|catch|returnValue|definition|event|border|imsx|constructing|one|Infinity|from|when|Content|cellpadding|flags|cellspacing|try|xhtml|Type|spaces|2930402|hosted_button_id|lastIndexOf|donate|active|development|keep|to|xclick|_s|Xml|please|like|you|paypal|cgi|cmd|webscr|bin|highlighted|scrollbars|aspScriptTags|phpScriptTags|sort|max|scriptScriptTags|toolbar_item|_|command|command_|number|getElementById|doubleQuotedString|singleLinePerlComments|singleLineCComments|multiLineCComments|singleQuotedString|multiLineDoubleQuotedString|xmlComments|alt|multiLineSingleQuotedString|If|https|1em|000|fff|background|5em|xx|bottom|75em|Gorbatchev|large|serif|CDATA|continue|utf|charset|content|About|family|sans|Helvetica|Arial|Geneva|3em|nogutter|Copyright|syntax|close|write|2004|Alex|open|JavaScript|highlighter|July|02|replaceChild|offset|83'.split('|'),0,{})) 18 | -------------------------------------------------------------------------------- /public/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* Eric Meyer's Resetting Again */ 2 | html, body, div, span, applet, object, iframe, 3 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 4 | a, abbr, acronym, address, big, cite, code, 5 | del, dfn, em, font, img, ins, kbd, q, s, samp, 6 | small, strike, strong, sub, sup, tt, var, 7 | b, u, i, center, 8 | dl, dt, dd, ol, ul, li, 9 | fieldset, form, label, legend, 10 | table, caption, tbody, tfoot, thead, tr, th, td { 11 | margin: 0; 12 | padding: 0; 13 | border: 0; 14 | outline: 0; 15 | font-size: 100%; 16 | vertical-align: baseline; 17 | background: transparent; 18 | } 19 | body { 20 | line-height: 1; 21 | } 22 | ol, ul { 23 | list-style: none; 24 | } 25 | blockquote, q { 26 | quotes: none; 27 | } 28 | 29 | /* remember to define focus styles! */ 30 | :focus { 31 | outline: 0; 32 | } 33 | 34 | /* remember to highlight inserts somehow! */ 35 | ins { 36 | text-decoration: none; 37 | } 38 | del { 39 | text-decoration: line-through; 40 | } 41 | 42 | /* tables still need 'cellspacing="0"' in the markup */ 43 | table { 44 | border-collapse: collapse; 45 | border-spacing: 0; 46 | } 47 | 48 | 49 | /* Default Styles */ 50 | 51 | body { 52 | border-top:4px solid #B3CCFE; 53 | margin: 0; 54 | padding: 0; 55 | font-family: arial, sans-serif; 56 | font-size: 13px; 57 | color: #231f14; 58 | line-height: 1.6em; 59 | } 60 | 61 | h2, h3, h4, h5, h6 { color: #3D5A96; } 62 | h2 { font-size: 28px; margin-bottom: 0.4em; letter-spacing: -1px; line-height: 32px; } 63 | h3 { font-size: 20px; margin: 0.8em 0; } 64 | h4 { font-size: 16px; margin: 0.5em 0; } 65 | 66 | a { color: #3D5A96; } 67 | a:visited { color: #5881D8;} 68 | a:hover { color: #90B4FE; } 69 | 70 | p { margin: 0.33em 0pt 1em; } 71 | 72 | pre { 73 | border: 1px solid gray; 74 | background-color: #F8F8F8; 75 | padding: .6em; 76 | margin: .6em; 77 | } 78 | 79 | strong { 80 | font-weight: bold; 81 | } 82 | 83 | em { 84 | font-style: italic; 85 | } 86 | 87 | code.inline-syntax { color: #4C5770; font-family: "Consolas","Bitstream Vera Sans Mono","Courier New",Courier,monospace; font-weight: 700; font-size: 14px; padding: 0 2px;} 88 | 89 | ol li { list-style-type: decimal; margin-left: 20px; } 90 | 91 | #header, #footer, #content { 92 | max-width: 740px; 93 | padding: 0 10px; 94 | margin: 0 auto; 95 | } 96 | #browser #header, #browser #footer, #browser #content { 97 | width: 940px; 98 | } 99 | 100 | /* Header styles */ 101 | #header { padding: 10px 0 0; } 102 | h2.logo { padding-left: 100px; height: 82px; background: url(clojure_logo.jpg) top left no-repeat; line-height: 82px;} 103 | 104 | #breadcrumb { height: 20px; } 105 | #breadcrumb #previous { float: left; } 106 | #breadcrumb #next {float: right } 107 | 108 | /* Content styles */ 109 | #content { padding: 0 0 10px; } 110 | 111 | div.browse-list { height: 200px; overflow: scroll; overflow-x: auto; width: 50%; display:inline-block; border-bottom: 1px solid #ddd; border-top: 1px solid #ddd; } 112 | div.browse-list ul li:nth-child(odd) { background-color: #EDF3FE; } 113 | div.browse-list ul li a { padding: 2px 5px; display: block; } 114 | div.browse-list ul li a:hover { background-color: #eee; color: #3881D8;} 115 | 116 | a.top-link { margin: 10px 0; display: block; } 117 | 118 | /* Syntax Highlighter Styles */ 119 | div.syntaxhighlighter { border: 1px solid #ddd !important; padding: 0.6em !important; margin: 0.6em !important; font-size: 14px !important; } 120 | #browser div.syntaxhighlighter { width: auto !important; } 121 | div.syntaxhighlighter.nogutter div.line { background-color: #F8F8F8 !important; } 122 | 123 | /* Footer styles */ 124 | #footer { padding: 10px 0; color: #a09b97; border-top: solid 1px #ddd; } 125 | #footer a { color: #6f6c69; } 126 | #footer a:hover { color: #514e4c; } 127 | -------------------------------------------------------------------------------- /public/stylesheets/clojure_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevance/labrepl/51909dcb3eeb9f148eeae109316f7d73cc81512a/public/stylesheets/clojure_logo.jpg -------------------------------------------------------------------------------- /public/stylesheets/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevance/labrepl/51909dcb3eeb9f148eeae109316f7d73cc81512a/public/stylesheets/help.png -------------------------------------------------------------------------------- /public/stylesheets/magnifier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevance/labrepl/51909dcb3eeb9f148eeae109316f7d73cc81512a/public/stylesheets/magnifier.png -------------------------------------------------------------------------------- /public/stylesheets/page_white_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevance/labrepl/51909dcb3eeb9f148eeae109316f7d73cc81512a/public/stylesheets/page_white_code.png -------------------------------------------------------------------------------- /public/stylesheets/page_white_copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevance/labrepl/51909dcb3eeb9f148eeae109316f7d73cc81512a/public/stylesheets/page_white_copy.png -------------------------------------------------------------------------------- /public/stylesheets/printer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/relevance/labrepl/51909dcb3eeb9f148eeae109316f7d73cc81512a/public/stylesheets/printer.png -------------------------------------------------------------------------------- /public/stylesheets/shCore.css: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | .syntaxhighlighter a, 18 | .syntaxhighlighter div, 19 | .syntaxhighlighter code, 20 | .syntaxhighlighter table, 21 | .syntaxhighlighter table td, 22 | .syntaxhighlighter table tr, 23 | .syntaxhighlighter table tbody, 24 | .syntaxhighlighter table thead, 25 | .syntaxhighlighter table caption, 26 | .syntaxhighlighter textarea { 27 | -moz-border-radius: 0 0 0 0 !important; 28 | -webkit-border-radius: 0 0 0 0 !important; 29 | background: none !important; 30 | border: 0 !important; 31 | bottom: auto !important; 32 | float: none !important; 33 | height: auto !important; 34 | left: auto !important; 35 | line-height: 1.1em !important; 36 | margin: 0 !important; 37 | outline: 0 !important; 38 | overflow: visible !important; 39 | padding: 0 !important; 40 | position: static !important; 41 | right: auto !important; 42 | text-align: left !important; 43 | top: auto !important; 44 | vertical-align: baseline !important; 45 | width: auto !important; 46 | box-sizing: content-box !important; 47 | font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; 48 | font-weight: normal !important; 49 | font-style: normal !important; 50 | font-size: 1em !important; 51 | min-height: inherit !important; 52 | min-height: auto !important; 53 | } 54 | 55 | .syntaxhighlighter { 56 | margin: 1em 0 1em 0 !important; 57 | position: relative !important; 58 | overflow: auto !important; 59 | font-size: 1em !important; 60 | } 61 | .syntaxhighlighter.source { 62 | overflow: hidden !important; 63 | } 64 | .syntaxhighlighter .bold { 65 | font-weight: bold !important; 66 | } 67 | .syntaxhighlighter .italic { 68 | font-style: italic !important; 69 | } 70 | .syntaxhighlighter .line { 71 | white-space: pre !important; 72 | } 73 | .syntaxhighlighter table { 74 | width: 100% !important; 75 | } 76 | .syntaxhighlighter table caption { 77 | text-align: left !important; 78 | padding: .5em 0 0.5em 1em !important; 79 | } 80 | .syntaxhighlighter table td.code { 81 | width: 100% !important; 82 | } 83 | .syntaxhighlighter table td.code .container { 84 | position: relative !important; 85 | } 86 | .syntaxhighlighter table td.code .container textarea { 87 | box-sizing: border-box !important; 88 | position: absolute !important; 89 | left: 0 !important; 90 | top: 0 !important; 91 | width: 100% !important; 92 | height: 100% !important; 93 | border: none !important; 94 | background: white !important; 95 | padding-left: 1em !important; 96 | overflow: hidden !important; 97 | white-space: pre !important; 98 | } 99 | .syntaxhighlighter table td.gutter .line { 100 | text-align: right !important; 101 | padding: 0 0.5em 0 1em !important; 102 | } 103 | .syntaxhighlighter table td.code .line { 104 | padding: 0 1em !important; 105 | } 106 | .syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line { 107 | padding-left: 0em !important; 108 | } 109 | .syntaxhighlighter.show { 110 | display: block !important; 111 | } 112 | .syntaxhighlighter.collapsed table { 113 | display: none !important; 114 | } 115 | .syntaxhighlighter.collapsed .toolbar { 116 | padding: 0.1em 0.8em 0em 0.8em !important; 117 | font-size: 1em !important; 118 | position: static !important; 119 | width: auto !important; 120 | height: auto !important; 121 | } 122 | .syntaxhighlighter.collapsed .toolbar span { 123 | display: inline !important; 124 | margin-right: 1em !important; 125 | } 126 | .syntaxhighlighter.collapsed .toolbar span a { 127 | padding: 0 !important; 128 | display: none !important; 129 | } 130 | .syntaxhighlighter.collapsed .toolbar span a.expandSource { 131 | display: inline !important; 132 | } 133 | .syntaxhighlighter .toolbar { 134 | position: absolute !important; 135 | right: 1px !important; 136 | top: 1px !important; 137 | width: 11px !important; 138 | height: 11px !important; 139 | font-size: 10px !important; 140 | z-index: 10 !important; 141 | } 142 | .syntaxhighlighter .toolbar span.title { 143 | display: inline !important; 144 | } 145 | .syntaxhighlighter .toolbar a { 146 | display: block !important; 147 | text-align: center !important; 148 | text-decoration: none !important; 149 | padding-top: 1px !important; 150 | } 151 | .syntaxhighlighter .toolbar a.expandSource { 152 | display: none !important; 153 | } 154 | .syntaxhighlighter.ie { 155 | font-size: .9em !important; 156 | padding: 1px 0 1px 0 !important; 157 | } 158 | .syntaxhighlighter.ie .toolbar { 159 | line-height: 8px !important; 160 | } 161 | .syntaxhighlighter.ie .toolbar a { 162 | padding-top: 0px !important; 163 | } 164 | .syntaxhighlighter.printing .line.alt1 .content, 165 | .syntaxhighlighter.printing .line.alt2 .content, 166 | .syntaxhighlighter.printing .line.highlighted .number, 167 | .syntaxhighlighter.printing .line.highlighted.alt1 .content, 168 | .syntaxhighlighter.printing .line.highlighted.alt2 .content { 169 | background: none !important; 170 | } 171 | .syntaxhighlighter.printing .line .number { 172 | color: #bbbbbb !important; 173 | } 174 | .syntaxhighlighter.printing .line .content { 175 | color: black !important; 176 | } 177 | .syntaxhighlighter.printing .toolbar { 178 | display: none !important; 179 | } 180 | .syntaxhighlighter.printing a { 181 | text-decoration: none !important; 182 | } 183 | .syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a { 184 | color: black !important; 185 | } 186 | .syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a { 187 | color: #008200 !important; 188 | } 189 | .syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a { 190 | color: blue !important; 191 | } 192 | .syntaxhighlighter.printing .keyword { 193 | color: #006699 !important; 194 | font-weight: bold !important; 195 | } 196 | .syntaxhighlighter.printing .preprocessor { 197 | color: gray !important; 198 | } 199 | .syntaxhighlighter.printing .variable { 200 | color: #aa7700 !important; 201 | } 202 | .syntaxhighlighter.printing .value { 203 | color: #009900 !important; 204 | } 205 | .syntaxhighlighter.printing .functions { 206 | color: #ff1493 !important; 207 | } 208 | .syntaxhighlighter.printing .constants { 209 | color: #0066cc !important; 210 | } 211 | .syntaxhighlighter.printing .script { 212 | font-weight: bold !important; 213 | } 214 | .syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a { 215 | color: gray !important; 216 | } 217 | .syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a { 218 | color: #ff1493 !important; 219 | } 220 | .syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a { 221 | color: red !important; 222 | } 223 | .syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a { 224 | color: black !important; 225 | } 226 | -------------------------------------------------------------------------------- /public/stylesheets/shThemeDefault.css: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | .syntaxhighlighter { 18 | background-color: white !important; 19 | } 20 | .syntaxhighlighter .line.alt1 { 21 | background-color: white !important; 22 | } 23 | .syntaxhighlighter .line.alt2 { 24 | background-color: white !important; 25 | } 26 | .syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { 27 | background-color: #e0e0e0 !important; 28 | } 29 | .syntaxhighlighter .line.highlighted.number { 30 | color: black !important; 31 | } 32 | .syntaxhighlighter table caption { 33 | color: black !important; 34 | } 35 | .syntaxhighlighter .gutter { 36 | color: #afafaf !important; 37 | } 38 | .syntaxhighlighter .gutter .line { 39 | border-right: 3px solid #6ce26c !important; 40 | } 41 | .syntaxhighlighter .gutter .line.highlighted { 42 | background-color: #6ce26c !important; 43 | color: white !important; 44 | } 45 | .syntaxhighlighter.printing .line .content { 46 | border: none !important; 47 | } 48 | .syntaxhighlighter.collapsed { 49 | overflow: visible !important; 50 | } 51 | .syntaxhighlighter.collapsed .toolbar { 52 | color: blue !important; 53 | background: white !important; 54 | border: 1px solid #6ce26c !important; 55 | } 56 | .syntaxhighlighter.collapsed .toolbar a { 57 | color: blue !important; 58 | } 59 | .syntaxhighlighter.collapsed .toolbar a:hover { 60 | color: red !important; 61 | } 62 | .syntaxhighlighter .toolbar { 63 | color: white !important; 64 | background: #6ce26c !important; 65 | border: none !important; 66 | } 67 | .syntaxhighlighter .toolbar a { 68 | color: white !important; 69 | } 70 | .syntaxhighlighter .toolbar a:hover { 71 | color: black !important; 72 | } 73 | .syntaxhighlighter .plain, .syntaxhighlighter .plain a { 74 | color: black !important; 75 | } 76 | .syntaxhighlighter .comments, .syntaxhighlighter .comments a { 77 | color: #008200 !important; 78 | } 79 | .syntaxhighlighter .string, .syntaxhighlighter .string a { 80 | color: blue !important; 81 | } 82 | .syntaxhighlighter .keyword { 83 | color: #006699 !important; 84 | } 85 | .syntaxhighlighter .preprocessor { 86 | color: gray !important; 87 | } 88 | .syntaxhighlighter .variable { 89 | color: #aa7700 !important; 90 | } 91 | .syntaxhighlighter .value { 92 | color: #009900 !important; 93 | } 94 | .syntaxhighlighter .functions { 95 | color: #ff1493 !important; 96 | } 97 | .syntaxhighlighter .constants { 98 | color: #0066cc !important; 99 | } 100 | .syntaxhighlighter .script { 101 | font-weight: bold !important; 102 | color: #006699 !important; 103 | background-color: none !important; 104 | } 105 | .syntaxhighlighter .color1, .syntaxhighlighter .color1 a { 106 | color: gray !important; 107 | } 108 | .syntaxhighlighter .color2, .syntaxhighlighter .color2 a { 109 | color: #ff1493 !important; 110 | } 111 | .syntaxhighlighter .color3, .syntaxhighlighter .color3 a { 112 | color: red !important; 113 | } 114 | 115 | .syntaxhighlighter .keyword { 116 | font-weight: bold !important; 117 | } 118 | -------------------------------------------------------------------------------- /resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=info, R 2 | 3 | log4j.appender.R=org.apache.log4j.RollingFileAppender 4 | log4j.appender.R.File=logs/labrepl.log 5 | log4j.appender.R.MaxFileSize=1000KB 6 | log4j.appender.R.MaxBackupIndex=1 7 | log4j.appender.R.layout=org.apache.log4j.PatternLayout 8 | log4j.appender.R.layout.ConversionPattern=%d{ISO8601} %-5p [%c] - %m%n 9 | -------------------------------------------------------------------------------- /src/handlers.clj: -------------------------------------------------------------------------------- 1 | (ns handlers 2 | (:use [clojure.tools.logging :only (info)])) 3 | 4 | (defn with-logging [handler] 5 | (fn [request] 6 | (let [start (System/nanoTime) 7 | response (handler request) 8 | elapsed (/ (double (- (System/nanoTime) start)) 1000000.0)] 9 | (when response 10 | (info (str (.toUpperCase (name (:request-method request))) " " (:uri request) " " elapsed " milliseconds")) 11 | response)))) -------------------------------------------------------------------------------- /src/labrepl.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:author "Stu Halloway" 2 | :doc "Compojure app that displays lab instructions."} 3 | labrepl 4 | (:use [clojure.tools.logging :only (info)] 5 | [compojure.core :only (defroutes GET)] 6 | [ring.adapter.jetty :only (run-jetty)] 7 | [labrepl.util :only (make-url)]) 8 | (:require [compojure.route :as route] 9 | [handlers :as handlers] 10 | [labrepl.layout :as layout])) 11 | 12 | (def all [:intro 13 | :names-and-places 14 | :its-all-data 15 | :looping 16 | :project-euler 17 | :mini-browser 18 | :unified-update-model 19 | :zero-sum 20 | :cellular-automata 21 | :defstrict 22 | :rock-paper-scissors]) 23 | 24 | (defn home [] 25 | (layout/home 26 | [:ul 27 | (map 28 | (fn [lab] [:li (make-url lab)]) 29 | all)])) 30 | 31 | (defn instructions 32 | [lab] 33 | ((ns-resolve lab 'instructions))) 34 | 35 | (defn render-lab [lab] 36 | (let [lab-ns (symbol (str "labs." lab))] 37 | (require lab-ns) 38 | (layout/lab [:h2 lab] 39 | (meta (find-ns lab-ns)) 40 | (instructions lab-ns)))) 41 | 42 | (defroutes lab-routes 43 | (GET "/" [] (home)) 44 | (GET "/labs/:name" [name] (render-lab name)) 45 | (route/files "/") 46 | (route/not-found "

Not Found

")) 47 | 48 | (def application (-> lab-routes 49 | handlers/with-logging)) 50 | 51 | (defn -main [& args] 52 | (run-jetty (var application) {:port 8080 :join? false}) 53 | (println "Welcome to the labrepl. Browse to http://localhost:8080 to get started!")) 54 | 55 | (comment 56 | (require 'labrepl) 57 | 58 | (labrepl/application {:request-method :get 59 | :uri "/nope"}) 60 | (labrepl/application {:request-method :get 61 | :uri "/labs/intro"}) 62 | 63 | ) 64 | -------------------------------------------------------------------------------- /src/labrepl/layout.clj: -------------------------------------------------------------------------------- 1 | (ns labrepl.layout 2 | (:use [hiccup.core :only (html)] 3 | [hiccup.page-helpers :only (include-css include-js link-to)])) 4 | 5 | (def default-stylesheets 6 | ["/stylesheets/shCore.css" 7 | "/stylesheets/shThemeDefault.css" 8 | "/stylesheets/application.css"]) 9 | 10 | (def default-javascripts 11 | ["/javascripts/jquery.js" 12 | "/javascripts/application.js" 13 | "/javascripts/shCore.js" 14 | "/javascripts/shBrushClojure.js"]) 15 | 16 | (defn home [body] 17 | (html 18 | [:head 19 | [:title "Clojure Labs"] 20 | (apply include-css default-stylesheets) 21 | (apply include-js default-javascripts)] 22 | [:body [:div {:id "header"} [:h2.logo "Clojure Labs"]] 23 | [:div {:id "content"} body] 24 | [:div {:id "footer"} "Clojure labrepl. Copyright Relevance Inc. All Rights Reserved."]])) 25 | 26 | (defn navigation [link-data] 27 | [:div {:id "breadcrumb"} 28 | [:div {:id "previous"} (if-let [prev (:prev link-data)] 29 | (link-to (:prev-url link-data) (str "Previous Lab: " prev)) 30 | (link-to "/" "Home"))] 31 | [:div {:id "next"} (if-let [next (:next link-data)] 32 | (link-to (:next-url link-data) (str "Next Lab: " next)) 33 | (link-to "/" "Home"))]]) 34 | 35 | (defn lab [title link-data & body] 36 | {:pre [(string? (last title))]} 37 | (html 38 | [:head 39 | [:title (last title)] 40 | (apply include-css default-stylesheets) 41 | (apply include-js default-javascripts)] 42 | [:body [:div {:id "header"} title] 43 | [:div {:id "content"} 44 | (navigation link-data) 45 | body] 46 | [:div {:id "footer"} "Clojure labrepl. Copyright Relevance Inc. All Rights Reserved."]])) 47 | -------------------------------------------------------------------------------- /src/labrepl/util.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:author "Stu Halloway" 2 | :doc "Utilities for creating lab instruction pages."} 3 | labrepl.util 4 | (:use [clojure.pprint :only (pprint)] 5 | [clojure.repl :only (source-fn)]) 6 | (:require [clojure.string :as s])) 7 | 8 | (defn lab-url 9 | [lab-name] 10 | (str "/labs/" (name lab-name))) 11 | 12 | (defn make-url 13 | [lab] 14 | [:a {:href (lab-url lab)} (name lab)]) 15 | 16 | (defn format-code 17 | [& codes] 18 | (apply str (map 19 | (fn [code] 20 | (if (string? code) 21 | (str code "\n") 22 | (with-out-str (pprint code)))) 23 | codes))) 24 | 25 | (defn one-liner? 26 | [s] 27 | (if s 28 | (< (count (remove empty? (s/split s #"\s*\n\s*"))) 2) 29 | true)) 30 | 31 | (defn code* 32 | "Show codes (literal strings or forms) in a pre/code block." 33 | [& codes] 34 | (let [code-string (apply format-code codes) 35 | class-string "brush: clojure; toolbar: false; gutter: false;" 36 | class-string (if (one-liner? code-string) (str class-string " light: true;") class-string)] 37 | [:script {:type "syntaxhighlighter" :class class-string} 38 | (str "")])) 39 | 40 | (defmacro code 41 | "Show codes (literal strings or forms) in a pre/code block." 42 | [& codes] 43 | `(apply code* '~codes)) 44 | 45 | (defmacro c 46 | "Show code (symbol or string) literally inline." 47 | [code] 48 | (let [code (if (string? code) (symbol code) code)] 49 | `[:code {:class "inline-syntax"} (s/trim-newline (with-out-str (pprint '~code)))])) 50 | 51 | (defmacro source 52 | "Show source code in a pre block." 53 | [sym] 54 | `(code ~(source-fn sym))) 55 | 56 | (defn showme* 57 | [code-string] 58 | [:div {:class "toggle"} 59 | [:div [:a {:href "javascript:void(null)"} "Show Code"]] 60 | [:div {:style "display:none;"} [:a {:href "javascript:void(null)"} "Hide Code"] 61 | (code* code-string)]]) 62 | 63 | (defmacro showme 64 | "Show code (literal string or source from a var) in a 65 | 'Show Me' block students can click to reveal." 66 | [str-or-sym] 67 | (let [c (if (symbol? str-or-sym) (source-fn str-or-sym) str-or-sym)] 68 | `(showme* '~c))) 69 | 70 | (defn output-indent 71 | "Indent output lines with '-> / ' (for showing REPL results)." 72 | [lines] 73 | (str 74 | "-> " 75 | (s/join "\n " 76 | (s/split (with-out-str (pprint lines)) #"\n")))) 77 | 78 | (defmacro returning-exceptions 79 | "Return exception info in string instead of blowing up." 80 | [& forms] 81 | `(try 82 | ~@forms 83 | (catch Exception e# (str (.getClass e#) " " (.getMessage e#))))) 84 | 85 | (defn deindent 86 | "Deindent all lines of a string based on the indentation of the 87 | first line." 88 | [str] 89 | (when str 90 | (let [[line :as lines] (s/split str #"\n")] 91 | (if-let [whitespace-match (re-find #"^\s+" line)] 92 | (s/join "\n" (map #(subs % (.length whitespace-match)) lines)) 93 | str)))) 94 | 95 | (defmacro repl* 96 | "Internal helper for repl." 97 | [form] 98 | (if (instance? String form) 99 | (let [result (returning-exceptions (load-string form))] 100 | `(str 101 | ~(deindent form) 102 | "\n" 103 | (output-indent '~result))) 104 | `(str 105 | (with-out-str (pprint '~form)) 106 | (output-indent (returning-exceptions ~form))))) 107 | 108 | (defmacro repl-code 109 | [form] 110 | `(code* (repl* ~form))) 111 | 112 | (defmacro repl-showme 113 | [form] 114 | `(showme* (repl* ~form))) 115 | 116 | -------------------------------------------------------------------------------- /src/labs/cellular_automata.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:next "defstrict" :next-url "/labs/defstrict" 2 | :prev "Zero Sum" :prev-url "/labs/zero-sum"} 3 | labs.cellular-automata 4 | (:use [labrepl.util :only (c showme repl-showme repl-code)] 5 | [solutions.automaton])) 6 | 7 | (defn overview [] 8 | [[:h3 "Overview"] 9 | [:p "In this exercise, you will build a Swing GUI for displaying cellular automata such 10 | as " [:a {:href "http://en.wikipedia.org/wiki/Brian%27s_Brain"} "Brian's Brain"] " 11 | and " [:a {:href "http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life"} "Conway's 12 | Game of Life"] ". The code is inspired by blog examples by " 13 | [:a {:href "http://www.bestinclass.dk/index.php/2009/10/brians-functional-brain/"} "Lau Jensen"] " 14 | and " [:a {:href "http://blog.thinkrelevance.com/2009/10/7/brians-functional-brain-take-1-5"} "Stu Halloway" ] ". 15 | If you have never created an automaton before, read through the links above 16 | before you begin."]]) 17 | 18 | (defn groundwork [] 19 | [[:h3 "Groundwork"] 20 | [:ol 21 | [:li "First, let's define some constants: " (c dim-board) " sets the board to 22 | 90x90, " (c dim-screen) " sets the screen to 600x600, and " (c dim-scale) " 23 | expresses the ratio of screen to board." 24 | (showme dim-board) 25 | (showme dim-screen) 26 | (showme dim-scale)] 27 | [:li "A board is a list of lists, with each value either " (c :on) " or " (c :off) ". 28 | At the REPL, try using a " (c for) " expression to create a single small row, 29 | with about half of the values randomly chosen to be initially " (c :on) ":" 30 | (repl-showme (for [y (range 10)] (if (< (rand) 0.5) :on :off)))] 31 | [:li "Using two nested for expressions, create a " (c new-board) " function that 32 | expects a pair of " (c [x y]) " sizes:" 33 | (showme new-board)] 34 | [:li "Test " (c new-board) " at the REPL:" 35 | (repl-showme (new-board [10 10]))] 36 | [:li "Now let's create an empty Swing GUI to work with. I can't bear to step through 37 | the tedium, so just take a look at " (c launch-1) ":" 38 | (showme launch-1)] 39 | [:li "Run " (c launch-1) " and verify that it creates an empty window and returns a 40 | board."]]]) 41 | 42 | (defn swing-gui [] 43 | [[:h3 "Swing GUI"] 44 | [:ol 45 | [:li "Time to bring the GUI to life. Create " (c launch-2) ", which is the 46 | fleshed-out version of the trivial " (c launch-1) ". It should add lets 47 | for a " (c "BufferedImage img") " to do the drawing work offscreen, and 48 | for a " (c panel) " that proxies " (c JPanel) ". The proxy should implement " 49 | (c paint) " by calling a (yet-to-be-written) " (c render) " function." 50 | (showme launch-2)] 51 | [:li "Render is going to iterate over the board, coloring cells white, gray, or 52 | black based on their on/dying/off status. Rather than have explicit loop 53 | variables, we are going to transform the board so that each cell knows its 54 | coordinates. Create a " (c with-coords) " function that takes a board and 55 | replaces each cell with a " (c [status x y]) " tuple:" 56 | (showme with-coords)] 57 | [:li "Test " (c with-coords) " at the REPL:" 58 | (repl-showme (with-coords (new-board [3 3])))] 59 | [:li "Now define " (c render) ", which takes a graphics object, an image, and the 60 | board. It should:" 61 | [:ol 62 | [:li "Let a " (c background-graphics) " to hold " (c "(.getGraphics img)") "."] 63 | [:li "Fill the background-graphics with black."] 64 | [:li "Loop over all the cells in all the rows of " (c (with-coords board)) ", 65 | calling out to a (to-be-written) " 66 | (c (render-cell background-graphics cell)) " function."] 67 | [:li "Draw the image back into the real graphics object."]] 68 | (showme render)] 69 | [:li "Next, define " (c render-cell) ". It should scale the cell up to screen 70 | units using " (c dim-scale) ", set the color using the (to-be-written) " 71 | (c state->color) " function, and fill the cell on screen." 72 | (showme render-cell)] 73 | [:li "The " (c state->color) " function is actually just a map:" 74 | (showme state->color)] 75 | [:li "Make sure your functions are defined in the correct order, or declared 76 | before use."] 77 | [:li "Call " (c launch-2) ", and you should see one still frame from a cellular 78 | automaton."]]]) 79 | 80 | (defn let-er-rip [] 81 | [[:h3 "Let 'Er Rip"] 82 | [:ol 83 | [:li "Each cell's state in the next step depends on the current 84 | state of its eight immediate neighbors. Clojure destructuring 85 | makes it easy to extract these neighbors from a list of lists. 86 | Write an " (c active-neighbors) " function that counts all the 87 | neighbors in the " (c :on) " state:" 88 | (showme active-neighbors)] 89 | [:li "Test " (c active-neighbors) " against a board that is all on:" 90 | (repl-showme (active-neighbors (repeat (repeat :on))))] 91 | [:li "The updates rules for a cell in Brian's Brain are:" 92 | [:ol 93 | [:li "An " (c :on) " cell goes to " (c :dying)] 94 | [:li "A " (c :dying) " cell goes to " (c :off)] 95 | [:li "An " (c :off) " cell with two active (" (c :on) ") neighbors goes to " 96 | (c :on)] 97 | [:li "All other cells go to " (c off)]] 98 | "Implement " (c brians-brain-rules) " as a function of three rows." 99 | (showme brians-brain-rules)] 100 | [:li "Test the rules at the REPL." 101 | (repl-showme (brians-brain-rules nil nil nil)) 102 | (repl-showme (brians-brain-rules [:on :on] nil nil))] 103 | [:li "In order to apply the rules, we need to take the entire board in 3x3 units. 104 | This is easy to visualize in one dimension, using Clojure's partition function:" 105 | (repl-code (partition 3 1 [:a :b :c :d :e])) 106 | "Using this idiom, implement a function " (c torus-window) ", so called because 107 | it 'wraps around' by prepending the last row and appending the first row:" 108 | (showme torus-window)] 109 | [:li "The most interesting function to write is " (c step) ", which 110 | calls " (c brians-brain-rules) " once for each cell, via a combination 111 | of " (c torus-window) ", " (c map) ", and " (c apply) ":" 112 | (showme step)] 113 | [:li "The stateful part of the application is simple. We will use an atom to reference 114 | the board. Write an " (c update-board) " function that takes an atom and updates 115 | it via " (c step) ":" 116 | (showme update-board)] 117 | [:li "Almost there. Create an " (c activity-loop) " function of " (c panel) " 118 | and " (c board) " that loops as long as the " (c board) " does not refer to 119 | nil, calling " (c update-board) " and then " (c .repaint) ":" 120 | (showme activity-loop)] 121 | [:li "Finally, we need the completed " (c launch) " function. " (c launch) " builds 122 | on the previous iteratations, adding a call to " (c future) " that kicks off 123 | the " (c activity-loop) " before returning the board." 124 | (showme launch)] 125 | [:li "You are ready to go! " (c launch) " your automaton and watch it go. When you 126 | (or your CPU) grow tired, you can stop it by setting the returned atom to " 127 | (c nil) ", or by simply killing the JVM."]]]) 128 | 129 | (defn bonus [] 130 | [[:h3 "Bonus"] 131 | [:li "Load your code with " (c *warn-on-reflection*) " set to true, and then add type 132 | hints until your code is non-reflective."] 133 | [:li "What other things might make this code faster. Think about it a bit, and then 134 | read about " 135 | [:a {:href "http://www.bestinclass.dk/index.php/2009/10/brians-transient-brain/"} 136 | "Lau's experiences optimizing Brian's Brain."]] 137 | [:li "Add support for Conway's Game of Life. Hint: only one function needs to change."] 138 | [:li "Add a menu to switch between Conway and Brian mid-simulation."]]) 139 | 140 | (defn instructions [] 141 | (concat (overview) 142 | (groundwork) 143 | (swing-gui) 144 | (let-er-rip) 145 | (bonus))) 146 | -------------------------------------------------------------------------------- /src/labs/defstrict.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:next "Rock Paper Scissors" :next-url "/labs/rock-paper-scissors" 2 | :prev "Cellular Automata" :prev-url "/labs/cellular-automata"} 3 | labs.defstrict 4 | (:use [labrepl.util :only (c showme repl-code source)] 5 | solutions.defstrict)) 6 | 7 | (defn overview-ins [] 8 | [[:h3 "Overview"] 9 | [:p "Clojure macros make it easy to extend Clojure itself. Indeed, many of 10 | Clojure's own core features are implemented as macros. In this exercise, 11 | you will use macros to add a special " (c defn) " variant named " (c defstrict) " 12 | that unifies type tagging and preconditions."]]) 13 | 14 | (defn the-problem-ins [] 15 | [[:h3 "Introducing defstrict"] 16 | [:p "Static typing provides (at least!) three benefits:" 17 | [:ul 18 | [:li "Invocation can be optimized, as no reflection against the object is necessary."] 19 | [:li "Argument types can be verified to be correct."] 20 | [:li "Static analysis of programs can identify bugs, perform optimizations, etc."]]] 21 | [:p "Clojure can provide some of these benefits, in a way consistent with its a la carte, dynamic nature:" 22 | [:ul 23 | [:li "Type tags allow the Clojure compiler to inject class casts and avoid the overhead of reflective invocation."] 24 | [:li "Preconditions can verify the type of arguments at runtime. (In fact, they can do much more, 25 | testing any function against any combination of the argument list.)"]]] 26 | [:p "Functions can use type tags and preconditions in any combination, and many Clojure programs 27 | use neither. In this exercise, we will imagine an unusual project that wants both of these 28 | features on every argument to every function. We will define a " (c defstrict) " macro to capture this idiom."]]) 29 | 30 | (defn shout-ins [] 31 | [[:h3 "Shout!"] 32 | [:ol 33 | [:li "Let's create a very simple function that we can use to see type hints and preconditions in 34 | action. For our first iteration, create " (c shout-1) " that calls " (c .toUpperCase) " on its argument" 35 | (showme shout-1)] 36 | [:li "Try passing invalid arguments " (c nil) " and " (c :foo) " to " (c shout-1) ". Make sure you 37 | understand the way each fails."] 38 | [:li "Now, create " (c shout-2) " that tags its argument as a string." 39 | (showme shout-2)] 40 | [:li "Thanks to the type tag, " (c shout-2) " will no longer make a reflective invocation 41 | of " (c .toUpperCase) ". This is faster, and it also changes some failure modes. 42 | Try " (c nil) " and " (c :foo) " again."] 43 | [:li "Now create a " (c shout-3) " that uses both type tags and an " (c instance?) " 44 | precondition on its argument." 45 | (showme shout-3)] 46 | [:li "Test that " (c shout-3) " provides a more informative error message for various bad inputs."]] 47 | [:p "A la carte is nice until you want the same fixed meal every time. If you wanted to write a 48 | ton of functions that all did type tagging and instance checking, the style used in " (c shout-3) " 49 | would become tedious. Let's pretend that there is a Clojure feature called " (c defstrict) " 50 | that provides a simple syntax like this:" 51 | (source shout) 52 | "In a Lisp, you don't have to pretend. Let's implement " (c defstrict) "."]]) 53 | 54 | (defn arg-type-preconditions-ins [] 55 | [[:h3 "arg-type-preconditions"] 56 | [:p "To build " (c defstrict) " we will start with a helper function " (c arg-type-preconditions) " 57 | that converts argument specifiers into precondition forms. For example:" 58 | (repl-code "(arg-type-preconditions '[^String s ^Integer i])") "We'll build it inside-out from the REPL."] 59 | [:ol 60 | [:li "First, write an " (c instance-check) " function that checks a symbol for type hints, and 61 | emits an " (c instance?) "check." 62 | (showme instance-check)] 63 | [:li "Test " (c instance-check) " from the repl:" (repl-code "(instance-check '^String s)")] 64 | [:li "Now you can write " (c arg-type-preconditions) ". It simply maps " (c instance-check) " 65 | over all the args, removes the nils, and puts the checks into the " (c {:pre []}) " 66 | structure of a Clojure precondition." 67 | (showme arg-type-preconditions)] 68 | [:li "Test " (c arg-type-preconditions) " from the repl." 69 | (repl-code "(arg-type-preconditions '[^String a b ^Integer c])")]]]) 70 | 71 | (defn defstrict-ins [] 72 | [[:h3 "defstrict"] 73 | [:ol 74 | [:li "With the building blocks in place, " (c defstrict) " itself is simple. It takes 75 | a name, an arglist, and a variable number of body forms, and then it emits:" 76 | [:ol 77 | [:li "a " (c defn)] 78 | [:li "the name"] 79 | [:li "the argument list"] 80 | [:li "the preconditions, using " (c arg-type-preconditions)] 81 | [:li "the body forms"]] 82 | (showme defstrict)] 83 | [:li "Test " (c defstrict) " using " (c macroexpand-1) "."] 84 | [:li "Test " (c defstrict) " by defining a strict function and calling it." 85 | (showme shout)]]]) 86 | 87 | (defn bonus [] 88 | [[:h3 "Bonus"] 89 | [:ol 90 | [:li "What happens if you pass a function call instead of a class literal 91 | to " (c defstrict) "? How would you improve upon this?"] 92 | [:li "Is multiple evaluation a possible problem for any of the arguments 93 | to " (c defstrict) "? Why or why not?"]]]) 94 | 95 | (defn instructions [] 96 | (concat (overview-ins) 97 | (the-problem-ins) 98 | (shout-ins) 99 | (arg-type-preconditions-ins) 100 | (defstrict-ins) 101 | (bonus))) 102 | -------------------------------------------------------------------------------- /src/labs/intro.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:next "Names and Places" :next-url "/labs/names-and-places"} 2 | labs.intro 3 | (:use [labrepl.util :only (code)])) 4 | 5 | (defn overview [] 6 | [[:h3 "Overview"] 7 | [:p "In this lab you will learn to 8 | call functions from the Clojure Read-Eval-Print Loop (REPL), and a 9 | set of tools that you can use from the REPL to explore Clojure. Note 10 | that some of these tools are not available with a minimal install of 11 | Clojure. Later in the course you will see how to include all of 12 | these tools in your own projects."] 13 | [:p "You need to be running the 14 | labrepl to perform the steps in this lab."]]) 15 | 16 | (defn functions [] 17 | [[:h3 "Functions"] 18 | [:ol 19 | [:li "Find your labrepl window, which should have a prompt like " (code "user=> ") 20 | "This prompt tells you that you are currently working in the user 21 | namespace, which is a scratch namespace for experimenting. From the 22 | prompt, you can call functions with forms like this:" 23 | (code "(funcname arg1 arg2 ...)") 24 | [:li "Go ahead and try \"hello world.\"" 25 | (code "(println \"hello world\")")] 26 | [:li "Function names do not have to be words. Try some math functions:" 27 | (code (+ 1 2) 28 | (/ 22 7) 29 | (/ 22.0 7) 30 | (+ 1 2 3 4 5))] 31 | [:li "You can define your own functions with " [:code "defn"] ". 32 | Use the following form to define a function named triple that triples 33 | its argument." 34 | (code (defn triple 35 | [arg] 36 | (* 3 arg)))] 37 | [:li "Try defining and calling a few other functions of your choosing."]]]]) 38 | 39 | (defn documentation [] 40 | [[:h3 "Documentation"] 41 | [:ol 42 | [:li "To learn about a function, you can examine its docstring. Print the 43 | docstring for " [:code "println"] " using the form" 44 | (code (doc println))] 45 | [:li "If you are not sure of the name of a function, you can search for docs with " 46 | [:code "find-doc"] ". This can sometimes return a huge amount of 47 | information! Try " (code (find-doc "append"))] 48 | [:li [:code "find-doc"] " can also handle regular expressions, which in Clojure 49 | look like strings prepended by an octothorpe. Use a regular expression to 50 | find all the " [:code "find-"] " functions." (code (find-doc #"find-\w+"))] 51 | [:li "There is no documentation like the source code. Use " [:code "source"] 52 | " to view the soure code of a function." (code (source println))] 53 | [:li "Notice how the source for " [:code "println"] " includes a string before 54 | the arguments. This is how you define docstrings. Try it with your own 55 | functions: " 56 | (code (defn triple 57 | "Triples arg. Don't write redundant docstrings like this in real code." 58 | [arg] 59 | (* 3 arg))) 60 | (code (doc triple))]]]) 61 | 62 | (defn java [] 63 | [[:h3 "Java"] 64 | [:ol 65 | [:li "Clojure provides direct, unproxied access to Java. To call a static 66 | function use a form like " (code "(ClassName/function & args)") 67 | "(The " [:code "& args"] " is shorthand for zero or more arguments.) 68 | Try calling" (code (System/getProperty "java.home"))] 69 | [:li "You can create Java instances with forms like " (code (new ClassName & args)) 70 | " or the terser, more idiomatic " (code (ClassName. & args)) " Try making 71 | a date. " (code (java.util.Date.))] 72 | [:li "Once you have an instance you probably will want to call methods on it. 73 | For now, stuff your date into a global using " [:code "def"] ": " 74 | (code (def now (java.util.Date.)))] 75 | [:li "You can call instance methods like so:" (code (.getMonth now) 76 | (.toGMTString now))] 77 | [:li "If you can't remember all the date methods off the top of your 78 | head, you can pass either a class or an instance to " [:code "javadoc"] 79 | (code (javadoc now) 80 | (javadoc java.util.Date))]]]) 81 | 82 | (defn bonus [] 83 | [[:h3 "Bonus"] 84 | "What do you think the following forms will do?" 85 | (code (doc doc)) 86 | (code (find-doc "find-doc")) 87 | (code (source source)) 88 | (code (javadoc javadoc)) 89 | "Try them. Do you understand what you are seeing?"]) 90 | 91 | (defn instructions [] 92 | (concat (overview) 93 | (functions) 94 | (documentation) 95 | (java) 96 | (bonus))) -------------------------------------------------------------------------------- /src/labs/its_all_data.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:next "Looping" :next-url "/labs/looping" 2 | :prev "Names and Places" :prev-url "/labs/names-and-places"} 3 | labs.its-all-data 4 | (:use [labrepl.util :only (c code)]) 5 | (:require [clojure.string :as str])) 6 | 7 | (defn some-data-types [] 8 | [[:h3 "Some Data Types"] 9 | [:ol 10 | [:li "Let's start at the very beginning, with " (c nil) ". " (c nil) " evaluates 11 | to false in a boolean context, as you can see by calling" 12 | (code (if nil "nil is true" "nil is false"))] 13 | [:li "You can probably guess how " (c true) " and " (c false) " will behave in 14 | a boolean context:" (code (if true "logical true" "logical false")) 15 | (code (if false "logical true" "logical false"))] 16 | [:li "Integer literals come in decimal, octal and hex varieties:" 17 | (code "10") (code "010") (code "0x10")] 18 | [:li "Because math operators come first in an expression, you can pass as many 19 | arguments as you want" (code (+ 1 2 3)) (code (+ 10 10 10))] 20 | [:li "Clojure has a ratio type, so division may surprise you:" (code (/ 22 7))] 21 | [:li "If you want a decimal result, pass a float literal:" (code (/ 22.0 7))] 22 | [:li "For integer division and remainder, use " (c quot) " and " (c rem) ": " 23 | (code (quot 22 7)) (code (rem 22 7))] 24 | [:li "Clojure integer literals are not limited to the range of Java's primitive 25 | types. They can be any size, and Clojure will choose the correct underlying 26 | representation." 27 | (code (class (* 1000 1000))) 28 | (code (class (* 1000 1000 1000 1000))) 29 | (code (class (* 1000 1000 1000 1000 1000 1000 1000 1000))) 30 | "Notice that the last expression throws and overflow exception. This is 31 | due to the result overflowing into a BigInteger. Starting in 1.3.0, 32 | Clojure no longer auto-boxes this type of operation for you. Instead, you need 33 | to give a small hint to ensure that overflowing does not occur" 34 | (code (class (* 1000N 1000 1000 1000 1000 1000 1000 1000))) 35 | "The " (c N) " at the end of the first argument means " (c BigInteger) " 36 | to Clojure and will cause the entire expression to box accordingly. The " (c N) 37 | " can be placed at the end of any of the arguments and will yield the same result." 38 | (code (class (* 1000 1000 1000N 1000 1000 1000 1000 1000)))] 39 | [:li "Numeric literals suffixed with " (c M) " are " (c BigDecimal) "s, and provide 40 | the same guarantees for correctness as the underlying platform:" 41 | (code "(+ 100000M 0.00000000001M)")] 42 | [:li "Strings are simply Java Strings. They are immutable, and have access to all 43 | the underlying Java methods:" (code "\"hello\"") 44 | (code (.toUpperCase "hello"))]]]) 45 | 46 | (defn name-types [] 47 | [[:h3 "Types that Name Things"] 48 | [:ol 49 | [:li "Symbols name things in code-space, e.g. functions and Java classes. You can 50 | call a function through a symbol such as " (c println) ": " 51 | (code (println "hello"))] 52 | [:li "Rather than calling a function through a symbol, you might choose just to 53 | retrieve the function itself:" (code println)] 54 | [:li "The literal representation of a function at the REPL is just a mangled name. 55 | Instead of just looking at the function, you might create another name 56 | pointing to the same function:" 57 | (code (def my-println println))] 58 | [:li "Now you can call " (c my-println) " just like you would " (c println) ": " 59 | (code (my-println "hello"))] 60 | [:li "Sometimes you want to refer to the symbol itself, without retrieving whatever 61 | it refers to. To do this, you can " [:i "quote"] " the symbol:" 62 | (code "(quote println)")] 63 | [:li "Quoting is so common that that there is a sugared form: simply put a single 64 | quote in front of any symbol to prevent that form from being evaluated:" 65 | (code 'foo)] 66 | [:li "In fact, any Clojure form can be quoted. Look at the difference between" 67 | (code '(+ 1 2 3)) " and " (code (+ 1 2 3))] 68 | [:li "Keywords name things in domain-space, e.g. the attributes of domain entities. 69 | Because they are not used to name code entities, they simply evaluate to 70 | themselves and do not have to be quoted. Keywords are preceded by a colon:" 71 | (code :first-name) 72 | (code :last-name)] 73 | [:li "Keywords are often used to create map literals:" 74 | (code (def me {:first-name "Stu" :last-name "Halloway"}))] 75 | [:li "You can extract values from a map with " (c get) ": " 76 | (code (get me :first-name))] 77 | [:li "Maps are themselves functions, taking a key argument, so you could also write:" 78 | (code (me :first-name))] 79 | [:li "Keywords are also functions, taking a map argument, so yet another approach is:" 80 | (code (:first-name me))] 81 | [:li "Keywords are also used to name options. For example, you can use the " (c :as) 82 | " to specify a short form prefix for a namespace you are loading:" 83 | (code (require '[clojure.string :as str]))] 84 | [:li "Now you can use the" (c str) "prefix to access string functions:" 85 | (code (str/join " " ["the" "quick" "brown" "fox"]))]]]) 86 | 87 | (defn collections [] 88 | [[:h3 "Collections"] 89 | [:ol 90 | [:li "Lists are the traditional lisp linked list. You can create them with " 91 | (c list) " or with a quoted literal:" 92 | (code (list 1 2 3)) 93 | (code '(1 2 3))] 94 | [:li "List support efficient insertion at the front via " (c cons) ", which is 95 | short for \"construct\"." (code (cons 0 '(1 2 3)))] 96 | [:li "Vectors are indexed by integer values, and can be created with a literal 97 | form in square brackets:" 98 | (code ["hello" "world"]) 99 | (code (get ["hello" "world"] 1))] 100 | [:li "Map literals are enclosed in " (c {}), "." 101 | (code "{:country \"US\" :capital \"Washington, D.C.\"}")] 102 | [:li "Commas are whitespace. You may prefer using them to delimit pairs in map 103 | literals:" (code {:country "US", :capital "Washington, D.C."})] 104 | [:li "Character literals look like " (c \Z) ", and sets are delimited by " 105 | (code #{}), ", so the set of English vowels is " 106 | (code #{\a \e \i \o \u})] 107 | [:li "Vectors, maps, and sets are all associative collections. You can use them as 108 | functions to lookup values:" 109 | (code (["hello" "world"] 1)) 110 | (code ({:country "US", :capitol "Washington, D.C."} :country)) 111 | (code (#{\a \e \i \o \u} \z))] 112 | [:li "Write a function " (c vowel) " that returns its input if it is a vowel, 113 | or " (c nil) " otherwise." 114 | (code (defn vowel [ch] (#{\a \e \i \o \u} ch)))] 115 | [:li "Since the set of vowels is already a function, you could write " (c vowel) " 116 | more concisely as " (code (def vowel #{\a \e \i \o \u})) "Use the REPL to 117 | test that the two versions of " (c vowel) " are equivalent."]]]) 118 | 119 | (defn seqs [] 120 | [[:h3 "Seqs"] 121 | [:p "A sequence (seq) is a logical list. All Clojure collections are seq-able, 122 | that is, they can be treated as seqs. Using the sequence library, you can 123 | program in a general way against any Clojure collection type. Here are just a 124 | few examples:" 125 | [:ol 126 | [:li "You can get the " (c first) " or " (c rest) " of any collection:" 127 | (code (first [1 2 3])) 128 | (code (rest [1 2 3])) 129 | "Try taking the " (c first) " and " (c rest) " of some maps and sets. What are 130 | the return types?"] 131 | [:li "You can " (c take) " or " (c drop) " n elements of a collection." 132 | (code (take 5 (range 100))) 133 | (code (drop 5 (range 100))) 134 | "Again, try these functions against some other collection types as well."] 135 | [:li "You can " (c map) " a function across the elements of a collection, 136 | applying the function to each:" 137 | (code (map (fn [x] (* x 2)) (range 50)))] 138 | [:li "You can " (c filter) " a collection, returning only those elements that 139 | match a predicate:" (code (filter odd? (range 50)))] 140 | [:li (c reduce) " walks a collection, applying a function to a pair of elements 141 | and carrying the result. The following are equivalent:" 142 | (code "(reduce + [1 2]) \n-> (+ 1 2)") 143 | (code "(reduce + [1 2 3]) \n-> (+ (+ 1 2) 3)")] 144 | [:li "Try reducing something a little bigger." (code (reduce + (range 101)))] 145 | [:li "Because all collections can be treated as sequences, it is very easy to 146 | extend Clojure's power. If you write a function that works with seqs, all 147 | data can use it. The hardest part of writing Clojure apps is often deciding 148 | if the function you want already exists. What if you wanted something 149 | like " (c reduce), ", but returning all the intermediate steps. No problem, 150 | that is called " (c reductions) ": " (code (reductions + (range 101)))]]]]) 151 | 152 | (defn bonus [] 153 | [[:h3 "Bonus"] 154 | [:ol 155 | [:li "Our first " (c reduce) " example showed using reduce to perform 156 | repeated addition. But in Clojure, the " (c +) " function can itself 157 | perform repeated addition! What advantages does " (c reduce) " 158 | offer over implicit reduction within a function?"] 159 | [:li "Pick a language that you know well. What feature of your chosen language 160 | is analogous to Clojure keywords? To Clojure symbols? What do the 161 | differences (if any) imply?"] 162 | [:li "The " (c reverse) " function returns the reverse of a collection." 163 | (code (reverse [1 2 3])) "Its behavior with strings may surprise 164 | you, but it is consistent:" (code (reverse "foobar")) "Why 165 | is " (c reverse) "'s behavior the right thing to do, even for strings, 166 | and what options can/should be provided for String->String functions?"]]]) 167 | 168 | (defn instructions [] 169 | (concat (some-data-types) 170 | (name-types) 171 | (collections) 172 | (seqs) 173 | (bonus))) 174 | -------------------------------------------------------------------------------- /src/labs/looping.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:next "Project Euler" :next-url "/labs/project-euler" 2 | :prev "It's All Data" :prev-url "/labs/its-all-data"} 3 | labs.looping 4 | (:use [labrepl.util :only (c code repl-code showme)] 5 | [solutions.looping]) 6 | (:refer-clojure :exclude [min])) 7 | 8 | (defn overview [] 9 | [[:h3 "Overview"] 10 | [:p "When you are getting started with Clojure, you may (briefly!) miss imperative, 11 | mutable loops. In this lab, you will see how to loop in Clojure, and why you 12 | won't need to do it very often. You will implement three functions:" 13 | [:ol 14 | [:li [:code "min"] " returns the minimum of a variable-length sequence of args."] 15 | [:li [:code "zipm"] " builds a map by stepwise combination of a sequence of keys 16 | and a sequence of values."] 17 | [:li [:code "minmax"] " returns the minimum and maximum of a variable-length 18 | sequence of args."]]]]) 19 | 20 | (defn min [] 21 | [[:h3 "min"] 22 | [:ol 23 | [:li "Clojure has exactly one primitive control flow form: " (c if) ". Try using 24 | it to return the minimum of two numbers:" 25 | (repl-code (if (< 3 4) 3 4))] 26 | [:li "Clojure also has exactly one primitive control flow construct:" (c loop) ". 27 | Alone, it would loop forever, so you typically combine it with " (c if) " 28 | to terminate the recursion. Implement a function " 29 | (c min-1) " that takes the minimum of a variable-length argument list. It 30 | should" 31 | [:ol 32 | [:li "Begin the loop by binding " (c min) " to the first arg, and " (c more) " 33 | to a " (c seq) " over the rest of the args."] 34 | [:li "Grab the next number " (c x) " from the front of " (c more) " in an " 35 | (c if-let) "form."] 36 | [:li "If " (c x) " then recur with the smaller of " (c x) " and " (c min) ", 37 | followed by the " (c next) " of the numbers."] 38 | [:li "In the else branch of the " (c if-let) ", simply return the result " 39 | (c min)]] 40 | (showme min-1)] 41 | [:li "As you probably guessed from the suffix " (c -1) ", we are going to 42 | implement " (c min) " again, and make it better. The first improvement 43 | will use destructuring. Consider the following two forms, which are 44 | roughly equivalent:" 45 | (repl-code (let [[x & more] [1 2 3]] 46 | {:x x :more more})) 47 | (repl-code (let [coll [1 2 3]] 48 | {:x (first coll) :more (next coll)})) 49 | "Notice how the destructuring gets rid of the intermediate binding to " 50 | (c coll) ", which we never really wanted anyway. It also gets rid of the calls 51 | to " (c first) " and " (c next) ". Create a " (c min-2) " 52 | that uses destructuring in a similar way to simplify " (c min-1) "." 53 | (showme min-2)] 54 | [:li "An alternative to explicit looping is to call " (c reduce) ", which takes a 55 | function and applies it stepwise, two arguments a time, down a collection, e.g." 56 | (repl-code (reduce + [1 2 3])) 57 | "Implement " (c min-3) " which reduces over a function that returns the min of 58 | two arguments:" 59 | (showme min-3) 60 | "Test that " (c min-3) " works as expected:" 61 | (repl-code (min-3 5 2 6 1 8))]]]) 62 | 63 | (defn zipm [] 64 | [[:h3 "zipm"] 65 | [:ol 66 | [:li "The previous example, " (c min) ", looped over a collection to produce a 67 | single value. When your inputs and outputs are both collections, there are 68 | more implementation choices to consider. We will see this with " (c zipm) ", 69 | which takes sequences of keys and values to make a map:" 70 | (let [zipm zipmap] 71 | (repl-code (zipm [:a :b :c] [1 2 3])))] 72 | [:li "One option is to loop over three variables: the result map " (c m) " 73 | (which gets bigger), and the input sequences " (c ks) " and " (c vs) " 74 | (which get smaller). Create a " (c zipm-1) " that uses a loop and " 75 | (c assoc) " to build the result map." (showme zipm-1)] 76 | [:li "As with the " (c min) " example, the " (c zipm) " loop can be improved with 77 | destructuring. Create a " (c zipm-2) " that destructures into the key and value 78 | collections, avoiding all the calls to " (c first) " and " (c next) "." 79 | (showme zipm-2)] 80 | [:li "Create a " (c zipm-3) " that " (c reduce) "s over a function " (c assoc) "ing 81 | in a key/value pair." 82 | (showme zipm-3)] 83 | [:li "The " (c reduce) " version is simple already, but we can make it simpler by 84 | simply calling a collection constructor such as " (c hash-map) ":" 85 | (repl-code (hash-map :a 1 :b 2 :c 3)) 86 | "To call " (c hash-map) " in this way we just need a function to interleave 87 | the key and value collections:" 88 | (repl-code (interleave [:a :b :c] [1 2 3])) 89 | "Create a " (c zipm-4) " using " (c hash-map) " and " (c interleave) "." 90 | (showme zipm-4)] 91 | [:li "A very useful constructor is " (c into) ", which \"pours\" one collection 92 | into another. If you had a sequence of key/value pairs, you could pour them " 93 | (c into) " a map. Here is one way to cconvert the two sequences into a 94 | sequence of pairs:" 95 | (repl-code (map vector [:a :b :c] [1 2 3])) 96 | "Create a " (c zipm-5) " that pours the key/values " (c into) " a map:" 97 | (showme zipm-5)] 98 | [:li "The best approach of all is to notice that what you are doing is common 99 | enough to be built into Clojure, as in this case with " (c zipmap) "." 100 | (repl-code (zipmap [:a :b :c] [1 2 3]))]]]) 101 | 102 | (defn minmax [] 103 | [[:h3 "minmax"] 104 | [:p "In both the previous examples, the " (c reduce) " version looks simpler to 105 | my eye than the looping versions. But " (c reduce) " isn't always better. 106 | In this step, we will implement " (c minmax) ", which returns a map 107 | containing both the min and max from a collection."] 108 | [:ol 109 | [:li "Create a " (c minmax-1) " using a loop. This will look almost like " 110 | (c min-2) " from the earlier step, except that it will loop over three 111 | variables instead of two:" 112 | [:ol 113 | [:li (c min) " holds the current min"] 114 | [:li (c max) " holds the current max"] 115 | [:li (c more) " hold the remainder of the collection"]] 116 | "The return value should put both " (c min) " and " (c max) "into a map" 117 | (showme minmax-1)] 118 | [:li "For comparison, implement " (c minmax-2) " using reduce. Think carefully 119 | about the reduction function. It needs to hold two values at each iteration: 120 | one each for " (c min) " and " (c max) "." 121 | (showme minmax-2)] 122 | [:p "For functions that are updating more than one piece of information, I find the 123 | looping version is often more readable. But macros such as " (c ->) " make it 124 | simple to write multiple updates, so the " (c reduce) " isn't bad 125 | either. YMMV."]]]) 126 | 127 | (defn bonus [] 128 | [[:h3 "Bonus"] 129 | [:ol 130 | [:li "Read the implementations of " (c min) " and " (c zipmap) " in Clojure. Why 131 | are they different from any of the examples above?"] 132 | [:li "Some collection builders start with an empty collection, while others can 133 | take an argument passed in. Review the various " (c zipmap) " approaches. 134 | Which ones could be written to take an existing collection and add to it?"] 135 | [:li "Implement " (c min) " and " (c zipmap) " in an imperative style using a 136 | mutable OO language. Compare and contrast with the Clojure implementations 137 | above."] 138 | [:li "All the Clojure functions above are threadsafe. Make your mutable " 139 | (c zipmap) " threadsafe."]]]) 140 | 141 | (defn instructions [] 142 | (concat (overview) 143 | (min) 144 | (zipm) 145 | (minmax) 146 | (bonus))) 147 | 148 | -------------------------------------------------------------------------------- /src/labs/mini_browser.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:next "Unified Update Model" :next-url "/labs/unified-update-model" 2 | :prev "Project Euler" :prev-url "/labs/project-euler"} 3 | labs.mini-browser 4 | (:use [compojure.core :only (defroutes GET)] 5 | [hiccup.core :only (html)] 6 | [hiccup.page-helpers :only (include-css include-js)] 7 | [labrepl.layout :only (default-stylesheets default-javascripts)] 8 | [labrepl.util :only (c repl-showme showme showme* source)] 9 | [solutions.browser-mockup] 10 | [solutions.mini-browser])) 11 | 12 | (defn ll 13 | "literal link" 14 | [s] 15 | [:a {:href s} s]) 16 | 17 | (defn overview [] 18 | [[:h3 "Overview"] 19 | [:p "In this lab you will 20 | implement a simple Clojure object browser using the web framework 21 | Compojure. The browser will provide access to Clojure namespaces, 22 | vars, data, docstrings, and source code."]]) 23 | 24 | (defn defining-layout [] 25 | [[:h3 "Layout"] 26 | [:p "Clojure data can easily 27 | be viewed as isomorphic to HTML. A Clojure vector that begins with a 28 | keyword is an HTML element, a Clojure map is HTML attributes, and 29 | strings are strings. Hiccup exposes the isomorphism through 30 | the " (c html) " function, which we will use in our mockup of 31 | the mini-browser."] 32 | [:ol 33 | [:li "From a REPL, use the " (c hiccup.core) " namespace."] 34 | [:li "Create a simple paragraph element with some text:" 35 | (repl-showme (html [:p "hello world"]))] 36 | [:li "Use a map to add a css class of " (c awesome) ":" 37 | (repl-showme (html [:p {:class "awesome"} "hello world"]))] 38 | [:li "How about an HTML " (c head) " with title 'Mini-Browser, and a " (c body) " with id 39 | 'browser'?" 40 | (repl-showme (html [:head [:title "Mini-Browser"]] [:body {:id "browser"}]))] 41 | [:li "We need to pull in the compojure library in order to serve our hiccup 42 | generated HTML. Use " (c compojure.core) " and put the HTML from the previous 43 | step into a " (c mockup-1) " function:" 44 | (showme mockup-1)] 45 | [:li "Pull in " (c compojure.core) " and use " (c defroutes) " to create " (c mockup-routes) " that routes a GET of /m1 46 | to " (c mockup-1)". " (showme* '(defroutes mockup-routes (GET "/m1" [] (mockup-1))))] 47 | [:li "Use " (c ring.adapter.jetty) " and create a " (c mockup-server) " function that 48 | calls " (c run-jetty) " with two arguments:" 49 | [:ol 50 | [:li "the var containing the routes (in our case," (c mockup-routes) ")"] 51 | [:li "a map of options that get passed down to jetty"]] 52 | (showme mockup-server)] 53 | [:li "Run the " (c mockup-server) " and browse to " (ll "http://localhost:8999/m1") " to 54 | see the (empty) page."] 55 | [:li "Let's create a more complete " (c mockup-2) " with some layout divs." 56 | (source mockup-2)] 57 | [:li "Add " (c mockup-2) " to a route /m2 and browse to " (ll "http://localhost:8999/m2") " 58 | to see the second mockup."] 59 | [:li "Now let's add some standard styling and JavaScript. Use " (c hiccup.page-helpers) " 60 | and its " (c include-css) " function to generate markup that will pull in CSS files. 61 | Try it at the REPL:" 62 | (repl-showme (include-css "/stylesheets/application.css"))] 63 | [:li (c include-js) " does the same thing for JavaScript:" 64 | (repl-showme (include-js "/javascripts/application.js"))] 65 | [:li "The labs include a set of premade JavaScripts and CSS files. Use the namespace " 66 | (c labrepl.layout) ", and you can access these files via the vars " 67 | (c default-stylesheets) " and " (c default-javascripts)": " 68 | (repl-showme [default-stylesheets, default-javascripts])] 69 | [:li "Create a " (c mockup-3) " that adds the default CSS and JavaScripts:" 70 | (showme mockup-3)] 71 | [:li "Add an m3 route to " (c mockup-3) ". Use " (c compojure.route) " and add a catch-all 72 | route that serves files (The " (c files) " function will serve files from the " 73 | (c public) " directory by default.)" 74 | (showme* '(defroutes mockup-routes 75 | (GET "/m1" [] (mockup-1)) 76 | (GET "/m2" [] (mockup-2)) 77 | (GET "/m3" [] (mockup-3)) 78 | (files "/")))]]]) 79 | 80 | (defn static-mockup [] 81 | [[:h3 "Static Mockup"] 82 | [:p "The mini-browser will show a list of namespaces, a list of vars in the currently-selected 83 | namespace, and the docstring and source code for the currently-selected var. In this 84 | section we will get the UI working with fake data. As you write each function, test 85 | its output."] 86 | [:ol 87 | [:li "The browser will use the url /browse/foo for browsing the namespace foo. Create a " 88 | (c namespace-link) " function that returns an anchor tag linking to the passed in 89 | namespace name." (showme namespace-link)] 90 | [:li "Create a " (c namespace-browser) " function that takes a collection of namespace 91 | names, and creates a " (c div.browse-list) " around an unordered list of links 92 | from" (c namespace-link) "." (showme namespace-browser)] 93 | [:li "We will use the url " (c "/browse/com.foo/bar") " for browsing the var " (c bar) " 94 | in the " (c com.foo) " namespace. Create a " (c var-link) " function that takes a 95 | namespace name and a var name and builds a link." (showme var-link)] 96 | [:li "Create a " (c var-browser) " function that puts a " (c div.browse-list) (c variables) " 97 | around an unorderd list of var links:" (showme var-browser)] 98 | [:li "Create a " (c mockup-4) " that is like " (c mockup-3) ", but add a " 99 | (c namespace-browser) " and a " (c var-browser) " to the content div. (Make up 100 | some fake namespace and var names to populate the data." (showme mockup-4)] 101 | [:li "Create a route to " (c mockup-4) " and test it in the browser at " 102 | (ll "http://localhost:8999/m4") "."]]]) 103 | 104 | (defn making-it-live [] 105 | [[:h3 "Making It Live"] 106 | [:ol 107 | [:li "Let's leave the mockup behind, and create new routes and a servlet for live data. 108 | First, create a " (c layout) " function that looks like the mockups, but lets you 109 | pass in the body forms to fill the content div:" (showme layout)] 110 | [:li "We need a function to get the real list of namespace names. " (c all-ns) " returns 111 | the ns objects, from there you can get their " (c .name) "s and " (c sort) " them. 112 | Create a " (c namespace-names) " function that does this." 113 | (showme namespace-names)] 114 | [:li "Test " (c namespace-names) "." 115 | (repl-showme (namespace-names))] 116 | [:li "Next, create " (c application-routes) " so that a GET of / returns the " 117 | (c (namespace-names)) " wrapped in a " (c namespace-browser) " in a " (c layout) "." 118 | (showme* '(defroutes application-routes 119 | (GET "/" [] (html 120 | (layout 121 | (namespace-browser (namespace-names))))) 122 | (files "/")))] 123 | [:li "Create a " (c main) " function to launch the browser on port 9000." 124 | (showme main)] 125 | [:li "Run " (c main) " and browse to " (ll "http://localhost:9000") " to see the live 126 | namespaces."] 127 | 128 | [:li "Next we need a function " (c var-names) " that returns the var names given a 129 | namespace name. To build it you will need to call" (c symbol) ", " (c find-ns) ", 130 | " (c ns-publics) ", and " (c keys) "." (showme var-names)] 131 | [:li "Test " (c var-names) "." (repl-showme (var-names "clojure.string"))] 132 | [:li "Now for the var details. First, we need a simple " (c var-symbol) "helper that 133 | manufactures a fully-qualified var symbol, given the namespace and var as strings:" 134 | (showme var-symbol)] 135 | [:li "The " (c var-detail) " function should return markup:" 136 | [:ul 137 | [:li "The var symbol in an h3 tag"] 138 | [:li "The docstring in a " (c "pre code") " block. You can ask a var for its docstring 139 | by querying the metadata " (c (:doc (meta var)))] 140 | [:li "The source code itself. You will need to use" (c find-var) ", " 141 | (c clojure.repl/source-fn) ", and " (c labrepl.util/code*) ", which will wrap 142 | the code in HTML tags for you."]] 143 | (showme var-detail)] 144 | [:li "Test " (c var-detail) " from the repl." 145 | (repl-showme (var-detail "clojure.core" "and"))] 146 | [:li "Update the " (c application-routes) " to include a " (c "/browse/*") " route. It should 147 | extract the namespace and var names from the params, and then call " 148 | (c namespace-browser) ", " (c var-browser) ", and " (c var-detail) " to produce 149 | the HTML response." (showme application-routes)] 150 | [:li "Run the browser with the new routes, and try it out at " 151 | (ll "http://localhost:9000") "."]]]) 152 | 153 | (defn bonus [] 154 | [[:h3 "Bonus"] 155 | [:ol 156 | [:li "The 'everything is a function' approach makes testing easy. Routes are just 157 | functions--try calling one of your routes directly:" 158 | (repl-showme (mockup-routes {:uri "/m1"}))] 159 | [:li "The syntax highlighter isn't perfect--it sometimes highlights parts of words. 160 | What is the issue, and what is the fix?"] 161 | [:li "The lab you are reading is itself a Compojure app. Use your browser to explore 162 | the namespaces starting with " (c labrepl) "."]]]) 163 | 164 | (defn instructions [] 165 | (concat (overview) 166 | (defining-layout) 167 | (static-mockup) 168 | (making-it-live) 169 | (bonus))) 170 | -------------------------------------------------------------------------------- /src/labs/names_and_places.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:next "It's All Data" :next-url "/labs/its-all-data" 2 | :prev "Intro" :prev-url "/labs/intro"} 3 | labs.names-and-places 4 | (:use [labrepl.util :only (c code repl-code)] 5 | [solutions.dialect :only (canadianize)] 6 | [clojure.java.io :only (file)]) 7 | (:require [clojure.java.io :as io]) 8 | (:import [java.util Date Random])) 9 | 10 | (defn overview [] 11 | [[:h3 "Overview"] 12 | [:p "In this lab you will learn to manage Clojure namespaces and Java packages. 13 | In short:" 14 | [:ul 15 | [:li "use " [:code "require"] " to load clojure libraries"] 16 | [:li "use " [:code "refer"] " to refer to functions in the current namespace"] 17 | [:li "use " [:code "use"] " to load and refer all in one step"] 18 | [:li "use " [:code "import"] " to refer to Java classes in the current namespace"]]]]) 19 | 20 | (defn require-section [] 21 | [[:h3 "Require"] 22 | [:ol 23 | [:li "You can load the code for any clojure library with " (c "(require libname)") 24 | ". Try it with " (c clojure.java.io) ":" (code (require 'clojure.java.io))] 25 | [:li "Then print the directory of names available in the namespace" 26 | (code (dir clojure.java.io))] 27 | [:li "Show using " [:code "file"] " to create a new file:" 28 | (repl-code (clojure.java.io/file "foo"))] 29 | [:li "Writing out the namespace prefix on every function call is a pain, so you 30 | can specify a shorter alias using " [:code "as"] ":" 31 | (code (require '[clojure.java.io :as io]))] 32 | [:li "Calling the shorter form is much easier:" (repl-code (io/file "foo"))] 33 | [:li "You can see all the loaded namespaces with" (code (all-ns))]]]) 34 | 35 | (defn refer-and-use [] 36 | [[:h3 "Refer and Use"] 37 | [:ol 38 | [:li "It would be even easier to use a function with no namespace prefix at all. You 39 | can do this by " [:i "referring"] " to the name, which makes a 40 | reference to the name in the current namespace:" 41 | (code (refer 'clojure.java.io))] 42 | [:li "Now you can call " [:code "file"] " directly:" (repl-code (file "foo"))] 43 | [:li "If you want to load and refer all in one step, call " [:code "use"] ": " 44 | (code (use 'clojure.java.io))] 45 | [:li "Referring a library refers all of its names. This is often undesirable, because" 46 | [:ul 47 | [:li "it does not clearly document intent to readers"] 48 | [:li "it brings in more names than you need, which can lead to name collisions"]] 49 | "Instead, use the following style to specify only those names you want:" 50 | (code (use '[clojure.java.io :only (file)])) 51 | "The " [:code ":only"] " option is available on all the namespace 52 | management forms. (There is also an " [:code ":exclude"] " which 53 | works as you might expect.)"] 54 | [:li "The variable " [:code "*ns*"] " always contains the current namespace, and you 55 | can see what names your current namespace refers to by calling" 56 | (code (ns-refers *ns*))]] 57 | [:li "The refers map is often pretty big. If you are only interested in one symbol, 58 | pass that symbol to the result of calling " [:code "ns-refers"] ": " 59 | (code ((ns-refers 'labs.names-and-places) 'file))]]) 60 | 61 | (defn import-section [] 62 | [[:h3 "Import"] 63 | [:ol 64 | [:li "Importing is like referring, but for Java classes instead of Clojure 65 | namespaces. Instead of " (code (java.io.File. "woozle")) " you can say " 66 | (code (import java.io.File) (File. "woozle"))] 67 | [:li "You can import multiple classes in a Java package with the form " 68 | (code (import [package Class Class])) "For example: " 69 | (code (import [java.util Date Random]) 70 | (Date. (.nextInt (Random.))))] 71 | [:li "Programmers new to Lisp are often put off by the \"inside-out\" reading 72 | of forms like the date creation above. Starting from the inside, you " 73 | [:ul 74 | [:li "get a new " [:code "Random"]] 75 | [:li "get the next random integer"] 76 | [:li "pass the long to the " [:code "Date"] " constructor"]] 77 | "You don't have to write inside-out code in Clojure. The " [:code "->"] " 78 | macro takes its first form, and passes it as the first argument to its next form. 79 | The result then becomes the first argument of the next form, and so on. It is 80 | easier to read than to describe:" 81 | (repl-code (-> (Random.) (.nextInt) (Date.)))]]]) 82 | 83 | (defn load-and-reload [] 84 | [[:h3 "Load and Reload"] 85 | [:p "The REPL isn't for everything. For work you plan to keep, you will want to 86 | place your source code in a separate file. Here are the rules of thumb to 87 | remember when creating your own Clojure namespaces." 88 | [:ol 89 | [:li "Clojure namespaces (a.k.a. libraries) are equivalent to Java packages."] 90 | [:li "Clojure respects Java naming conventions for directories and files, but 91 | Lisp naming conventions for namespace names. So a Clojure namespace " 92 | [:code "com.my-app.utils"] " would live in a path named " 93 | [:code "com/my_app/utils.clj"] ". Note especially the underscore/hyphen 94 | distinction."] 95 | [:li "Clojure files normally begin with a namespace declaration, e.g." 96 | (code (ns com.my-app.utils))] 97 | [:li "The syntax for import/use/refer/require presented in the previous sections 98 | is for REPL use. Namespace declarations allow similar forms--similar 99 | enough to aid memory, but also different enough to confuse. The following 100 | forms at the REPL:" 101 | (code (use 'foo.bar) 102 | (require 'baz.quux) 103 | (import '[java.util Date Random])) 104 | " would look like this in a source code file:" 105 | (code (ns com.my-app.utils 106 | (:use foo.bar) 107 | (:require baz.quux) 108 | (:import [java.util Date Random]))) 109 | " Symbols become keywords, and quoting is no longer required."] 110 | [:li "At the time of this writing, the error messages for doing it wrong with 111 | namespaces are, well, opaque. Be careful."]]] 112 | [:p "Now let's try creating a source code file. We aren't going to bother with 113 | explicit compilation for now. Clojure will automatically (and quickly) 114 | compile source code files on the classpath. Instead, we can just add Clojure 115 | (.clj) files to the " [:code "src"] " directory." 116 | [:ol 117 | [:li "Create a file named " [:code "student/dialect.clj"] " in the " [:code "src"] " 118 | directory, with the appropriate namespace declaration:" 119 | (code (ns student.dialect))] 120 | [:li "Now, implement a simple " [:code "canadianize"] " function that takes a 121 | string, and appends " 122 | [:code ", eh?"] 123 | (code (defn canadianize 124 | [sentence] 125 | (str sentence ", eh")))] 126 | [:li "From your REPL, use the new namespace:" (code (use 'student.dialect))] 127 | [:li "Now try it out." 128 | (let [canadianize #(str % ", eh")] 129 | (repl-code (canadianize "Hello, world.")))] 130 | [:li "Oops! We need to trim the period off the end of the input. Fortunately, " 131 | [:code "clojure.string."] " provides " [:code "replace"] ". 132 | Go back to " [:code "student/dialect.clj"] " and add require in " 133 | [:code "[clojure.string :as str]"] ": " 134 | (code (ns student.dialect 135 | (:require [clojure.string :as str])))] 136 | [:li "Now, update " [:code "canadianize"] " to use " [:code "replace"] ": " 137 | (code (defn canadianize 138 | [sentence] 139 | (str/replace sentence #"\.$" ", eh?")))] 140 | [:li "If you simply retry calling " [:code "canadianize"] " from the repl, 141 | you will not see your new change, because the code was already loaded. 142 | However, you can use namespace forms with " [:code "reload"] " 143 | ( or " [:code "reload-all"] ") to reload a namespace (and its dependencies)." 144 | (code (use :reload 'student.dialect))] 145 | [:li "Now you should see the new version of " (c canadianize) ": " 146 | (repl-code (canadianize "Hello, world."))]]]]) 147 | 148 | (defn bonus [] 149 | [[:h3 "Bonus"] 150 | [:ol 151 | [:li "Canadianize was too easy. Implement " [:code "pig-latinize"] "."] 152 | [:li "Pig latin was too easy. Implement " [:code "germanize"] "."]]]) 153 | 154 | (defn instructions [] 155 | (concat (overview) 156 | (require-section) 157 | (refer-and-use) 158 | (import-section) 159 | (load-and-reload) 160 | (bonus))) -------------------------------------------------------------------------------- /src/labs/project_euler.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:next "Mini Browser" :next-url "/labs/mini-browser" 2 | :prev "Looping" :prev-url "/labs/looping"} 3 | labs.project-euler 4 | (:use [labrepl.util :only (c showme repl-showme)] 5 | [solutions.project-euler])) 6 | 7 | (defn overview [] 8 | [[:h3 "Overview"] 9 | [:p [:a {:href "http://projecteuler.net"} "Project Euler"] " is a set of mathematical 10 | and computer programming puzzles. You can create an account on the site, read 11 | the puzzle descriptions, and track the puzzles you have completed correctly. 12 | The puzzles are excellent for learning how to use laziness, recursion, and the 13 | sequence library in Clojure."] 14 | [:p "This lab will walk you through implementing the first Project Euler problem:"] 15 | [:blockquote 16 | [:p "If we list all the natural numbers below 10 that are multiples of 3 or 5, we 17 | get 3, 5, 6 and 9. The sum of these multiples is 23."] 18 | [:p "Find the sum of all the multiples of 3 or 5 below 1000."]]]) 19 | 20 | (defn filtering [] 21 | [[:h3 "Filtering"] 22 | [:p "When decomposing a problem into functions, a good place to start is 23 | identifying pure functions that will be used to filter the data. These 24 | functions are easy to define and test."] 25 | [:ol 26 | [:li "One way to attack this problem is finding numbers that are evenly divisible 27 | by 3 or 5. Create a " (c divides?) " predicate that takes a dividend and a 28 | divisor, and returns true if divisor evenly divides the dividend:" 29 | (showme divides?)] 30 | [:li "We will eventually want to filter on divisibility by more than one number, 31 | so create a " (c divides-any) " function that takes a variable list of 32 | numbers, and returns a predicate that tests whether its arg can be evenly 33 | divided by any of the numbers. (Hint: work inside-out, using " (c divides?) ", 34 | " (c some) ", and " (c boolean)"). " (showme divides-any)]]]) 35 | 36 | (defn recursion-solution [] 37 | [[:h3 "A Recursive Solution"] 38 | [:p "With the filtering primitives in place, we can focus on the recursion needed to 39 | calculate the result. Create a function of one argument, the exclusive " 40 | (c upper) " bound to sum. Inside this function, you will need to:"] 41 | [:ol 42 | [:li (c let) " a local function " (c divisible?) " that tests for divisibility by 43 | 3 or 5."] 44 | [:li "Loop starting with a " (c sum) " of zero and an " (c n) " of one."] 45 | [:li "The loop should terminate if " (c n) " is greater than or equal to " 46 | (c upper) "."] 47 | [:li "The recur should always increment " (c n) ". If n is " (c divisible?) ", " 48 | (c sum) " should increment by " (c n) ", otherwise, " (c sum) " should 49 | remain unchanged."]] 50 | (showme problem-1-recur) 51 | [:p "Create an account on " [:a {:href "http://projecteuler.net"} "Project Euler"] " 52 | and verify your solution."]]) 53 | 54 | (defn reduction-solution [] 55 | [[:h3 "A Solution by Reduction"] 56 | [:p "Another approach is to create a sequence of all the values that should 57 | contribute to the sum, and then simply add them. Let's work this inside out 58 | at the REPL, using just the numbers up to twenty."] 59 | [:ol 60 | [:li "Create the range bounded by 20." (repl-showme (range 20))] 61 | [:li "Filter the range to only the numbers divisible by 3 and 5." 62 | (repl-showme (filter 63 | (divides-any 3 5) 64 | (range 20)))] 65 | [:li "Add them together:" 66 | (repl-showme (apply + 67 | (filter 68 | (divides-any 3 5) 69 | (range 20))))] 70 | [:li "And we're done. Put that all into a function of " (c upper) ":" 71 | (showme problem-1)]]]) 72 | 73 | (defn left-to-right-solution [] 74 | [[:h3 "A Left-To-Right Solution"] 75 | [:p "The reductive solution is simple and elegant, but many people like to read 76 | code left-to-right, not inside out. The Clojure macros " (c ->) " and " 77 | (c ->>) " take their first argument, and insert it into the second form. 78 | The result of the second form is inserted into the third form, and so on. " 79 | (c ->) " inserts arguments at the first position, and " (c ->>) " inserts at 80 | the last position. Again this is simple enough to quickly implement at the REPL:"] 81 | [:ol 82 | [:li "Grab the range up to 20:" (repl-showme (range 20))] 83 | [:li "Insert the range into a filter:" 84 | (repl-showme (->> (range 20) (filter (divides-any 3 5))))] 85 | [:li "Insert the filtered range into +:" 86 | (repl-showme (->> (range 20) 87 | (filter (divides-any 3 5)) 88 | (apply +)))] 89 | [:li "And we're done. Put that all into a function:" 90 | (showme problem-1-left-to-right)]]]) 91 | 92 | (defn bonus [] 93 | [[:h3 "Bonus"] 94 | [:ol 95 | [:li "Implement the approaches above without using the helper functions " 96 | (c divides?) " and " (c divides-any?) ". What difference does this make?"] 97 | [:li "All the solutions above take all the numbers in range, and then exclude 98 | the ones we don't want. Go the other way and implement a solution that 99 | builds up the sequence of numbers we " [:em "do"] " want."] 100 | [:li "Implement Project Euler problems 2-5. The file " 101 | (c solutions/project_euler.clj) " has some correct solutions."]]]) 102 | 103 | (defn instructions [] 104 | (concat (overview) 105 | (filtering) 106 | (recursion-solution) 107 | (reduction-solution) 108 | (left-to-right-solution) 109 | (bonus))) 110 | -------------------------------------------------------------------------------- /src/labs/rock_paper_scissors.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:prev "defstrict" :prev-url "/labs/defstrict"} 2 | labs.rock-paper-scissors 3 | (:use [labrepl.util :only (c showme)] 4 | [solutions.rock-paper-scissors])) 5 | 6 | (defn overview [] 7 | [[:h3 "Overview"] 8 | [:p "Clojure's protocols and types are a more powerful and 9 | flexible alternative to interfaces and classes. They solve 10 | the expression problem, allowing you to extend old things 11 | that you don't control, and allowing old code to easily 12 | use new, all with performance consistent with the fastest 13 | forms of virtual invocation available on the host platform (e.g. Java)."] 14 | [:p "We won't explore all the power at once. In this lab, you will 15 | get your feet wet using protocols and types to simulate players 16 | in the classic rock-paper-scissors game. I got the idea for this 17 | exercise from an episode of the " [:a {:href "http://www.rubyquiz.com/quiz16.html"} "Ruby Quiz"] "."]]) 18 | 19 | (defn the-rules [] 20 | [[:h3 "The Rules"] 21 | [:ol 22 | [:li "The rules are pretty simple. Rock beats scissors, scissors beats paper, 23 | and paper beats rock. Write an idiomatic Clojure function " (c dominates) " 24 | that returns the thing that dominates the argument passed in." 25 | (showme dominates)] 26 | [:li "Create a " (c choices) " set that contains the possible choices. Don't Repeat Yourself." 27 | (showme choices)] 28 | [:li "Create a " (c winner) " function that takes two players' choices, and returns the winner, 29 | or " (c nil) " for a tie:" (showme winner)] 30 | [:li "Create a " (c draw?) " predicate that takes two players' choices and returns true if they are a draw." 31 | (showme draw?)] 32 | [:li "Create an " (c iwon?) " predicate that takes two players' choices and returns true if the first player won." 33 | (showme iwon?)]]]) 34 | 35 | (defn the-players [] 36 | [[:h3 "The Players"] 37 | [:ol 38 | [:li "All the players will conform to a " (c Player) " protocol. It should have two methods: " 39 | [:ol 40 | [:li (c choose) " takes a player and returns that player's choice"] 41 | [:li (c update-strategy) " takes a player, that player's last choice, and the other player's 42 | last choice, returning the " (c Player) " for the next round:"]] 43 | (showme Player)] 44 | [:li "Before we define our players we need to define our idea of random choice" 45 | (showme random-choice)] 46 | [:li "Use " (c defrecord) " to define a " (c Random) " player. " (c Random) " always picks at 47 | random, and never changes strategy based on what the other player is doing." 48 | (showme "(defrecord Random [] 49 | Player 50 | (choose [_] (random-choice)) 51 | (update-strategy [this me you] this))")] 52 | [:li "Create " (c Stubborn) ", who is initialized with a choice and sticks with it come hell or high water:" 53 | (showme "(defrecord Stubborn [choice] 54 | Player 55 | (choose [_] choice) 56 | (update-strategy [this me you] this))")] 57 | [:li "Create " (c Mean) ", who is a little more subtle. " (c Mean) "sticks with 58 | what worked last time, or plays at random following a loss:" 59 | (showme "(defrecord Mean [last-winner] 60 | Player 61 | (choose [_] (if last-winner last-winner (random-choice))) 62 | (update-strategy [_ me you] (Mean. (when (iwon? me you) me))))")] 63 | [:li "Now let's play the " (c game) ", with three arguments: two players and a number 64 | of rounds. " (c game) " reads nicely as a loop with five arguments:" 65 | [:ol 66 | [:li "player 1"] 67 | [:li "player 2"] 68 | [:li "player 1's current score"] 69 | [:li "player 2's current score"] 70 | [:li "the number of rounds remaining"]] 71 | "The game should return the two player's scores in a map." 72 | (showme game)] 73 | [:li "Try some games with the various players. Do the results match your intuition?"]]]) 74 | 75 | (defn bonus [] 76 | [[:h3 "Bonus"] 77 | [:ol 78 | [:li "Create some other strategies."] 79 | [:li "Compare your solution with some of the " 80 | [:a {:href "http://www.rubyquiz.com/quiz16.html"}"Ruby Quiz solutions"] ". What are the salient differences?"] 81 | [:li "How would you parallelize the code to spread games out to all your cores?"] 82 | [:li "Extend the simulation to support " 83 | [:a {:href "http://www.samkass.com/theories/RPSSL.html"} "Rock Paper Scissors Spock Lizard"] "."]]]) 84 | 85 | (defn instructions [] 86 | (concat (overview) 87 | (the-rules) 88 | (the-players) 89 | (bonus))) 90 | -------------------------------------------------------------------------------- /src/labs/unified_update_model.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:next "Zero Sum" :next-url "/labs/zero-sum" 2 | :prev "Mini Browser" :prev-url "/labs/mini-browser"} 3 | labs.unified-update-model 4 | (:refer-clojure :exclude [get]) 5 | (:use [labrepl.util :only (c repl-code showme repl-showme code source)] 6 | [solutions.fight :only (google-search-base 7 | url-encode fight 8 | estimated-hits-for 9 | add-estimate)] 10 | [solutions.atom-cache]) 11 | (:require 12 | [clojure.data.json :as json] 13 | [solutions.ref-cache :as rc])) 14 | 15 | (defn overview [] 16 | [[:h3 "Overview"] 17 | [:p "The Unified Update Model works as follows:" 18 | [:ul 19 | [:li "Data is immutable."] 20 | [:li [:em "State"] " is manged through indirection, via " [:em "references"] " 21 | to immutable data."] 22 | [:li "There are a variety of different reference types, each of which provides 23 | reliable concurrency semantics."] 24 | [:li "The reference types share a Unified Update Model, which is the atomic 25 | application of a function to the existing value."]] 26 | [:p "The Unified Update Model makes Clojure's concurrency easy to use, both in 27 | concept and at the API level. In this section you will explore the Unified 28 | Update Model in the context of different Clojure reference types."]]]) 29 | 30 | (defn atoms [] 31 | [[:h3 "Atoms"] 32 | [:ol 33 | [:li "An atom is an atomic reference to a single piece of data. You can create 34 | an atom like this:" (repl-code (def counter (atom 0)))] 35 | [:li "To update an atom, simply call " (c swap!) " with the atom and an update 36 | function. To increment the " 37 | (c counter) ":" 38 | (repl-code (swap! counter inc))] 39 | [:li "If the function has arguments, simply tack them on the end. Let's update 40 | the " (c counter) " a little faster:" (repl-code (swap! counter + 1000))] 41 | [:li "It is really that simple, and it is thread-safe. And the atomic values can 42 | be composites:" 43 | (repl-code (def language (atom {:name "Clojure" :age 2})))] 44 | [:li "The " (c xxx-in) " family of functions is great for working with composites:" 45 | (repl-code (swap! language update-in [:age] inc))] 46 | [:li "Let's use atoms to build a simple object cache. First, create a " 47 | (c create-1) " function that returns an atom wrapped around an empty map." 48 | (showme create-1)] 49 | [:li "Next, create a " (c get) " function that returns an item from the cache. 50 | Note that you will need to use the " (c :refer-clojure) " option to " 51 | (c ns) " to prevent collision with clojure's own " (c get) "." 52 | (showme get)] 53 | [:li "Create a " (c put-1) " function that puts an item into the cache, 54 | using " (c assoc) "." (showme put-1)] 55 | [:li "Take your new cache out for a spin at the REPL:" 56 | (repl-showme (let [c (create-1)] 57 | (put-1 c :greeting "Hello, World") 58 | (get c :greeting)))] 59 | [:li "Let's make the cache a little easier for callers. Define a " (c create) " 60 | function that lets callers specify an initial map, defaulting to an empty map:" 61 | (showme create)] 62 | [:li "Next, define a " (c put) " that can take a key/value pair as before, or 63 | an entire map to be added." (showme put)] 64 | [:li "Test your improved cache:" 65 | (repl-showme (let [grades (create {:A 100 :B 90})] 66 | (put grades {:C 80 :D 70}) 67 | grades))]]]) 68 | 69 | (defn refs [] 70 | [[:h3 "Transactions and Refs"] 71 | [:p "Refs differ from atoms in that they do not stand alone. Updates can be grouped 72 | together in atomic transactions. This is " [:em "not the same"] " as grouping 73 | updates in a lock-based system. Transactions never impede readers, cannot 74 | deadlock, and do not erode abstraction boundaries."] 75 | [:ol 76 | [:li "Use a " (c ref) " to create a counter with an initial value of zero:" 77 | (repl-showme (def counter (ref 0)))] 78 | [:li "As with an atom, you can read the value of a ref with " (c "deref/@") ":" 79 | (repl-showme @counter)] 80 | [:li "Updates are different, and require a call to " (c alter) ". But " (c alter) " 81 | does not work outside of a transaction:" 82 | (repl-code (alter counter inc))] 83 | [:li "All updates to a ref must be scoped inside a " (c dosync) ". Increment the 84 | counter:" (repl-showme (dosync (alter counter inc)))] 85 | [:li "Let's create a cache based on refs. As before, begin with a " (c create) " 86 | function." (showme rc/create)] 87 | [:li "Then get:" (showme rc/get)] 88 | [:li "And put:" (showme rc/put)] 89 | [:li "Test the ref-based cache:" 90 | (repl-showme (let [colors (rc/create {:hulk "green" :aquaman "orange"})] 91 | (rc/put colors {:flash "red" :sinestro "yellow"}) 92 | colors))] 93 | [:li "Transactions can nest arbitrarily, and multiple calls to " (c dosync) " 94 | all join the same transaction. At the REPL, create two different caches, 95 | and update them in same transaction:" 96 | (repl-showme (let [[colors powers] (repeatedly rc/create)] 97 | (dosync 98 | (rc/put colors :hulk "green") 99 | (rc/put powers :sinestro "fear-powered space flight")) 100 | {:colors colors :powers powers}))] 101 | [:li "Some operations are commutative (you don't care what order they happen in, 102 | as long as they all happen). If you know that an operation is commutative, 103 | you can use " (c commute) " instead of " (c alter) ". This is a performance 104 | optimization, allowing the STM to reduce the impedance between writers 105 | in some situations. Create a " (c fast-put) " that uses commute instead 106 | of " (c alter) "." (showme rc/fast-put)] 107 | [:li "Make sure that " (c fast-put) " works correctly:" 108 | (repl-showme (let [colors (rc/create {:hulk "green" :aquaman "orange"})] 109 | (rc/fast-put colors {:flash "red" :sinestro "yellow"}) 110 | colors))] 111 | [:li "The mechanics of " (c commute) " are simple, but the implications require 112 | some thought. Is " (c commute) " actually appropriate for a cache? For 113 | a counter?"]]]) 114 | 115 | (defn futures [] 116 | [[:h3 "Futures"] 117 | [:p "A future represents work to be done off the current thread. To see futures 118 | in action, let's create something slow: a program that compares the estimated 119 | google results for two search terms. You will need to include the following 120 | namespaces:" 121 | (code "(require '[clojure.data.json :as json])") 122 | (code "(use '[solutions.fight])")] 123 | [:ol 124 | [:li "The " (c slurp) " function takes a URL or filename string and returns a String of 125 | response data. Try it a the REPL to see how it works."] 126 | [:li "To get google search results, you will need the following URL prefix:" 127 | (source google-search-base)] 128 | [:li "Since some interesting searches are multiword, you will want 129 | to " (c url-encode) " them: " 130 | (repl-code (url-encode "two words"))] 131 | [:li "The " (c url-encode) " function is a thin wrapper around Java's " (c URLEncoder) 132 | (showme url-encode)] 133 | [:li "The search results are returned as JSON. The" (c json/read-str) " function 134 | converts JSON into Clojure data. Test it at the REPL:" 135 | (repl-showme (json/read-str "{\"foo\" : [1, 2]}" :key-fn keyword))] 136 | [:li "Using the functions " (c slurp) ", " (c url-encode) ", and " 137 | (c json/read-str) ", you can write an " (c estimated-hits-for) " function 138 | that returns the estimated hits for a search term:" 139 | (showme estimated-hits-for)] 140 | [:li "Try calling " (c estimated-hits-for) ". Note the (hopefully brief) latency 141 | as the request goes out to Google and back." 142 | (repl-showme (estimated-hits-for "clojure"))] 143 | [:li "At the REPL, wrap the call to " (c estimated-hits-for) " in a future so 144 | that control returns immediately and the work proceeds on a background thread:" 145 | (repl-showme (def clojure-result (future (estimated-hits-for "clojure"))))] 146 | [:li "Whenver you are ready, you can block waiting for a result by dereferencing 147 | the future:" (repl-showme @clojure-result)] 148 | [:li "Write a " (c fight) " function that takes two search terms. It should start 149 | one future to search for each term, and then a third future that waits on 150 | the first two:" (showme fight)] 151 | [:li "Use " (c fight) " to prove your deeply held biases. Thanks, Internet!"]]]) 152 | 153 | (defn agents [] 154 | [[:h3 "Agents"] 155 | [:p "Although futures allow " (c deref) ", they do not implement the unified 156 | update model. There is no ongoing state in a future, just a one-shot 157 | off-thread result. In this section, you will see agents, which implement 158 | the unified update model off-thread, allowing multiple background updates to 159 | a piece of state. We will continue the " (c fight) " example, using an agent 160 | to track all of the results over time."] 161 | [:ol 162 | [:li "You can create an agent by calling " (c agent) " with an initial state. At 163 | the REPL, define " (c fight-results) " as an agent with an initially empty map:" 164 | (repl-showme (def fight-results (agent {})))] 165 | [:li "Since agents use the unified update model, you already know how to use 166 | them, except for the function name to call (it's " (c send-off) "). We 167 | will store terms as keys, and their results counts as values in the map. " 168 | (c send-off) " a function to " (c fight-results) " that will update it 169 | with the hit count for \"clojure\":" 170 | (repl-showme 171 | (send-off fight-results 172 | (fn [state] 173 | (assoc state "clojure" (estimated-hits-for "clojure")))))] 174 | [:li "Next, capture the update function in named function " (c add-estimate) ". 175 | Think carefully about the arguments you will need, and the order in which 176 | they need to appear:" (showme add-estimate)] 177 | [:li "Now you are free to call " (c add-estimate) " as many times as you want:" 178 | (repl-showme (doseq [term ["derek wildstar" "mark venture"]] 179 | (send-off fight-results add-estimate term)))] 180 | [:li "Having sent a bunch of functions to an agent, you will often want to wait 181 | for them all to be done. You can await completion of all actions launched 182 | from the current thread with " (c await) " (no timeout) or " 183 | (c await-for) ". Use the safer of these two to make sure the work you 184 | requested is complete:" 185 | (repl-showme (await-for 1000 fight-results))] 186 | [:li "Dereference " (c fight-results) " to see what you got:" 187 | (repl-showme @fight-results)]]]) 188 | 189 | (defn bonus [] 190 | [[:h3 "Bonus"] 191 | [:ol 192 | [:li "In the section on atoms above, what are the legal types for keys in the 193 | cache? How would this differ in a mutable language like Java?"] 194 | [:li "Same question, but for the reference cache. What types are legal keys?"] 195 | [:li "In the fight examples, there is no error handling or recovery. What would 196 | you do to address this?"] 197 | [:li "There are actually two functions for updating agents: " (c send) " 198 | and " (c send-off) ". Read their docstrings. Does the agent-based fight 199 | use the correct function?"] 200 | [:li "Which functions in Clojure have names ending with bang (!), and why?"] 201 | [:li "Why do the various reference types share a read function " (c deref) ", 202 | but have different update functions?"]]]) 203 | 204 | (defn instructions [] 205 | (concat (overview) 206 | (atoms) 207 | (refs) 208 | (futures) 209 | (agents) 210 | (bonus))) 211 | 212 | -------------------------------------------------------------------------------- /src/labs/zero_sum.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:next "Cellular Automata" :next-url "/labs/cellular-automata" 2 | :prev "Unified Update Model" :prev-url "/labs/unified-update-model"} 3 | labs.zero-sum 4 | (:use [labrepl.util :only (c showme)]) 5 | (:require [solutions 6 | [accounts-1 :as accounts-1] 7 | [accounts-2 :as accounts-2] 8 | [accounts-3 :as accounts-3]])) 9 | 10 | (defn overview [] 11 | [[:h3 "Overview"] 12 | [:p "This lab will demonstrate using Clojure's Software Transactional Memory (STM), 13 | and get you started thinking about where identities occur in your code."]]) 14 | 15 | (defn requirements [] 16 | [[:h3 "Requirements"] 17 | [:p "You will be tracking the allocation of a fixed resource shared by N named 18 | accounts. A working solution will have the following:"] 19 | [:ol 20 | [:li "A function to create an initial set of account names and balances."] 21 | [:li "A function that transfers from one account to another."] 22 | [:li "A function to get the current balance for an individual account."] 23 | [:li "A function to get the total balance across all accounts. This balance should 24 | never change, since the resource is fixed and transfers are zero-sum."] 25 | [:li "All functions should be threadsafe!"]] 26 | [:p "If you are already somewhat comfortable with Clojure's reference API, try 27 | implementing these requirements from scratch. Otherwise, or if you get stuck, 28 | you can refer to the step-by-step instructions that follow."]]) 29 | 30 | (defn simple-accounts [] 31 | [[:h3 "Simple Accounts"] 32 | [:ol 33 | [:li "Create a function named " (c make-accounts) " that takes a map with " 34 | (c :count) " and " (c :initial-balance) ". It should create a map whose 35 | keys are names (using numbers from 0 to count-1) and whose values are 36 | refs to the initial balance." 37 | (showme accounts-1/make-accounts)] 38 | [:li "Create a function named " (c total-balance) " that sums account values for 39 | accounts created by " (c :make-accounts) ". You will need to dereference 40 | each value in the map." (showme accounts-1/total-balance)] 41 | [:li "Create a " (c transfer) " function that takes a map keyed with " 42 | (c [:accounts :from :to :amount]) " and does a transfer. You will need to 43 | use " (c dosync) " to scope a transaction, and you should " [:i "not"] " 44 | need to actually read any balances." (showme accounts-1/transfer)] 45 | [:li "Create a " (c balance) " function that takes " (c accounts) " and an " 46 | (c account-id) " and returns the current balance for an account." 47 | (showme accounts-1/balance)] 48 | [:li "Using the REPL or a unit testing framework you already know to test these 49 | functions against the requirements listed at the start of the lab. Don't 50 | worry about the \"must be thread safe!\" requirement yet."]]]) 51 | 52 | (defn is-it-threadsafe [] 53 | [[:h3 "Is It Threadsafe?"] 54 | [:p "No."] 55 | [:p "The transaction guarantees that all updates are atomic, consistent, and 56 | isolated. But the reads are not atomic, so " (c total-balance) " could be 57 | wrong. Let's prove it in code, by generating a bunch of random transactions 58 | on multiple threads and then watching reads get out of sync."] 59 | [:ol 60 | [:li "First, we need some random account ids: a " (c from-id) " and a " (c to-id) ". 61 | But in Clojure it rarely makes sense to deliver " [:i "two"] " of something, 62 | when you could deliver a lazy, infinite sequence instead. Write a " 63 | (c random-account-ids) " function that returns a lazy sequence of ids from 64 | accounts. You can use " (c rand-nth) " to get a random element from a 65 | collection, and you can use " (c repeatedly) " to return a lazy sequence of 66 | results from calling an impure function." 67 | (showme accounts-1/random-account-ids)] 68 | [:li "Now, create a " (c random-transfer) " function to do a transfer at random. 69 | Let " (c [from-id to-id]) " from " (c random-account-ids) ", then use " 70 | (c rand-int) " to let a random transfer amount, then make the " 71 | (c transfer) " :" (showme accounts-1/random-transfer)] 72 | [:li "Next we need to do a bunch of transfers, from multiple threads." 73 | [:ul "Create a " (c bunch-o-txes) " function that takes " 74 | (c [accounts n iterations]) "."] 75 | [:ul "Use " (c dotimes) " to do a fraction of the iterations on each thread."] 76 | [:ul "Use " (c future) " to package the work to run on another thread."] 77 | [:ul "Use " (c take) " and " (c repeatedly) " to get n threads worth."] 78 | (showme accounts-1/bunch-o-txes)] 79 | [:li "Finally, we need a " (c trial) " function that puts it all together." 80 | [:ul "Trial should take a map with keys " (c [:accounts :iterations :threads]) ". "] 81 | [:ul "Let a variable to track the" (c expected-balance) " (which is the initial 82 | balance of accounts)."] 83 | [:ul "Let a variable to hold some futures created by calling " (c bunch-o-txes) ". "] 84 | [:ul "Start a loop. If all the futures are " (c (.isDone f)) ", then return the 85 | accounts and futures in a map. (This is useful for poking at the results in 86 | the REPL)."] 87 | [:ul "If the futures are not done, assert that the " (c expected-balance) " is 88 | still right, and " (c recur) ". "] 89 | [:ul "Create a no-argument arity version that starts with 10 accounts, balance 90 | 100 each, 1000 iterations, and 2 threads."] 91 | (showme accounts-1/trial)] 92 | [:li "Try running the trial. In my tests on a dual-core machine, the assertion 93 | fails almost instantly"] 94 | [:li "A simple fix is to change " (c total-balance) " to read within a transaction, 95 | which guarantees that all reads are from the same point-in-time." 96 | (showme accounts-2/total-balance) 97 | "Now you should be able to run as long a " (c trial) " as you like without a 98 | problem."]] 99 | [:p "Reading from a transaction is fast, requires no locks, and never impedes 100 | writers. However, there is a way to solve this problem that avoids the read 101 | transaction, by changing the granularity we use to think about identity."]]) 102 | 103 | (defn granularity [] 104 | [[:h3 "Granularity of Identity"] 105 | [:p "So far, the unit of identity has been the individual account. But what if our 106 | unit of identity was the set of all accounts? Instead of a map with reference 107 | values, we could use a single reference to an immutable map. Let's make this 108 | change, and see how it affects each of our functions:"] 109 | [:ol 110 | [:li "The new version of " (c make-accounts) " should be a ref to a map, not a map 111 | whose values are refs:" (showme accounts-3/make-accounts)] 112 | [:li "The new version of " (c total-balance) " is simpler: it merely sumes the 113 | values of the referenced " (c accounts) " map:" 114 | (showme accounts-3/total-balance)] 115 | [:li "The new version of " (c transfer) " is more complex. Inside the transaction, 116 | you need to make two updates to the same map. Hint: use the " (c update-in) " 117 | function:" (showme accounts-3/transfer)] 118 | [:li "The new " (c balance) " function differs only in placement of parentheses, 119 | so that the dereference comes before the key lookup:" 120 | (showme accounts-3/balance)] 121 | [:li "The " (c random-account-ids) " function now needs to dereference " 122 | (c accounts) ":" (showme accounts-3/random-account-ids)] 123 | [:li "That's it. The driver functions that you use to test transactions do not 124 | have to change. Verify that the new system works correctly."]]]) 125 | 126 | (defn bonus [] 127 | [[:h3 "Bonus"] 128 | [:ol 129 | [:li "In the code above, you experimented with two different identities: the 130 | account and the set of all accounts. One of these approaches is the correct 131 | one. Which one, and why? Under what circumstances would the other approach 132 | make sense?"] 133 | [:li "The identity-per-account version of " (c make-accounts) " used " 134 | (c repeatedly) ", but the identity-for-all-accounts version used " 135 | (c repeat) ". Why the difference?"] 136 | [:li "Don't let the terminology of the example trap you into dollars-and-cents 137 | thinking. If you were building a program to play Monopoly, how many places 138 | might you use this code?"] 139 | [:li "What does the solution do to prevent accounts from going negative? How 140 | reliable is this? 141 | Sketch out at least two other approaches."] 142 | [:li "How is reading in a transaction different from reading in a lock?"] 143 | [:li "Could you use " (c commute) " instead of " (c alter) " to update the account 144 | balances? Read the requirements carefully."]]]) 145 | 146 | (defn instructions [] 147 | (concat (overview) 148 | (requirements) 149 | (simple-accounts) 150 | (is-it-threadsafe) 151 | (granularity) 152 | (bonus))) -------------------------------------------------------------------------------- /src/solutions/accounts_1.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:author "Stu Halloway" 2 | :doc "BROKEN version of accounts example. total-balance does not read consistently"} 3 | solutions.accounts-1) 4 | 5 | (defn make-accounts 6 | "Create a map of account-num->:initial-balance for :count numbered 7 | accounts." 8 | [{:keys [count initial-balance]}] 9 | (zipmap (range count) (repeatedly (partial ref initial-balance)))) 10 | 11 | (defn total-balance 12 | "Total balance of all accounts" 13 | [accounts] 14 | (apply + (map deref (vals accounts)))) 15 | 16 | (defn transfer 17 | [{:keys [accounts from to amount]}] 18 | (dosync 19 | (alter (accounts from) - amount) 20 | (alter (accounts to) + amount))) 21 | 22 | (defn balance 23 | [accounts account-id] 24 | @(accounts account-id)) 25 | 26 | (defn random-account-ids 27 | "Return a lazy seq of random account ids from accounts" 28 | [accounts] 29 | (let [ids (keys accounts)] 30 | (repeatedly (fn [] (rand-nth ids))))) 31 | 32 | (defn random-transfer 33 | "Perform a random tranfer between two accounts in accounts. 34 | Both accounts might be same account, we don't care." 35 | [accounts] 36 | (let [[from-id to-id] (random-account-ids accounts) 37 | amount (rand-int (balance accounts from-id))] 38 | (transfer {:accounts accounts 39 | :from from-id 40 | :to to-id 41 | :amount amount}))) 42 | 43 | (defn bunch-o-txes 44 | "Return n futures doing iterations random transactions 45 | against accounts, spread equally across n threads." 46 | [accounts n iterations] 47 | (take n (repeatedly 48 | (fn [] 49 | (future 50 | (dotimes [_ (/ iterations n)] 51 | (random-transfer accounts))))))) 52 | 53 | (defn trial 54 | "Creates :no-of-accounts accounts with :initial-balance. 55 | Bang on them from :threads different threads for 56 | :iterations. All the while, calling thread reads 57 | total-balance, asserting that it stays correct." 58 | ([] (trial {:accounts (make-accounts {:count 10 :initial-balance 100}) 59 | :iterations 1000 60 | :threads 2})) 61 | ([{:keys [accounts iterations threads]}] 62 | (let [expected-balance (total-balance accounts) 63 | futures (bunch-o-txes accounts threads iterations)] 64 | (loop [] 65 | (if (every? #(.isDone %) futures) 66 | {:accounts accounts :futures futures} 67 | (do 68 | (assert (= expected-balance (total-balance accounts))) 69 | (recur))))))) 70 | -------------------------------------------------------------------------------- /src/solutions/accounts_2.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:author "Stu Halloway" 2 | :doc "Improved version of accounts example. total-balance reads consistently."} 3 | solutions.accounts-2) 4 | 5 | (defn make-accounts 6 | "Create a map of account-num->(ref :initial-balance) for :count 7 | numbered accounts." 8 | [{:keys [count initial-balance]}] 9 | (zipmap (range count) (repeatedly (partial ref initial-balance)))) 10 | 11 | (defn total-balance 12 | "Total balance of all accounts" 13 | [accounts] 14 | (dosync (apply + (map deref (vals accounts))))) 15 | 16 | (defn transfer 17 | [{:keys [accounts from to amount]}] 18 | (dosync 19 | (alter (accounts from) - amount) 20 | (alter (accounts to) + amount))) 21 | 22 | (defn balance 23 | [accounts account-id] 24 | @(accounts account-id)) 25 | 26 | (defn random-account-ids 27 | "Return a lazy seq of random account ids from accounts" 28 | [accounts] 29 | (let [ids (keys accounts)] 30 | (repeatedly (fn [] (rand-nth ids))))) 31 | 32 | (defn random-transfer 33 | "Perform a random tranfer between two accounts in accounts. 34 | Both accounts might be same account, we don't care." 35 | [accounts] 36 | (let [[from-id to-id] (random-account-ids accounts) 37 | amount (rand-int (balance accounts from-id))] 38 | (transfer {:accounts accounts 39 | :from from-id 40 | :to to-id 41 | :amount amount}))) 42 | 43 | (defn bunch-o-txes 44 | "Return n futures doing iterations random transactions 45 | against accounts, spread equally across n threads." 46 | [accounts n iterations] 47 | (take n (repeatedly 48 | (fn [] 49 | (future 50 | (dotimes [_ (/ iterations n)] 51 | (random-transfer accounts))))))) 52 | 53 | (defn trial 54 | "Creates :no-of-accounts accounts with :initial-balance. 55 | Bang on them from :threads different threads for 56 | :iterations. All the while, calling thread reads 57 | total-balance, asserting that it stays correct." 58 | ([] (trial {:accounts (make-accounts {:count 10 :initial-balance 100}) 59 | :iterations 1000 60 | :threads 2})) 61 | ([{:keys [accounts iterations threads]}] 62 | (let [expected-balance (total-balance accounts) 63 | futures (bunch-o-txes accounts threads iterations)] 64 | (loop [] 65 | (if (every? #(.isDone %) futures) 66 | {:accounts accounts :futures futures} 67 | (do 68 | (assert (= expected-balance (total-balance accounts))) 69 | (recur))))))) 70 | -------------------------------------------------------------------------------- /src/solutions/accounts_3.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:author "Stu Halloway" 2 | :doc "Variant version of accounts example. total-balance reads 3 | consistently. Uses one identity for a group of accounts."} 4 | solutions.accounts-3) 5 | 6 | (defn make-accounts 7 | "Create ref to a map of account-num->:initial-balance for :count 8 | numbered accounts." 9 | [{:keys [count initial-balance]}] 10 | (ref (zipmap (range count) (repeat initial-balance)))) 11 | 12 | (defn total-balance 13 | "Total balance of all accounts" 14 | [accounts] 15 | (apply + (vals @accounts))) 16 | 17 | (defn transfer 18 | [{:keys [accounts from to amount]}] 19 | (dosync 20 | (alter 21 | accounts 22 | (fn [accounts] 23 | (-> accounts 24 | (update-in [from] #(- % amount)) 25 | (update-in [to] #(+ % amount))))))) 26 | 27 | (defn balance 28 | [accounts account-id] 29 | (@accounts account-id)) 30 | 31 | (defn random-account-ids 32 | "Return a lazy seq of random account ids from accounts" 33 | [accounts] 34 | (let [ids (keys @accounts)] 35 | (repeatedly (fn [] (rand-nth ids))))) 36 | 37 | (defn random-transfer 38 | "Perform a random tranfer between two accounts in accounts. 39 | Both accounts might be same account, we don't care." 40 | [accounts] 41 | (let [[from-id to-id] (random-account-ids accounts) 42 | amount (rand-int (balance accounts from-id))] 43 | (transfer {:accounts accounts 44 | :from from-id 45 | :to to-id 46 | :amount amount}))) 47 | 48 | (defn bunch-o-txes 49 | "Return n futures doing iterations random transactions 50 | against accounts, spread equally across n threads." 51 | [accounts n iterations] 52 | (take n (repeatedly 53 | (fn [] 54 | (future 55 | (dotimes [_ (/ iterations n)] 56 | (random-transfer accounts))))))) 57 | 58 | (defn trial 59 | "Creates :no-of-accounts accounts with :initial-balance. 60 | Bang on them from :threads different threads for 61 | :iterations. All the while, calling thread reads 62 | total-balance, asserting that it stays correct." 63 | ([] (trial {:accounts (make-accounts {:count 10 :initial-balance 100}) 64 | :iterations 1000 65 | :threads 2})) 66 | ([{:keys [accounts iterations threads]}] 67 | (let [expected-balance (total-balance accounts) 68 | futures (bunch-o-txes accounts threads iterations)] 69 | (loop [] 70 | (if (every? #(.isDone %) futures) 71 | {:accounts accounts :futures futures} 72 | (do 73 | (assert (= expected-balance (total-balance accounts))) 74 | (recur))))))) 75 | -------------------------------------------------------------------------------- /src/solutions/apple_pie.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:author "Stu Halloway"} 2 | solutions.apple-pie 3 | (:require 4 | [clojure.data.generators :as gen] 5 | [clojure.core.reducers :as r])) 6 | 7 | (defn gen-apples 8 | "Eagerly generate n apples of type type, where stickers is 9 | the proporition of apples that have stickers, and edible 10 | is the proportion of apples that are edible" 11 | [{:keys [n type stickers edible]}] 12 | (->> (repeatedly n (fn [] {:type type 13 | :sticker? (< (gen/double) stickers) 14 | :edible? (< (gen/double) edible)})) 15 | (into []))) 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/solutions/atom_cache.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.atom-cache 2 | (:refer-clojure :exclude [get])) 3 | 4 | (defn create-1 5 | [] 6 | (atom {})) 7 | 8 | (defn get 9 | [cache key] 10 | (@cache key)) 11 | 12 | (defn put-1 13 | [cache key value] 14 | (swap! cache assoc key value)) 15 | 16 | (defn create 17 | ([] (create {})) 18 | ([initial-value] (atom initial-value))) 19 | 20 | (defn put 21 | ([cache value-map] 22 | (swap! cache merge value-map)) 23 | ([cache key value] 24 | (swap! cache assoc key value))) 25 | -------------------------------------------------------------------------------- /src/solutions/automaton.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.automaton 2 | (import [javax.swing JFrame JPanel] 3 | [java.awt Color Graphics] 4 | [java.awt.image BufferedImage])) 5 | 6 | (def dim-board [ 90 90]) 7 | (def dim-screen [600 600]) 8 | (def dim-scale (vec (map / dim-screen dim-board))) 9 | 10 | (defn new-board 11 | "Create a new board with about half the cells set to :on." 12 | ([] (new-board dim-board)) 13 | ([[dim-x dim-y]] 14 | (for [x (range dim-x)] 15 | (for [y (range dim-y)] 16 | (if (< 50 (rand-int 100)) :on :off))))) 17 | 18 | (defn with-coords [board] 19 | (for [[row-idx row] (map-indexed vector board)] 20 | (for [[col-idx val] (map-indexed vector row)] 21 | [val row-idx col-idx]))) 22 | 23 | (defn without-coords [board] 24 | (for [row board] 25 | (for [[state] row] state))) 26 | 27 | (defn active-neighbors 28 | "Count the active (:on) neighbors one cell away from me in 29 | any direction. Maximum of 8." 30 | [[[nw n ne] 31 | [w _ e ] 32 | [sw s se]]] 33 | (count 34 | (filter 35 | #{:on} 36 | (concat [nw n ne w e sw s se])))) 37 | 38 | (defn brians-brain-rules 39 | "Determine the cell's next state based on its current 40 | state and number of active neighbors." 41 | [above [_ cell _ :as row] below] 42 | (cond 43 | (= :on cell) :dying 44 | (= :dying cell) :off 45 | (= 2 (active-neighbors [above row below])) :on 46 | :else :off )) 47 | 48 | (defn torus-window 49 | "The torus window is a cursor over the board, with each item 50 | containining a cell and all its immediate neighbors." 51 | [coll] 52 | (partition 3 1 (concat [(last coll)] coll [(first coll)]))) 53 | 54 | (defn step 55 | "Advance the automation by one step, updating all cells." 56 | [board] 57 | (doall 58 | (map (fn [window] 59 | (apply #(apply map brians-brain-rules %&) 60 | (doall (map torus-window window)))) 61 | (torus-window board)))) 62 | 63 | (defn update-board 64 | "Update the automaton" 65 | [board-ref] 66 | (swap! board-ref step)) 67 | 68 | (def state->color {:on java.awt.Color/WHITE 69 | :off java.awt.Color/BLACK 70 | :dying java.awt.Color/GRAY}) 71 | 72 | (defn render-cell [g cell] 73 | (let [[state x y] cell 74 | [x-scale y-scale] dim-scale 75 | x (inc (* x x-scale)) 76 | y (inc (* y y-scale))] 77 | (doto g 78 | (.setColor (state->color state)) 79 | (.fillRect x y (dec x-scale) (dec y-scale))))) 80 | 81 | (defn render [graphics img board] 82 | (let [background-graphics (.getGraphics img)] 83 | (doto background-graphics 84 | (.setColor java.awt.Color/BLACK) 85 | (.fillRect 0 0 (dim-screen 0) (dim-screen 1))) 86 | (doseq [row (with-coords board) 87 | cell row] 88 | (when-not (#{:off} (cell 0)) 89 | (render-cell background-graphics cell))) 90 | (.drawImage graphics img 0 0 nil))) 91 | 92 | (defn activity-loop [panel board] 93 | (while 94 | @board 95 | (update-board board) 96 | (.repaint panel))) 97 | 98 | (defn launch-1 [] 99 | (let [[screen-x screen-y] dim-screen 100 | board (atom (new-board)) 101 | frame (javax.swing.JFrame.) 102 | panel (proxy [javax.swing.JPanel] [])] 103 | (doto frame 104 | (.add panel) 105 | (.pack) 106 | (.setSize screen-x screen-y) 107 | (.show) 108 | (.setDefaultCloseOperation javax.swing.JFrame/DISPOSE_ON_CLOSE)) 109 | board)) 110 | 111 | (defn launch-2 [] 112 | (let [[screen-x screen-y] dim-screen 113 | board (atom (new-board)) 114 | frame (javax.swing.JFrame.) 115 | img (java.awt.image.BufferedImage. screen-x screen-y java.awt.image.BufferedImage/TYPE_INT_ARGB) 116 | panel (proxy [javax.swing.JPanel] [] 117 | (paint [g] (render g img @board)))] 118 | (doto frame 119 | (.add panel) 120 | (.pack) 121 | (.setSize screen-x screen-y) 122 | (.show) 123 | (.setDefaultCloseOperation javax.swing.JFrame/DISPOSE_ON_CLOSE)) 124 | board)) 125 | 126 | (defn launch [] 127 | (let [[screen-x screen-y] dim-screen 128 | board (atom (new-board)) 129 | frame (javax.swing.JFrame.) 130 | img (java.awt.image.BufferedImage. screen-x screen-y java.awt.image.BufferedImage/TYPE_INT_ARGB) 131 | panel (proxy [javax.swing.JPanel] [] 132 | (paint [g] (render g img @board)))] 133 | (doto frame 134 | (.add panel) 135 | (.pack) 136 | (.setSize screen-x screen-y) 137 | (.show) 138 | (.setDefaultCloseOperation javax.swing.JFrame/DISPOSE_ON_CLOSE)) 139 | (future (activity-loop panel board)) 140 | board)) 141 | 142 | 143 | -------------------------------------------------------------------------------- /src/solutions/browser_mockup.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.browser-mockup 2 | (:use [hiccup.core :only (html)] 3 | [hiccup.page-helpers :only (include-css include-js)] 4 | [compojure.core :only (defroutes GET)] 5 | [ring.adapter.jetty :only (run-jetty)] 6 | [labrepl.layout :only (default-stylesheets default-javascripts)] 7 | [solutions.mini-browser :only (namespace-browser var-browser)]) 8 | (:require [compojure.route :as route])) 9 | 10 | (defn mockup-1 [] 11 | (html 12 | [:head 13 | [:title "Mini-Browser"]] 14 | [:body {:id "browser"}])) 15 | 16 | (defn mockup-2 [] 17 | (html 18 | [:head 19 | [:title "Mini-Browser"]] 20 | [:body {:id "browser"} 21 | [:div {:id "header"} 22 | [:h2 "Mini-Browser"]] 23 | [:div {:id "content"} 24 | "Body"] 25 | [:div {:id "footer"} 26 | "Clojure Mini-Browser"]])) 27 | 28 | (defn mockup-3 [] 29 | (html 30 | [:head 31 | [:title "Mini-Browser"] 32 | (apply include-css default-stylesheets) 33 | (apply include-js default-javascripts)] 34 | [:body {:id "browser"} 35 | [:div {:id "header"} 36 | [:h2 "Mini-Browser"]] 37 | [:div {:id "content"} 38 | "Body TBD"] 39 | [:div {:id "footer"} 40 | "Clojure Mini-Browser"]])) 41 | 42 | (defn mockup-4 [] 43 | (html 44 | [:head 45 | [:title "Mini-Browser"] 46 | (apply include-css default-stylesheets) 47 | (apply include-js default-javascripts)] 48 | [:body {:id "browser"} 49 | [:div {:id "header"} 50 | [:h2 "Mini-Browser"]] 51 | [:div {:id "content"} 52 | (namespace-browser ["fake-ns1" "fake-ns2"]) 53 | (var-browser "fake-ns1" ["some-var-1" "some-var-2"])] 54 | [:div {:id "footer"} 55 | "Clojure Mini-Browser"]]) ) 56 | 57 | (defroutes mockup-routes 58 | (GET "/m1" [] (mockup-1)) 59 | (GET "/m2" [] (mockup-2)) 60 | (GET "/m3" [] (mockup-3)) 61 | (GET "/m4" [] (mockup-4)) 62 | (route/files "/")) 63 | 64 | (defn mockup-server [] 65 | (run-jetty (var mockup-routes) {:port 8999 66 | :join? false})) 67 | -------------------------------------------------------------------------------- /src/solutions/defstrict.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.defstrict) 2 | 3 | (defn shout-1 4 | [s] 5 | (.toUpperCase s)) 6 | 7 | (defn shout-2 8 | [^String s] 9 | (.toUpperCase s)) 10 | 11 | (defn shout-3 12 | [^String s] 13 | {:pre [(instance? String s)]} 14 | (.toUpperCase s)) 15 | 16 | (defn shout-4 17 | [^{:tag String} s] 18 | {:pre [(instance? String s)]} 19 | (.toUpperCase s)) 20 | 21 | (defn instance-check 22 | "Returns an instance check based on a symbol's type hint, 23 | or nil." 24 | [sym] 25 | (if-let [type (:tag (meta sym))] 26 | `(instance? ~type ~sym))) 27 | 28 | (defn arg-type-preconditions 29 | "Returns preconditions with type checks based on the type 30 | hints in arglist." 31 | [arglist] 32 | {:pre 33 | (->> (map instance-check arglist) 34 | (remove nil?) 35 | (into []))}) 36 | 37 | (defmacro defstrict 38 | [name arglist & body] 39 | `(defn ~name 40 | ~arglist 41 | ~(arg-type-preconditions arglist) 42 | ~@body)) 43 | 44 | (defstrict shout 45 | [^String s] 46 | (.toUpperCase s)) 47 | -------------------------------------------------------------------------------- /src/solutions/dialect.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:author "Stu Halloway" 2 | :doc "Trivial examples used by the names-and-places lab."} 3 | solutions.dialect 4 | (:require [clojure.string :as s])) 5 | 6 | (defn canadianize 7 | [sentence] 8 | (str (subs sentence 0 (dec (count sentence))) ", eh?")) 9 | 10 | (defn pig-latinize-word 11 | [word] 12 | (s/replace word #"(?i:^([^aeiou]*)(.*)$)" "$2$1ay")) 13 | 14 | (defn pig-latinize-sentence 15 | [sentence] 16 | (->> (s/split sentence #"[^\w]") 17 | (map pig-latinize-word) 18 | (s/join " "))) 19 | -------------------------------------------------------------------------------- /src/solutions/fight.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.fight 2 | (:require [clojure.data.json :as json]) 3 | (:import (java.net URL URLEncoder))) 4 | 5 | (def google-search-base 6 | "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=") 7 | 8 | (defn url-encode [q] 9 | (URLEncoder/encode q "UTF-8")) 10 | 11 | (defn estimated-hits-for 12 | [term] 13 | (let [http-response (slurp (str google-search-base (url-encode term))) 14 | json-response (json/read-str http-response :key-fn keyword)] 15 | (Long/parseLong (get-in json-response [:responseData :cursor :estimatedResultCount])))) 16 | 17 | (defn fight 18 | [term1 term2] 19 | (let [r1 (future (estimated-hits-for term1)) 20 | r2 (future (estimated-hits-for term2))] 21 | (future {term1 @r1 term2 @r2}))) 22 | 23 | (def fight-results 24 | (agent {})) 25 | 26 | (defn add-estimate 27 | [estimates term] 28 | (assoc estimates term (estimated-hits-for term))) 29 | 30 | -------------------------------------------------------------------------------- /src/solutions/looping.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.looping) 2 | 3 | (defn zipm-1 4 | [keys vals] 5 | (loop [m {} 6 | ks (seq keys) 7 | vs (seq vals)] 8 | (if (and ks vs) 9 | (recur (assoc m (first ks) (first vs)) 10 | (next ks) 11 | (next vs)) 12 | m))) 13 | 14 | (defn zipm-2 15 | [keys vals] 16 | (loop [m {} 17 | [k & ks :as keys] (seq keys) 18 | [v & vs :as vals] (seq vals)] 19 | (if (and keys vals) 20 | (recur (assoc m k v) ks vs) 21 | m))) 22 | 23 | (defn zipm-3 24 | [keys vals] 25 | (reduce (fn [m [k v]] (assoc m k v)) 26 | {} (map vector keys vals))) 27 | 28 | (defn zipm-4 29 | [keys vals] 30 | (apply hash-map (interleave keys vals))) 31 | 32 | (defn zipm-5 33 | [keys vals] 34 | (into {} (map vector keys vals))) 35 | 36 | (defn min-1 37 | [x & more] 38 | (loop [min x 39 | more (seq more)] 40 | (if-let [i (first more)] 41 | (recur (if (< i min) i min) (next more)) 42 | min))) 43 | 44 | (defn min-2 45 | [x & more] 46 | (loop [min x 47 | [i & more] (seq more)] 48 | (if i 49 | (recur (if (< i min) i min) more) 50 | min))) 51 | 52 | (defn min-3 53 | [& coll] 54 | (reduce 55 | (fn [x y] (if (< x y) x y)) 56 | coll)) 57 | 58 | (defn minmax-1 59 | [x & more] 60 | (loop [min x 61 | max x 62 | [x & more] (seq more)] 63 | (if x 64 | (recur 65 | (if (< x min) x min) 66 | (if (> x max) x max) 67 | more) 68 | {:min min :max max}))) 69 | 70 | (defn minmax-2 71 | [x & more] 72 | (reduce 73 | (fn [result x] 74 | (->> result 75 | (merge-with min {:min x}) 76 | (merge-with max {:max x}))) 77 | {:min x :max x} 78 | more)) 79 | -------------------------------------------------------------------------------- /src/solutions/mini_browser.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.mini-browser 2 | (:use [ring.adapter.jetty :only (run-jetty)] 3 | [compojure.core :only (defroutes GET)] 4 | [hiccup.core :only (html)] 5 | [hiccup.page-helpers :only (include-css include-js)] 6 | [labrepl.util :only (code*)] 7 | [labrepl.layout :only (default-stylesheets default-javascripts)] 8 | [compojure.route]) 9 | (:require [clojure.string :as str] 10 | [clojure.repl :as repl])) 11 | 12 | (defn namespace-names 13 | "Sorted list of namespace names (strings)." 14 | [] 15 | (->> (all-ns) 16 | (map #(.name %)) 17 | (sort))) 18 | 19 | (defn var-names 20 | "Sorted list of var names in a namespace (symbols)." 21 | [ns] 22 | (when-let [ns (find-ns (symbol ns))] 23 | (sort (keys (ns-publics ns))))) 24 | 25 | (defn namespace-link 26 | [ns-name] 27 | [:a {:href (str "/browse/" ns-name)} ns-name]) 28 | 29 | (defn namespace-browser 30 | [ns-names] 31 | [:div 32 | {:class "browse-list"} 33 | [:ul 34 | (map 35 | (fn [ns] [:li (namespace-link ns)]) 36 | ns-names)]]) 37 | 38 | (defn var-link 39 | [ns-name var-name] 40 | [:a {:href (str "/browse/" ns-name "/" (java.net.URLEncoder/encode (str var-name)))} var-name]) 41 | 42 | (defn var-browser 43 | [ns vars] 44 | (html 45 | [:div 46 | {:class "browse-list variables"} 47 | [:ul 48 | (map 49 | (fn [var] [:li (var-link ns var)]) 50 | vars)]])) 51 | 52 | (defn layout [& body] 53 | (html 54 | [:head 55 | [:title "Mini-Browser"] 56 | (apply include-css default-stylesheets) 57 | (apply include-js default-javascripts)] 58 | [:body {:id "browser"} 59 | [:div {:id "header"} 60 | [:h2 "Mini-Browser"]] 61 | [:div {:id "content"} 62 | body] 63 | [:div {:id "footer"} 64 | "Clojure Mini-Browser"]])) 65 | 66 | (defn view-function 67 | [func] 68 | (html 69 | [:h3 (find-var (symbol func))])) 70 | 71 | (defn var-symbol 72 | "Create a var-symbol, given the ns and var names as strings." 73 | [ns var] 74 | (symbol (str ns "/" var))) 75 | 76 | (defn var-detail 77 | [ns var] 78 | (when var 79 | (let [sym (var-symbol ns var) 80 | var (find-var sym)] 81 | (html [:h3 sym] 82 | [:h4 "Docstring"] 83 | [:pre [:code (:doc (meta var))]] 84 | [:h4 "Source"] 85 | (code* (repl/source-fn sym)))))) 86 | 87 | (defroutes application-routes 88 | (GET "/" [] (html 89 | (layout 90 | (namespace-browser (namespace-names)) 91 | [:div {:class "browse-list empty"}]))) 92 | (GET "/browse/*" request (let [[ns var] (str/split (get-in request [:params :*]) #"/")] 93 | (html 94 | (layout 95 | (namespace-browser (namespace-names)) 96 | (var-browser ns (var-names ns)) 97 | (var-detail ns var))))) 98 | (files "/") 99 | (not-found "

Not Found

")) 100 | 101 | (defn main [] 102 | (run-jetty (var application-routes) {:port 9000 103 | :join? false})) 104 | -------------------------------------------------------------------------------- /src/solutions/project_euler.clj: -------------------------------------------------------------------------------- 1 | (ns ^{:author "Stu Halloway" 2 | :doc "Some Project Euler solutions. See projecteuler.net."} 3 | solutions.project-euler 4 | (:require [clojure.string :as str])) 5 | 6 | ;; Taken from c.c.lazy-seq 7 | (def ^{:doc "Lazy sequence of all the prime numbers."} 8 | primes 9 | (concat 10 | [2 3 5 7] 11 | (lazy-seq 12 | (let [primes-from 13 | (fn primes-from [n [f & r]] 14 | (if (some #(zero? (rem n %)) 15 | (take-while #(<= (* % %) n) primes)) 16 | (recur (+ n f) r) 17 | (lazy-seq (cons n (primes-from (+ n f) r))))) 18 | wheel (cycle [2 4 2 4 6 2 6 4 2 4 6 6 2 6 4 2 19 | 6 4 6 8 4 2 4 2 4 8 6 4 6 2 4 6 20 | 2 6 6 4 2 4 6 2 6 4 2 4 2 10 2 10])] 21 | (primes-from 11 wheel))))) 22 | 23 | (defn divides? 24 | "Does divisor divide dividend evenly?" 25 | [dividend divisor] 26 | (zero? (rem dividend divisor))) 27 | 28 | (defn divides-any 29 | "Return a predicate that tests whether its arg can be 30 | evenly divided by any of nums." 31 | [& nums] 32 | (fn [arg] 33 | (boolean (some #(divides? arg %) nums)))) 34 | 35 | (defn problem-1-recur 36 | "Sum the numbers divisible by 3 or 5, from 0 to upper." 37 | ([] 38 | (problem-1-recur 1000)) 39 | ([upper] 40 | (let [divisible? (divides-any 3 5)] 41 | (loop [sum 0 n 1] 42 | (if (>= n upper) 43 | sum 44 | (recur 45 | (if (divisible? n) (+ sum n) sum) 46 | (inc n))))))) 47 | 48 | (defn problem-1 49 | "Sum the numbers divisible by 3 or 5, from 0 to upper." 50 | ([] (problem-1 1000)) 51 | ([upper] 52 | (apply 53 | + 54 | (filter 55 | (divides-any 3 5) 56 | (range upper))))) 57 | 58 | (defn problem-1-left-to-right 59 | "Sum the numbers divisible by 3 or 5, from 0 to upper." 60 | ([] (problem-1-left-to-right 1000)) 61 | ([upper] 62 | (->> (range upper) 63 | (filter (divides-any 3 5)) 64 | (apply +)))) 65 | 66 | (defn fibos 67 | [] 68 | (map first (iterate (fn [[a b]] [b (+ a b)]) [0 1]))) 69 | 70 | (defn problem-2 71 | "Sum the even-valued Fibonaccis up to upper" 72 | ([] (problem-2 (* 4 1000 1000))) 73 | ([upper] 74 | (->> (fibos) 75 | (take-while #(< % upper)) 76 | (filter even?) 77 | (apply +)))) 78 | 79 | (defn prime-factors 80 | "Returns the prime factors from smallest to largest" 81 | [n] 82 | (loop [factors [] 83 | n n] 84 | (if (< n 2) 85 | factors 86 | (let [next-factor (first (filter #(zero? (rem n %)) primes))] 87 | (recur (conj factors next-factor) (quot n next-factor)))))) 88 | 89 | (defn problem-3 90 | "Greatest prime factor" 91 | ([] (problem-3 600851475143)) 92 | ([n] (last (prime-factors n)))) 93 | 94 | (defn problem-4 95 | "Largest palindrome from product of two numbers" 96 | ([] (problem-4 1000)) 97 | ([upper] 98 | (apply max 99 | (for [x (range upper) y (range x upper) :when (let [s (str (* x y))] 100 | (= s (str/reverse s)))] 101 | (* x y))))) 102 | 103 | (defn problem-5 104 | "2520 is the smallest number that can be divided by each of the numbers 105 | from 1 to 10 without any remainder. 106 | 107 | What is the smallest number that is evenly divisible by all of the numbers 108 | from 1 to 20?" 109 | ([] (problem-5 20)) 110 | ([upper] 111 | (first (let [divisors (range 2 (inc upper))] 112 | (filter 113 | (fn [n] (every? #(zero? (rem n %)) divisors)) 114 | (iterate inc 2)))))) 115 | 116 | (defn unique-factors 117 | "Return unique factors up to max, removing factors that 118 | are subsumed by larger factors (e.g. if you have 4 you 119 | don't need 2.)" 120 | [max] 121 | (reduce 122 | (fn [result item] 123 | (if (some #(divides? % item) result) 124 | result 125 | (conj result item))) 126 | [] 127 | (range max 2 -1))) 128 | 129 | (defn square-of-sum 130 | [x] 131 | (let [sum (/ (* x (inc x)) 2)] 132 | (* sum sum))) 133 | 134 | (defn sum-of-squares 135 | [x] 136 | (apply + (map #(* % %) (range (inc x))))) 137 | 138 | (defn problem-6 139 | "sum of squares minus square of sum" 140 | [x] 141 | (- (square-of-sum x) (sum-of-squares x))) 142 | 143 | (defn problem-7 144 | "for this to count, you need to understand how lazy-seq primes works..." 145 | [x] 146 | (nth primes (dec x))) 147 | -------------------------------------------------------------------------------- /src/solutions/ref_cache.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.ref-cache 2 | (:refer-clojure :exclude [get])) 3 | 4 | (defn create 5 | ([] (create {})) 6 | ([initial-value] (ref initial-value))) 7 | 8 | (defn get 9 | [cache key] 10 | (@cache key)) 11 | 12 | (defn put 13 | ([cache value-map] 14 | (dosync 15 | (alter cache merge value-map))) 16 | ([cache key value] 17 | (dosync 18 | (alter cache assoc key value)))) 19 | 20 | (defn fast-put 21 | ([cache value-map] 22 | (dosync 23 | (commute cache merge value-map))) 24 | ([cache key value] 25 | (dosync 26 | (commute cache assoc key value)))) 27 | -------------------------------------------------------------------------------- /src/solutions/rock_paper_scissors.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.rock-paper-scissors) 2 | 3 | (def dominates 4 | {:rock :paper 5 | :scissors :rock 6 | :paper :scissors}) 7 | 8 | (def choices (set (keys dominates))) 9 | 10 | (defn random-choice [] 11 | (nth (vec choices) (rand-int (count choices)))) 12 | 13 | (defn winner [p1-choice p2-choice] 14 | (cond 15 | (= p1-choice p2-choice) nil 16 | (= (dominates p1-choice) p2-choice) p2-choice 17 | :else p1-choice)) 18 | 19 | (defn draw? [me you] (= me you)) 20 | 21 | (defn iwon? [me you] (= (winner me you) me)) 22 | 23 | (defprotocol Player 24 | (choose [p]) 25 | (update-strategy [p me you])) 26 | 27 | (defrecord Random [] 28 | Player 29 | (choose [_] (random-choice)) 30 | (update-strategy [this me you] this)) 31 | 32 | (defrecord Stubborn [choice] 33 | Player 34 | (choose [_] choice) 35 | (update-strategy [this me you] this)) 36 | 37 | (defrecord Mean [last-winner] 38 | Player 39 | (choose [_] (if last-winner last-winner (random-choice))) 40 | (update-strategy [_ me you] (Mean. (when (iwon? me you) me)))) 41 | 42 | (defn game 43 | [p1 p2 rounds] 44 | (loop [p1 p1 45 | p2 p2 46 | p1-score 0 47 | p2-score 0 48 | rounds rounds] 49 | (if (pos? rounds) 50 | (let [p1-choice (choose p1) 51 | p2-choice (choose p2) 52 | result (winner p1-choice p2-choice)] 53 | (recur 54 | (update-strategy p1 p1-choice p2-choice) 55 | (update-strategy p2 p2-choice p1-choice) 56 | (+ p1-score (if (= result p1-choice) 1 0)) 57 | (+ p2-score (if (= result p2-choice) 1 0)) 58 | (dec rounds))) 59 | {:p1 p1-score :p2 p2-score}))) 60 | -------------------------------------------------------------------------------- /src/student/README: -------------------------------------------------------------------------------- 1 | Your work goes in this directory. -------------------------------------------------------------------------------- /test/labrepl/apple_pie_bench.clj: -------------------------------------------------------------------------------- 1 | (ns labrepl.apple-pie-bench 2 | (:require 3 | [solutions.apple-pie :as pie] 4 | [criterium.core :as crit] 5 | [clojure.core.reducers :as r] 6 | [clojure.pprint :as pprint] 7 | [clojure.data :as data])) 8 | 9 | (defn filter-serial 10 | [apples] 11 | (->> (filter :edible? apples) 12 | dorun)) 13 | 14 | (defn map-serial 15 | [apples] 16 | (->> (map #(dissoc % :sticker?) apples) 17 | dorun)) 18 | 19 | (def map-and-filter 20 | (comp (partial filter :edible?) 21 | (partial map #(dissoc % :sticker?)))) 22 | 23 | (defn map-filter-serial 24 | [apples] 25 | (->> (map-and-filter apples) 26 | dorun)) 27 | 28 | (defn pmap-apples 29 | [apples] 30 | (->> (pmap #(dissoc % :sticker?) apples) 31 | dorun)) 32 | 33 | (def transform-apples 34 | (comp (r/filter :edible?) 35 | (r/map #(dissoc % :sticker?)))) 36 | 37 | (defn reduce-apples 38 | [apples] 39 | (->> (transform-apples apples) 40 | (into []))) 41 | 42 | (defn filter-fold 43 | [apples] 44 | (->> (r/filter :edible? apples) 45 | r/foldcat)) 46 | 47 | (defn map-fold 48 | [apples] 49 | (->> (r/map #(dissoc % :sticker?) apples) 50 | r/foldcat)) 51 | 52 | (defn map-filter-fold 53 | [apples] 54 | (->> (transform-apples apples) 55 | r/foldcat)) 56 | 57 | (defn bench 58 | "Wrap criterium so that it does not use stdout" 59 | [f] 60 | (let [s (java.io.StringWriter.)] 61 | (binding [*out* s] 62 | (assoc (crit/quick-benchmark* f) 63 | :stdout (str s))))) 64 | 65 | (defn transformations-equivalent 66 | [apples] 67 | (let [serial-reduced (map-and-filter apples) 68 | parallel-folded (map-filter-fold apples) 69 | [f s both :as diff] (data/diff serial-reduced (seq parallel-folded))] 70 | (when (or f s) 71 | (throw (ex-info "Serial and parallel implementations produced different results" 72 | {:serial serial-reduced 73 | :parallel parallel-folded 74 | :diff diff}))))) 75 | 76 | (defn -main 77 | [& _] 78 | (let [mean-msec #(long (* 1000 (first (:sample-mean %))))] 79 | (->> (for [napples [100000 1000000] 80 | :let [apples (pie/gen-apples {:n napples :type :golden :stickers 1 :edible 0.8})] 81 | sym '[filter-serial map-serial map-filter-serial filter-fold map-fold map-filter-fold]] 82 | (do 83 | (print "Testing " sym " " napples ": ") (flush) 84 | (let [result (bench (partial @(resolve sym) apples))] 85 | (println (mean-msec result) " msec") 86 | {:op sym 87 | :napples napples 88 | :result result}))) 89 | (map 90 | (fn [{:keys [op napples result]}] 91 | {"Operation" op 92 | "Apples" napples 93 | "Mean Time (msec)" (mean-msec result)})) 94 | (pprint/print-table)))) 95 | 96 | (comment 97 | (require :reload 'labrepl.apple-pie-bench) 98 | (in-ns 'labrepl.apple-pie-bench) 99 | (set! *print-length* 100) 100 | (transformations-equivalent (pie/gen-apples {:n 10000 :type :granny :stickers 1 :edible 0.5})) 101 | (ex-data *e) 102 | (-main) 103 | ) 104 | 105 | (comment 106 | 107 | ;; example criterium output 108 | 109 | {:lower-q 110 | [0.057168000000000004 (0.057168000000000004 0.057197500000000005)], 111 | :sample-count 6, 112 | :tail-quantile 0.025, 113 | :mean 114 | [0.05752041666666667 (0.057238000000000004 0.05824208333333334)], 115 | :total-time 0.690245, 116 | :sample-variance [3.600937416666678E-7 (0.0 0.0)], 117 | :samples 118 | (114746000 114336000 114395000 117468000 114603000 114697000), 119 | :sample-mean 120 | [0.05752041666666667 (0.05572018232775409 0.05932065100557925)], 121 | :outlier-variance 0.13888888888888884, 122 | :os-details 123 | {:arch "x86_64", 124 | :available-processors 8, 125 | :name "Mac OS X", 126 | :version "10.7.5"}, 127 | :outliers {:low-severe 0, :low-mild 0, :high-mild 0, :high-severe 1}, 128 | :upper-q [0.058560812500000003 (0.057342625 0.058734)], 129 | :options 130 | {:max-gc-attempts 100, 131 | :samples 6, 132 | :target-execution-time 100000000, 133 | :warmup-jit-period 5000000000, 134 | :tail-quantile 0.025, 135 | :bootstrap-size 500}, 136 | :results (0 0 0 0 0 0), 137 | :runtime-details 138 | {:java-version "1.6.0_37", 139 | :vm-version "20.12-b01-434", 140 | :clojure-version-string "1.5.1", 141 | :spec-version "1.0", 142 | :spec-name "Java Virtual Machine Specification", 143 | :input-arguments ["-Xmx6G"], 144 | :vm-name "Java HotSpot(TM) 64-Bit Server VM", 145 | :name "43482@Orolo.local", 146 | :clojure-version 147 | {:major 1, :minor 5, :incremental 1, :qualifier nil}, 148 | :sun-arch-data-model "64", 149 | :vm-vendor "Apple Inc.", 150 | :java-runtime-version "1.6.0_37-b06-434-11M3909", 151 | :spec-vendor "Sun Microsystems Inc."}, 152 | :variance 153 | [3.600937416666666E-7 (4.720141666666722E-9 7.266124416666656E-7)], 154 | :execution-count 2} 155 | 156 | ) 157 | -------------------------------------------------------------------------------- /test/labrepl/render_test.clj: -------------------------------------------------------------------------------- 1 | (ns labrepl.render-test 2 | (:use clojure.test 3 | labrepl labrepl.util)) 4 | 5 | (deftest render-the-labs 6 | [] 7 | (doseq [lab all] 8 | (let [url (lab-url lab) 9 | resp (application {:request-method :get 10 | :uri url})] 11 | (is 12 | (= {:status 200 13 | :headers 14 | {"Content-Type" "text/html; charset=utf-8"}} 15 | (select-keys resp 16 | [:status :headers])))))) 17 | -------------------------------------------------------------------------------- /test/labrepl/util_test.clj: -------------------------------------------------------------------------------- 1 | (ns labrepl.util-test 2 | (:use clojure.test 3 | labrepl.util)) 4 | 5 | (def foo []) 6 | 7 | (deftest test-utils 8 | (testing "format-code" 9 | (is (= "nil\n" (format-code nil))) 10 | (is (= "(+ a b)\n" (format-code "(+ a b)"))) 11 | (is (= "(+ c d)\n" (format-code '(+ c d))))) 12 | 13 | (testing "one-liner?" 14 | (is (true? (one-liner? nil))) 15 | (is (true? (one-liner? "foo"))) 16 | (is (true? (one-liner? "foo\n"))) 17 | (is (true? (one-liner? "foo\n "))) 18 | (is (false? (one-liner? "foo\nbar\n")))) 19 | 20 | (testing "deindent" 21 | (is (nil? (deindent nil))) 22 | (is (= "foo" (deindent "foo"))) 23 | (is (= "foo\nbar") (deindent "foo\nbar")) 24 | (is (= "foo\n bar") (deindent " foo\n bar"))) 25 | 26 | (testing "code" 27 | (is (= [:script 28 | {:type "syntaxhighlighter", :class "brush: clojure; toolbar: false; gutter: false; light: true;"} 29 | ""] 30 | (code (new ClassName foo))))) 31 | 32 | (testing "c" 33 | (is (= [:code {:class "inline-syntax"} "(new ClassName foo)"] 34 | (c (new ClassName foo))))) 35 | 36 | (testing "repl*" 37 | (is (= "(+ 1 2)\n-> 3" 38 | (repl* (+ 1 2)))) 39 | (is (= "( + 3 4 )\n-> 7" 40 | (repl* "( + 3 4 )")))) 41 | 42 | (testing "source" 43 | (is (= [:script 44 | {:type "syntaxhighlighter", :class "brush: clojure; toolbar: false; gutter: false; light: true;"} 45 | ""] 46 | (source foo))))) 47 | -------------------------------------------------------------------------------- /test/solutions/accounts_1_test.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.accounts-1-test 2 | (:use clojure.test 3 | solutions.accounts-1)) 4 | 5 | (deftest test-transfers 6 | (let [accounts (make-accounts {:initial-balance 100 7 | :count 2})] 8 | (transfer {:accounts accounts :from 0 :to 1 :amount 25}) 9 | (is (= 75 (balance accounts 0))) 10 | (is (= 125 (balance accounts 1))))) 11 | 12 | (deftest test-balance 13 | (testing "total balance is maintined when running in isolation" 14 | (let [accounts (make-accounts {:initial-balance 100 15 | :count 10})] 16 | (is (= 1000 (total-balance accounts)) "balance before") 17 | (dotimes [_ 10] (random-transfer accounts)) 18 | (is (= 1000 (total-balance accounts)) "balance after"))) 19 | (testing "total balance is *not* maintained across threads" 20 | (let [accounts (make-accounts {:initial-balance 100 21 | :count 10})] 22 | (is (thrown? 23 | Throwable 24 | #"Assert failed: \(= expected-balance \(total-balance accounts\)\)" 25 | (trial)))))) 26 | -------------------------------------------------------------------------------- /test/solutions/accounts_2_test.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.accounts-2-test 2 | (:use clojure.test 3 | solutions.accounts-2)) 4 | 5 | (deftest test-transfers 6 | (let [accounts (make-accounts {:initial-balance 100 7 | :count 2})] 8 | (transfer {:accounts accounts :from 0 :to 1 :amount 25}) 9 | (is (= 75 (balance accounts 0))) 10 | (is (= 125 (balance accounts 1))))) 11 | 12 | (deftest test-balance 13 | (testing "total balance is maintined when running in isolation" 14 | (let [accounts (make-accounts {:initial-balance 100 15 | :count 10})] 16 | (is (= 1000 (total-balance accounts)) "balance before") 17 | (dotimes [_ 10] (random-transfer accounts)) 18 | (is (= 1000 (total-balance accounts)) "balance after"))) 19 | (testing "total balance is maintained across threads" 20 | (let [accounts (make-accounts {:initial-balance 100 21 | :count 10}) 22 | trial-results (trial)] 23 | (is (= 1000 (total-balance (:accounts trial-results))))))) -------------------------------------------------------------------------------- /test/solutions/accounts_3_test.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.accounts-3-test 2 | (:use clojure.test 3 | solutions.accounts-3)) 4 | 5 | (deftest test-transfers 6 | (let [accounts (make-accounts {:initial-balance 100 7 | :count 2})] 8 | (transfer {:accounts accounts :from 0 :to 1 :amount 25}) 9 | (is (= 75 (balance accounts 0))) 10 | (is (= 125 (balance accounts 1))))) 11 | 12 | (deftest test-balance 13 | (testing "total balance is maintined when running in isolation" 14 | (let [accounts (make-accounts {:initial-balance 100 15 | :count 10})] 16 | (is (= 1000 (total-balance accounts)) "balance before") 17 | (dotimes [_ 10] (random-transfer accounts)) 18 | (is (= 1000 (total-balance accounts)) "balance after"))) 19 | (testing "total balance is maintained across threads" 20 | (let [accounts (make-accounts {:initial-balance 100 21 | :count 10}) 22 | trial-results (trial)] 23 | (is (= 1000 (total-balance (:accounts trial-results))))))) -------------------------------------------------------------------------------- /test/solutions/atom_cache_test.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.atom-cache-test 2 | (:use clojure.test) 3 | (:require [solutions.atom-cache :as cache])) 4 | 5 | (deftest test-version 6 | (let [cache (cache/create-1)] 7 | (is (nil? (cache/get cache :foo))) 8 | (is (= {:foo :bar} (cache/put-1 cache :foo :bar))))) 9 | 10 | (deftest test-improved-version 11 | (let [cache (cache/create {1 2})] 12 | (is (nil? (cache/get cache :foo))) 13 | (is (= {:foo :bar 1 2} (cache/put cache :foo :bar))) 14 | (is (= {:foo :zap 1 2} (cache/put cache {:foo :zap} ))))) 15 | -------------------------------------------------------------------------------- /test/solutions/browser_mockup_test.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.browser-mockup-test 2 | (:use clojure.test 3 | solutions.browser-mockup)) 4 | 5 | (deftest test-mockups 6 | (doseq [url ["/m1" "/m2" "/m3" "/m4"]] 7 | (let [resp (mockup-routes {:request-method :get :uri url})] 8 | (is (= {:status 200 :headers {"Content-Type" "text/html; charset=utf-8"}} 9 | (select-keys resp [:status :headers])))))) -------------------------------------------------------------------------------- /test/solutions/defstrict_test.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.defstrict-test 2 | (:use clojure.test 3 | solutions.defstrict)) 4 | 5 | (deftest test-shouts 6 | (testing "shout-1" 7 | (is (= "FOO" (shout-1 "foo"))) 8 | (is (thrown? NullPointerException (shout-1 nil))) 9 | (is (thrown? IllegalArgumentException (shout-1 :foo)))) 10 | (testing "shout-2 casts to String for performance" 11 | (is (= "FOO" (shout-2 "foo"))) 12 | (is (thrown? NullPointerException (shout-2 nil))) 13 | (is (thrown? ClassCastException (shout-2 :foo)))) 14 | (testing "shout-3 uses precondition to guarantee string input" 15 | (is (= "FOO" (shout-3 "foo"))) 16 | (is (thrown? Throwable #"Assert failed" (shout-3 nil))) 17 | (is (thrown? Throwable #"Assert failed" (shout-3 :foo)))) 18 | (testing "shout-4 works like shout-3" 19 | (is (= "FOO" (shout-4 "foo"))) 20 | (is (thrown? Throwable #"Assert failed" (shout-4 :bar))) 21 | (is (thrown? Throwable #"Assert failed" (shout-4 :foo))))) 22 | 23 | (deftest test-instance-check 24 | (is (nil? (instance-check 'foo))) 25 | (is (= '(clojure.core/instance? String foo) (instance-check '^String foo)))) 26 | 27 | (deftest test-arg-type-preconditions 28 | (is (= '{:pre 29 | [(clojure.core/instance? String a) 30 | (clojure.core/instance? Integer/TYPE b)]} 31 | (arg-type-preconditions '[^String a ^Integer/TYPE b])))) 32 | 33 | (defn argument-metadata 34 | "Return the argument metadata for a var that points 35 | to a function. List of lists for each arity." 36 | [var] 37 | (map (partial map meta) (:arglists (meta var)))) 38 | 39 | (deftest test-defstrict 40 | (is (= '(clojure.core/defn shout 41 | [s] 42 | {:pre [(clojure.core/instance? String s)]} 43 | (.toUpperCase s)) 44 | (macroexpand-1 45 | '(solutions.defstrict/defstrict shout [^String s] 46 | (.toUpperCase s))))) 47 | (is (= '(({:tag String})) 48 | (argument-metadata #'shout)))) 49 | -------------------------------------------------------------------------------- /test/solutions/looping_test.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.looping-test 2 | (:use clojure.test 3 | solutions.looping)) 4 | 5 | (deftest test-zipm 6 | (are [result keys vals] (= result (zipm-1 keys vals)) 7 | {} nil nil 8 | {:a 1} [:a] [1]) 9 | (are [result keys vals] (= result (zipm-2 keys vals)) 10 | {} nil nil 11 | {:a 1} [:a] [1]) 12 | (are [result keys vals] (= result (zipm-3 keys vals)) 13 | {} nil nil 14 | {:a 1} [:a] [1]) 15 | (are [result keys vals] (= result (zipm-4 keys vals)) 16 | {} nil nil 17 | {:a 1} [:a] [1]) 18 | (are [result keys vals] (= result (zipm-5 keys vals)) 19 | {} nil nil 20 | {:a 1} [:a] [1])) 21 | 22 | (deftest test-min 23 | (are [result vals] (= result (apply min-1 vals)) 24 | 2 [6 2 4] 25 | 2 [6 3 2 4] 26 | 0 [0]) 27 | (are [result vals] (= result (apply min-2 vals)) 28 | 2 [6 2 4] 29 | 2 [6 3 2 4] 30 | 0 [0]) 31 | (are [result vals] (= result (apply min-3 vals)) 32 | 2 [6 2 4] 33 | 2 [6 3 2 4] 34 | 0 [0]) 35 | (are [result vals] (= result (apply min vals)) 36 | 2 [6 2 4] 37 | 2 [6 3 2 4] 38 | 0 [0])) 39 | 40 | (deftest test-minmax 41 | (are [result vals] (= result (apply minmax-1 vals)) 42 | {:min 2 :max 7} [7 2 4] 43 | {:min 0 :max 0} [0]) 44 | (are [result vals] (= result (apply minmax-2 vals)) 45 | {:min 2 :max 7} [7 2 4] 46 | {:min 0 :max 0} [0])) 47 | -------------------------------------------------------------------------------- /test/solutions/ref_cache_test.clj: -------------------------------------------------------------------------------- 1 | (ns solutions.ref-cache-test 2 | (:use clojure.test) 3 | (:require [solutions.ref-cache :as cache])) 4 | 5 | (deftest test-ref-version 6 | (let [cache (cache/create {1 2})] 7 | (is (nil? (cache/get cache :foo))) 8 | (is (= {:foo :bar 1 2} (cache/put cache :foo :bar))) 9 | (is (= {:foo :zap 1 2} (cache/put cache {:foo :zap} ))) 10 | (is (= {:foo :zap 1 3} (cache/fast-put cache {1 3}))))) 11 | -------------------------------------------------------------------------------- /todo.org: -------------------------------------------------------------------------------- 1 | #+TODO: MAYBE TODO IN-PROGRESS REVIEW DONE 2 | * It's All Data [1/2] 3 | ** DONE Remove Incanter section 4 | CLOSED: [2011-05-03 Tue 15:14] 5 | ** TODO Replace Incanter example with something else 6 | * Names and Places [2/2] 7 | ** DONE Substitute c.c.math for something in core 8 | CLOSED: [2011-05-03 Tue 15:44] 9 | ** DONE Is c.c.with-ns necessary or does it need to be moved into a library 10 | CLOSED: [2011-05-04 Wed 10:41] 11 | * Mini Browser [3/3] 12 | ** DONE Send email to James discussing pulling dependencies up so things work properly with 1.3 13 | CLOSED: [2011-05-04 Wed 10:40] 14 | ** DONE Pull necessary contrib libraries for compojure/hiccup/ring into the right places 15 | CLOSED: [2011-05-04 Wed 10:40] 16 | ** DONE Send pull requests/patches out to the right places 17 | CLOSED: [2011-05-04 Wed 13:25] 18 | * Fight Solution [1/1] 19 | ** DONE Replace clojure.http.client with a straight Java API solution 20 | CLOSED: [2011-05-03 Tue 15:13] 21 | * Project Euler Solution [1/1] 22 | ** DONE Remove c.c.lazy-seqs dependency 23 | CLOSED: [2011-05-04 Wed 10:42] 24 | * Misc [4/4] 25 | ** DONE Fix broken tests 26 | CLOSED: [2011-05-04 Wed 10:52] 27 | ** DONE Add a link to the next lab if applicable 28 | CLOSED: [2011-05-05 Thu 23:16] 29 | ** DONE Add a link to the previous lab if applicable 30 | CLOSED: [2011-05-05 Thu 23:16] 31 | ** DONE Create a wiki that has a page of instructions for each editor versus everything contained in the README 32 | CLOSED: [2011-05-05 Thu 14:20] 33 | --------------------------------------------------------------------------------