├── .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+"17>"});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 1v1t>3J><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!3B>1z>\'}},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+"2W>"},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+"17>":"")+i)}H a},4f:6(a){H a?"<4a>"+a+"4a>":""},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>":"")+\'<2d 1g="17">\'+b+"2d>3P>3T>3Z>"},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 |
--------------------------------------------------------------------------------