├── examples └── exploring │ ├── gorilla.clj │ ├── webdriver.clj │ ├── atoms.clj │ ├── data_formats.clj │ ├── rotten_tomatoes.clj │ ├── persistent_data_structures.clj │ ├── rock_paper_scissors.clj │ ├── schema.clj │ ├── herbert.clj │ ├── rps_protocols.clj │ ├── seven_concurrency_models.clj │ ├── reducing_apple_pie.clj │ └── codebreaker.clj ├── .gitignore ├── test ├── run_tests.clj └── exploring │ └── convert_to_generative.clj ├── src ├── cljs │ └── scriptbowl2013 │ │ ├── macros.clj │ │ └── core.cljs └── clj │ └── exploring │ └── type_checked_namespace.clj ├── README.md ├── resources └── public │ ├── gojs.html │ └── css │ └── pure-min.css └── project.clj /examples/exploring/gorilla.clj: -------------------------------------------------------------------------------- 1 | (require '[gorilla-repl.core :as gorilla]) 2 | (gorilla/run-gorilla-server {:port 8990}) 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | .lein-repl-history 13 | .idea/* 14 | -------------------------------------------------------------------------------- /test/run_tests.clj: -------------------------------------------------------------------------------- 1 | (ns run-tests 2 | (:require [clojure.test.generative.runner :as runner])) 3 | 4 | (defn -main 5 | [& _] 6 | (System/setProperty "clojure.test.generative.msec" "10000") 7 | (System/setProperty "java.awt.headless" "true") 8 | (runner/-main "test")) 9 | -------------------------------------------------------------------------------- /examples/exploring/webdriver.clj: -------------------------------------------------------------------------------- 1 | (require '[clj-webdriver.taxi :as wd]) 2 | 3 | (wd/set-driver! {:browser :firefox} "http://clojure.org") 4 | 5 | (wd/input-text "#q" "multimethod") 6 | (wd/submit "#q") 7 | (-> (wd/find-element {:css ".WikiSearchResult a"}) 8 | wd/click) 9 | 10 | -------------------------------------------------------------------------------- /src/cljs/scriptbowl2013/macros.clj: -------------------------------------------------------------------------------- 1 | (ns scriptbowl2013.macros) 2 | 3 | (defmacro dochan [[binding chan] & body] 4 | `(let [chan# ~chan] 5 | (cljs.core.async.macros/go 6 | (loop [] 7 | (if-let [~binding (cljs.core.async/ (Set Any)]) 6 | (ann check-me [-> (Set Any)]) 7 | 8 | (typed/tc-ignore 9 | (defn dont-check-me 10 | [] 11 | :blah)) 12 | 13 | (defn check-me 14 | [] 15 | (set/difference #{1 2} #{1}) 16 | ;; uncomment to see an error with 'lein typed check' 17 | #_(set/difference #{1 2} [1 2])) 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/exploring/atoms.clj: -------------------------------------------------------------------------------- 1 | (ns exploring.atoms) 2 | 3 | ;; compare to swap-when! from 4 | ;; http://pragprog.com/book/pb7con/seven-concurrency-models-in-seven-weeks 5 | (defn swap-if! 6 | "If (pred current-value-of-atom) is true, atomically swaps the 7 | value of the atom to become (apply f current-value-of-atom args). 8 | Note that both pred and f may be called multiple times, and thus 9 | should be free of side-effects. Returns the 'new' value of the 10 | atom." 11 | [a pred f & args] 12 | (swap! a #(if (pred %) 13 | (apply f % args) 14 | %))) 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/exploring/data_formats.clj: -------------------------------------------------------------------------------- 1 | (ns exploring.data-formats 2 | (:require 3 | [clojure.repl :refer :all] 4 | [clojure.data.json :as json])) 5 | 6 | (defrecord Person 7 | [name dob interests]) 8 | 9 | (def einstein (Person. "Albert Einstein" 10 | #inst "1979-03-14" 11 | #{:thermodynamics :relativity})) 12 | 13 | (defmulti diminish 14 | "Reduce data to things JSON can handle" 15 | (fn [k v] (class v))) 16 | 17 | (defmethod diminish java.util.Date [k v] (str v)) 18 | (defmethod diminish :default [k v] v) 19 | 20 | ;; JSON loses record, keyword, set, and date types 21 | (println (json/write-str einstein :value-fn diminish)) 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/exploring/rotten_tomatoes.clj: -------------------------------------------------------------------------------- 1 | (require '[clojure.data.json :as json]) 2 | 3 | (def api-key 4 | "") 5 | 6 | (def box-office-uri 7 | (str 8 | "http://api.rottentomatoes.com/api/public/v1.0/lists/movies/box_office.json?apikey=" 9 | api-key 10 | "&limit=50")) 11 | 12 | (->> box-office-uri 13 | slurp 14 | json/read-json 15 | :movies 16 | (mapcat :abridged_cast) 17 | (map :name) 18 | frequencies 19 | (sort-by (comp - second)) 20 | (take 10)) 21 | 22 | [["Shiloh Fernandez" 2] ["Ray Liotta" 2] ["Isla Fisher" 2] ["Bradley Cooper" 2] ["Dwayne \"The Rock\" Johnson" 2] ["Morgan Freeman" 2] ["Michael Shannon" 2] ["Joel Edgerton" 2] ["Susan Sarandon" 2] ["Leonardo DiCaprio" 2]] 23 | -------------------------------------------------------------------------------- /examples/exploring/persistent_data_structures.clj: -------------------------------------------------------------------------------- 1 | (ns exploring.persistent-data-structures 2 | (:require 3 | [clojure.repl :refer :all] 4 | [clojure.set :as set])) 5 | 6 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 7 | ;; vectors 8 | 9 | (def v [42 :rabbit [1 2 3]]) 10 | 11 | (vector? v) 12 | 13 | (v 1) 14 | 15 | (get v 1) 16 | 17 | (peek v) 18 | 19 | (pop v) 20 | 21 | (subvec v 1) 22 | 23 | (assoc v 1 :badger) 24 | 25 | (vector 42 :rabbit) 26 | 27 | (vec [42 :rabbit]) 28 | 29 | ;; N.B. keys are indexes 30 | (contains? v 0) 31 | 32 | (rseq v) 33 | 34 | ((juxt first second) v) 35 | 36 | (mapv odd? (range 10)) 37 | 38 | (filterv odd? (range 10)) 39 | 40 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 41 | ;; maps 42 | 43 | (def m {:a 1 :b 2 :c 3}) 44 | 45 | (map? m) 46 | 47 | (contains? m :b) 48 | 49 | (get m :d 42) 50 | 51 | (m :b) 52 | 53 | (:b m) 54 | 55 | (keys m) 56 | 57 | (assoc m :d 4 :c 42) 58 | 59 | (dissoc m :b) 60 | 61 | (merge-with + m {:a 2 :b 3}) 62 | 63 | (hash-map :a 1 :b 2) 64 | 65 | (sorted-map :c 3 :b 2 :a 1) 66 | 67 | (sorted-map-by > 1 :a 2 :b 3 :c) 68 | 69 | (select-keys m [:a :d]) 70 | 71 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 72 | ;; sets 73 | 74 | (def colors #{:red :green :blue}) 75 | (def moods #{:happy :blue}) 76 | 77 | (disj colors :red) 78 | 79 | (set/difference colors moods) 80 | 81 | (set/intersection colors moods) 82 | 83 | (set/union colors moods) 84 | 85 | (hash-set 1 2 3) 86 | 87 | (set [1 2 3]) 88 | 89 | (sorted-set 3 2 1) 90 | 91 | (sorted-set-by > 1 2 3) 92 | 93 | 94 | -------------------------------------------------------------------------------- /examples/exploring/rock_paper_scissors.clj: -------------------------------------------------------------------------------- 1 | (ns exploring.rock-paper-scissors 2 | (:refer-clojure :exclude (==)) 3 | (:use [clojure.core.logic])) 4 | 5 | (defrel rps winner defeats loser) 6 | 7 | (fact rps :scissors :cut :paper) 8 | (fact rps :paper :covers :rock) 9 | (fact rps :rock :crushes :lizard) 10 | (fact rps :lizard :poisons :spock) 11 | (fact rps :spock :melts :scissors) 12 | (fact rps :scissors :decapitate :lizard) 13 | (fact rps :lizard :eats :paper) 14 | (fact rps :paper :disproves :spock) 15 | (fact rps :spock :vaporizes :rock) 16 | (fact rps :rock :breaks :scissors) 17 | 18 | (defrel rank title person) 19 | (fact rank :captain :kirk) 20 | (fact rank :commander :spock) 21 | (fact rank :lieutenant :uhura) 22 | (fact rank :ensign :chekov) 23 | 24 | ;; how can I defeat paper? 25 | (run* [verb] 26 | (fresh [winner] 27 | (rps winner verb :paper))) 28 | 29 | ;; what can kill? 30 | (run* [winner] 31 | (fresh [verb loser] 32 | (rps winner verb loser))) 33 | 34 | ;; find me one thing that can kill a Star Trek officer 35 | (run 1 [winner] 36 | (fresh [verb loser title] 37 | (rps winner verb loser) 38 | (rank title loser))) 39 | 40 | ;; functional approach (for comparison) 41 | (def rps-facts 42 | [[ :scissors :cut :paper ] 43 | [ :paper :covers :rock ] 44 | [ :rock :crushes :lizard ] 45 | [ :lizard :poisons :spock ] 46 | [ :spock :melts :scissors ] 47 | [ :scissors :decapitate :lizard ] 48 | [ :lizard :eats :paper ] 49 | [ :paper :disproves :spock ] 50 | [ :spock :vaporizes :rock ] 51 | [ :rock :breaks :scissors ]]) 52 | 53 | (defn wins 54 | [verb loser] 55 | (->> rps-facts 56 | (filter (fn [[_ v l]] 57 | (and (= verb v) (= l loser)))) 58 | (mapv first))) 59 | 60 | (wins :vaporizes :rock) 61 | -------------------------------------------------------------------------------- /examples/exploring/schema.clj: -------------------------------------------------------------------------------- 1 | (ns exploring-schema 2 | (:require [schema.core :as s] 3 | [schema.macros :as sm]) 4 | (:use [clojure.repl])) 5 | 6 | (doc s/check) 7 | 8 | ;; data first, the work up to exception-throwing fns 9 | (s/check s/Number 42) 10 | 11 | ;; what does an error look like? 12 | (s/check s/Keyword 42) 13 | 14 | ;; should ValidationError be a value (defrecord)? 15 | (class (s/check s/Keyword 42)) 16 | 17 | ;; validate by JVM class 18 | (s/validate java.util.Date #inst "2012") 19 | 20 | ;; nil is fine 21 | (try 22 | (s/validate java.util.Date nil) 23 | (catch Throwable t (def validation-error t))) 24 | 25 | ;; wish this was an ex-info 26 | (class validation-error) 27 | 28 | ;; check structural schema 29 | ;; check is eager (finds all the problems) 30 | (s/check {long long} {1 2 3 :c 4 :d}) 31 | 32 | (sm/defrecord Person 33 | [^String fname ^String lname]) 34 | 35 | (s/explain Person) 36 | (doc strict-map->Person) 37 | 38 | ;; metadata form for fn schema 39 | (sm/defn 40 | ^{:tag [[s/Number]]} ;; metadata must be on symbol, not arg list 41 | paired 42 | [^{:tag [s/Number]} nums] 43 | (partition 2 nums)) 44 | 45 | (s/fn-schema paired) 46 | 47 | ;; this will throw if args don't match schema 48 | (s/with-fn-validation 49 | (paired [1 2 3 4])) 50 | 51 | ;; "must satisfy" form 52 | (sm/defn 53 | tripped :- [[s/Number]] 54 | [nums :- [s/Number]] 55 | (partition 3 nums)) 56 | 57 | (s/with-fn-validation 58 | (tripped [1 2 3 4 5 6])) 59 | 60 | ;; maps with requirements 61 | (s/check {(s/required-key :foo) String 62 | long long} 63 | {1 2 :foo "wow" 3 4}) 64 | 65 | 66 | ;; map with value type requirement 67 | (s/check {(s/optional-key :foo) Number} 68 | {1 2 :foo "wow" 3 4}) 69 | 70 | 71 | ;; sequences with positional options/requirements 72 | (doc s/one) 73 | (s/check [(s/one s/String :first) 74 | (s/one s/Keyword :second) 75 | s/Number] 76 | [1 :blah 3 4 5]) 77 | -------------------------------------------------------------------------------- /examples/exploring/herbert.clj: -------------------------------------------------------------------------------- 1 | (ns exploring-herbert 2 | (:require [miner.herbert :as h] 3 | [miner.herbert.generators :as hg]) 4 | (:use [clojure.repl])) 5 | 6 | (doc h/conforms?) 7 | 8 | ;; conforms 9 | (h/conforms? 'int 42) 10 | 11 | ;; does not comform 12 | (h/conforms? 'kw 42) 13 | 14 | ;; validate by JVM class 15 | (h/conforms? [(class java.util.Date)] #inst "2012") 16 | 17 | ;; nesting shapes 18 | (h/conforms? '{kw int} {:foo 1}) 19 | (h/conforms? '{kw kw} {:foo 1}) 20 | 21 | (defrecord Person 22 | [fname lname]) 23 | 24 | (def person {:fname "Stu" :lname "Halloway"}) 25 | (def person-rec (map->Person person)) 26 | 27 | ;; shape of map / record 28 | (h/conforms? '{:fname str :lname str} person) 29 | (h/conforms? '{:fname str :lname str} person-rec) 30 | 31 | ;; type of record 32 | (h/conforms? '(tag exploring-herbert/Person) person) 33 | (h/conforms? '(tag exploring-herbert/Person) person-rec) 34 | 35 | ;; type *and* shape of record 36 | (h/conforms? '(tag exploring-herbert/Person {:fname str :lname str}) person-rec) 37 | (h/conforms? '(tag exploring-herbert/Person {:fname str}) person-rec) 38 | (h/conforms? '(tag exploring-herbert/Person {:fname str :lname "Bean"}) person-rec) 39 | 40 | ;; sequences with positional options/requirements 41 | (h/conforms? '(seq int any*) [1 :blah 3 4 5]) 42 | (h/conforms? '(seq int kw any*) [1 :blah 3 4 5]) 43 | (h/conforms? '(seq int kw str any*) [1 :blah 3 4 5]) 44 | 45 | ;; what happened when things went wrong? 46 | (h/blame '(seq int any*) [1 :blah 3 4 5]) 47 | (h/blame '(seq int kw str) [1 :blah 3 4 5]) 48 | 49 | ;; peek at the PEG internals 50 | (defn gory-details [schema] 51 | (let [grammar (h/schema->grammar schema) 52 | con-fn (h/constraint-fn schema)] 53 | (fn 54 | ([] grammar) 55 | ([x] (let [res (con-fn x)] 56 | (when (squarepeg.core/failure? res) 57 | res)))))) 58 | ((gory-details '(seq int kw str)) [1 :blah 3 4 5]) 59 | 60 | ;; make examples 61 | (hg/sample '(seq int kw*) 1) 62 | 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # exploring-clojure 2 | 3 | Template project for interactive exploration of Clojure. Will gather a 4 | kitchen sink of dependencies over time. 5 | 6 | ## Usage 7 | 8 | Start a REPL and try something. 9 | 10 | ## Clojure in Ten Big Ideas 11 | 12 | Code samples that follow [Clojure in Ten Big Ideas](). 13 | 14 | Except where otherwise noted, these are all in the examples/exploring 15 | directory, and intended to be interactively invoked, one form at a 16 | time, from the REPL. 17 | 18 | * edn: data\_formats.clj 19 | * persistent data structures: persistent\_data\_structures.clj 20 | * unified succession model: 21 | * sequences: 22 | * protocols: rps\_protocols.clj 23 | * ClojureScript: see below 24 | * reducers: reducing\_apple\_pie.clj 25 | * core.logic: rock\_paper\_scissors.clj 26 | * datalog: clone the [day of datomic](https://github.com/Datomic/day-of-datomic) repo and follow the README instructions 27 | * core.async: clone the 28 | [core.async](https://github.com/clojure/core.async) repo and work 29 | through the examples directory. 30 | 31 | ## Generative Testing 32 | 33 | Run generative tests 34 | 35 | lein run -m run-tests 36 | 37 | ## Other things to try 38 | 39 | * Keep a copy of the [Clojure 40 | cheatsheet](http://clojure.org/cheatsheet) handy. 41 | * [TryClojure](http://tryclj.com/) will let you try Clojure in the 42 | browser, without any local setup required. 43 | * [4Clojure](http://www.4clojure.com/) provides step-by-step 44 | interaction with immediate feedback. 45 | 46 | ## ClojureScript 47 | 48 | * Himera[http://himera.herokuapp.com/index.html] will let you try 49 | ClojureScript in the browser, without any local setup required. 50 | * The [modern-cljs 51 | tutorials](https://github.com/magomimmo/modern-cljs) are suitable 52 | for preparing to use ClojureScript in anger, covering toolchain and 53 | libs in addition to the language. 54 | 55 | ## License 56 | 57 | Copyright © 2013 Stuart Halloway 58 | 59 | Distributed under the Eclipse Public License, the same as Clojure. 60 | -------------------------------------------------------------------------------- /resources/public/gojs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |

Go blocks provide threadlike concurrency for 20 | JavaScript

21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 |           
32 | (go (while true
33 |   (<! (sleep 250)) 
34 |   (>! c "Red")))
35 |           
36 |         
37 | Go 38 |
39 | 40 |
41 |
42 |           
43 | (go (while true
44 |   (<! (sleep 1000)) 
45 |   (>! c "Blue")))
46 |           
47 |         
48 | Go 49 |
50 | 51 |
52 |
53 |           
54 | (go (while true
55 |   (<! (sleep 2000))
56 |   (>! c "Green")))
57 |           
58 |         
59 | Go 60 |
61 | 62 |
63 | 64 |
65 |
66 |
67 | (For more, see David 68 | Nolen's CSP 69 | post.) 70 |
71 |
72 |
73 | 74 | 75 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /examples/exploring/rps_protocols.clj: -------------------------------------------------------------------------------- 1 | ;; inspired by http://rubyquiz.com/quiz16.html 2 | ;; note that this implementation is purely functional 3 | 4 | (ns exploring.rps-protocols 5 | (:require [clojure.repl :refer :all])) 6 | 7 | (def dominates 8 | {:rock :paper 9 | :scissors :rock 10 | :paper :scissors}) 11 | 12 | (def choices (into [] (keys dominates))) 13 | 14 | (defn winner 15 | "Returns the winning choice." 16 | [p1-choice p2-choice] 17 | (cond 18 | (= p1-choice p2-choice) nil 19 | (= (dominates p1-choice) p2-choice) p2-choice 20 | :else p1-choice)) 21 | 22 | (defn draw? [me you] (= me you)) 23 | 24 | (defn iwon? [me you] (= (winner me you) me)) 25 | 26 | (defprotocol Player 27 | (choose [p] "Returns a game choice") 28 | (update-strategy [p me you] "Returns an updated player based on last play.")) 29 | 30 | (defrecord Random [] 31 | Player 32 | (choose [_] (rand-nth choices)) 33 | (update-strategy [this me you] this)) 34 | 35 | (defrecord Stubborn [choice] 36 | Player 37 | (choose [_] choice) 38 | (update-strategy [this me you] this)) 39 | 40 | (defrecord Mean [last-winner] 41 | Player 42 | (choose [_] (if last-winner last-winner (rand-nth choices))) 43 | (update-strategy [_ me you] (Mean. (when (iwon? me you) me)))) 44 | 45 | (defn mean-player 46 | "Creates a mean player" 47 | [] 48 | (->Mean nil)) 49 | 50 | (defn game 51 | "Pit players p1 and p2 against each other for rounds. 52 | returning map with :p1 and :p2 keys for their scores." 53 | [p1 p2 rounds] 54 | (loop [p1 p1 55 | p2 p2 56 | p1-score 0 57 | p2-score 0 58 | rounds rounds] 59 | (if (pos? rounds) 60 | (let [p1-choice (choose p1) 61 | p2-choice (choose p2) 62 | result (winner p1-choice p2-choice)] 63 | (recur 64 | (update-strategy p1 p1-choice p2-choice) 65 | (update-strategy p2 p2-choice p1-choice) 66 | (+ p1-score (if (= result p1-choice) 1 0)) 67 | (+ p2-score (if (= result p2-choice) 1 0)) 68 | (dec rounds))) 69 | {:p1 p1-score :p2 p2-score}))) 70 | 71 | (game (->Random) (->Random) 100) 72 | 73 | (game (->Random) (->Stubborn :rock) 100) 74 | 75 | (game (mean-player) (->Stubborn :rock) 100) 76 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject exploring-clojure "0.1.0-SNAPSHOT" 2 | :description "Template project for interactive exploration of Clojure" 3 | :license {:name "Eclipse Public License" 4 | :url "http://www.eclipse.org/legal/epl-v10.html"} 5 | :dependencies [[org.clojure/clojure "1.7.0-RC1"] 6 | [org.clojure/clojurescript "0.0-1889"] 7 | [org.clojure/core.async "0.1.278.0-76b25b-alpha" #_"0.1.242.0-44b1e3-alpha" #_"0.1.222.0-83d0c2-alpha"] 8 | [org.clojure/core.logic "0.6.6"] 9 | [org.clojure/core.match "0.2.0-rc6"] 10 | #_[org.clojure/core.typed "0.2.13"] 11 | [org.clojure/data.json "0.2.3"] 12 | [org.clojure/math.combinatorics "0.0.4"] 13 | [org.clojure/test.generative "0.5.1"] 14 | [org.clojure/test.check "0.5.9"] 15 | [com.datomic/datomic-free "0.9.4699"] 16 | [com.datomic/simulant "0.1.7"] 17 | [ring/ring-jetty-adapter "1.3.2" :exclusions [org.clojure/clojure]] 18 | [gorilla-repl "0.3.3"] 19 | [clj-webdriver "0.6.1"] 20 | [liberator "0.12.2"] 21 | [clj-time "0.6.0"] 22 | [criterium/criterium "0.3.1"] 23 | [dorothy "0.0.3"] 24 | [enlive/enlive "1.1.4"] 25 | [com.velisco/herbert "0.6.2"] 26 | [prismatic/schema "0.1.5"] 27 | [quil "1.6.0"] 28 | [rhizome "0.2.0"]] 29 | :plugins [#_[lein-typed "0.3.0"] [lein-cljsbuild "0.3.2"]] 30 | :source-paths ["src/clj" "src/cljs"] 31 | :core.typed {:check [exploring.type-checked-namespace]} 32 | :profiles {:dev {:jvm-opts ["-Xmx6g" "-server"]}} 33 | :cljsbuild {:builds 34 | {:dev {:source-paths ["src/cljs"] 35 | :compiler {:output-to "resources/public/javascript/exploring-clojure.js" 36 | :pretty-print true 37 | :optimizations :simple}} 38 | :prod {:source-paths ["src/cljs"] 39 | :compiler { :output-to "resources/public/javascript/exploring-clojure-min.js" 40 | :pretty-print false 41 | :optimizations :advanced}}}}) 42 | -------------------------------------------------------------------------------- /test/exploring/convert_to_generative.clj: -------------------------------------------------------------------------------- 1 | (ns exploring.convert-to-generative 2 | (:require 3 | [clojure.set :as set] 4 | [clojure.test :as test :refer (deftest is are)] 5 | [clojure.test.generative :refer (defspec)])) 6 | 7 | ;; TODO: design generative tests that cover the same domains as the 8 | ;; clojure.test tests below. 9 | 10 | (deftest test-union 11 | (are [x y] (= x y) 12 | (set/union) #{} 13 | 14 | ; identity 15 | (set/union #{}) #{} 16 | (set/union #{1}) #{1} 17 | (set/union #{1 2 3}) #{1 2 3} 18 | 19 | ; 2 sets, at least one is empty 20 | (set/union #{} #{}) #{} 21 | (set/union #{} #{1}) #{1} 22 | (set/union #{} #{1 2 3}) #{1 2 3} 23 | (set/union #{1} #{}) #{1} 24 | (set/union #{1 2 3} #{}) #{1 2 3} 25 | 26 | ; 2 sets 27 | (set/union #{1} #{2}) #{1 2} 28 | (set/union #{1} #{1 2}) #{1 2} 29 | (set/union #{2} #{1 2}) #{1 2} 30 | (set/union #{1 2} #{3}) #{1 2 3} 31 | (set/union #{1 2} #{2 3}) #{1 2 3} 32 | 33 | ; 3 sets, some are empty 34 | (set/union #{} #{} #{}) #{} 35 | (set/union #{1} #{} #{}) #{1} 36 | (set/union #{} #{1} #{}) #{1} 37 | (set/union #{} #{} #{1}) #{1} 38 | (set/union #{1 2} #{2 3} #{}) #{1 2 3} 39 | 40 | ; 3 sets 41 | (set/union #{1 2} #{3 4} #{5 6}) #{1 2 3 4 5 6} 42 | (set/union #{1 2} #{2 3} #{1 3 4}) #{1 2 3 4} 43 | 44 | ; different data types 45 | (set/union #{1 2} #{:a :b} #{nil} #{false true} #{\c "abc"} #{[] [1 2]} 46 | #{{} {:a 1}} #{#{} #{1 2}}) 47 | #{1 2 :a :b nil false true \c "abc" [] [1 2] {} {:a 1} #{} #{1 2}} 48 | 49 | ; different types of sets 50 | (set/union (hash-set) (hash-set 1 2) (hash-set 2 3)) 51 | (hash-set 1 2 3) 52 | (set/union (sorted-set) (sorted-set 1 2) (sorted-set 2 3)) 53 | (sorted-set 1 2 3) 54 | (set/union (hash-set) (hash-set 1 2) (hash-set 2 3) 55 | (sorted-set) (sorted-set 4 5) (sorted-set 5 6)) 56 | (hash-set 1 2 3 4 5 6))) 57 | 58 | (def ^:dynamic *test-value* 1) 59 | 60 | (deftest future-fn-properly-retains-conveyed-bindings 61 | (let [a (atom [])] 62 | (binding [*test-value* 2] 63 | @(future (dotimes [_ 3] 64 | ;; we need some binding to trigger binding pop 65 | (binding [*print-dup* false] 66 | (swap! a conj *test-value*)))) 67 | (is (= [2 2 2] @a))))) 68 | 69 | (deftest namespaced-keys-in-destructuring 70 | (let [{:keys [a/b c/d]} {:a/b 1 :c/d 2}] 71 | (is (= 1 b)) 72 | (is (= 2 d)))) 73 | -------------------------------------------------------------------------------- /src/cljs/scriptbowl2013/core.cljs: -------------------------------------------------------------------------------- 1 | ;; Most of the code here is stripped down and modified from David 2 | ;; Nolen's excellent JavaScript examples. 3 | ;; 4 | ;; See: 5 | ;; http://swannodette.github.io/2013/07/12/communicating-sequential-processes/ 6 | ;; https://github.com/swannodette/swannodette.github.com 7 | 8 | (ns scriptbowl2013.core 9 | (:require [goog.events :as events] 10 | [goog.events.EventType] 11 | [cljs.core.async :refer [>! event-type 16 | "Map keyword names to goog event constants." 17 | {:keyup goog.events.EventType.KEYUP 18 | :keydown goog.events.EventType.KEYDOWN 19 | :keypress goog.events.EventType.KEYPRESS 20 | :click goog.events.EventType.CLICK 21 | :dblclick goog.events.EventType.DBLCLICK 22 | :mousedown goog.events.EventType.MOUSEDOWN 23 | :mouseup goog.events.EventType.MOUSEUP 24 | :mouseover goog.events.EventType.MOUSEOVER 25 | :mouseout goog.events.EventType.MOUSEOUT 26 | :mousemove goog.events.EventType.MOUSEMOVE 27 | :focus goog.events.EventType.FOCUS 28 | :blur goog.events.EventType.BLUR}) 29 | 30 | (defn listen 31 | "Put DOM events on a channel" 32 | ([el type] (listen el type (chan))) 33 | ([el type out] 34 | (events/listen el (keyword->event-type type) 35 | (partial put! out)) 36 | out)) 37 | 38 | (defn by-id [id] 39 | (.getElementById js/document id)) 40 | 41 | (defn set-html! [el s] 42 | (set! (.-innerHTML el) s)) 43 | 44 | (defn event-target-id 45 | [e] 46 | (-> e .-currentTarget .-id)) 47 | 48 | (defn log 49 | "Return a channel that logs events from in and passes 50 | them on." 51 | [in] 52 | (let [out (chan)] 53 | (dochan [e in] 54 | (.log js/console e) 55 | (>! out e)) 56 | out)) 57 | 58 | (defn render 59 | [coll] 60 | (apply str 61 | (for [p coll] 62 | (str "
" p "
")))) 63 | 64 | (defn update-loop 65 | "Render a sliding-buffer view of the elements appearing 66 | on channel ch." 67 | [ch] 68 | (go (loop [coll (list "Ready")] 69 | (set-html! (by-id "results") 70 | (render coll)) 71 | (recur 72 | (->> coll (cons (! ch "Red"))) 88 | "go2" (go (while true 89 | (! ch "Blue"))) 91 | "go3" (go (while true 92 | (! ch "Green")))))))) 94 | 95 | (defn ^:export start 96 | [] 97 | (.log js/console "Starting") 98 | (let [buttons (mapv by-id ["go1" "go2" "go3"]) 99 | control-ch (chan) 100 | update-ch (chan)] 101 | (control-loop (->> control-ch (map< event-target-id) log) 102 | update-ch) 103 | (update-loop update-ch) 104 | (doseq [b buttons] 105 | (listen b :click control-ch))) 106 | (.log js/console "Started")) 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /examples/exploring/seven_concurrency_models.clj: -------------------------------------------------------------------------------- 1 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 2 | ;; Exploration of some ideas presented in 3 | ;; http://pragprog.com/book/pb7con/seven-concurrency-models-in-seven-weeks 4 | 5 | (ns exploring.seven-concurrency-models 6 | (:require 7 | [clojure.core.async :refer (go go-loop ! map< remove< chan put! 8 | close! pub sub) :as async] 9 | [clojure.core.match :refer (match)] 10 | [clojure.repl :refer :all])) 11 | 12 | 13 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 14 | ;; talker 1: using channels, go, and closed dispatch 15 | (defn process-1 16 | [item] 17 | (match [item] 18 | [[:greet & [name]]] (str "Hello " name) 19 | [[:praise & [name]]] (str name ", you're amazing") 20 | [[:celebrate & [name age]]] (str "Here's to another " age " years, " name))) 21 | 22 | ;; test fn first 23 | (process-1 [:greet "Huey"]) 24 | (process-1 [:praise "Dewey"]) 25 | (process-1 [:celebrate "Louie" 16]) 26 | 27 | ;; then go async 28 | (def talker-ch-1 (chan)) 29 | 30 | (def composed-ch-1 31 | (->> talker-ch-1 (map< process-1))) 32 | 33 | (def loop-1 34 | (go-loop [msg (> talker-ch-2 (map< process-2))) 71 | 72 | (def loop-2 73 | (go-loop [msg (> apples 33 | (filter :edible?) 34 | (map #(dissoc % :sticker?)) 35 | count)) 36 | 37 | (prepare-with-seqs apples) 38 | 39 | (defn counter 40 | ([] 0) 41 | ([x _] (inc x))) 42 | 43 | (defn prepare-with-reduce 44 | "Prepare the apples by reducing. Returns count of apples prepared." 45 | [apples] 46 | (->> apples 47 | (r/filter :edible?) 48 | (r/map #(dissoc % :sticker?)) 49 | (r/reduce counter))) 50 | 51 | (prepare-with-reduce apples) 52 | 53 | (defn prepare-with-fold 54 | [apples] 55 | (->> apples 56 | (r/filter :edible?) 57 | (r/map #(dissoc % :sticker?)) 58 | (r/fold counter))) 59 | 60 | (prepare-with-fold apples) 61 | 62 | (defn prepare-with-partition-then-fold 63 | "Prepare the apples by paritioning, then folding the partitions, then 64 | reducing the fold results. This demonstrates using fold as part of 65 | a larger strategy, but does *not* demonstrate doing anything particularly 66 | clever to feed the folds, e.g. with a data file in hand you could do 67 | better than starting with a sequence." 68 | ([apples] (prepare-with-partition-then-fold apples 100000)) 69 | ([apples n] 70 | (->> apples 71 | (partition-all n) 72 | (map #(prepare-with-fold (into [] %))) 73 | (reduce +)))) 74 | 75 | (prepare-with-partition-then-fold apples 3) 76 | 77 | (defn prepare-with-partition-pmap 78 | ([apples] (prepare-with-partition-then-fold apples 100000)) 79 | ([apples n] 80 | (->> apples 81 | (partition-all n) 82 | (pmap prepare-with-seqs) 83 | (reduce +)))) 84 | 85 | (defn bench 86 | "Wrap criterium so that it does not use stdout" 87 | [f] 88 | (let [s (java.io.StringWriter.)] 89 | (binding [*out* s] 90 | (assoc (crit/quick-benchmark* f) 91 | :stdout (str s))))) 92 | 93 | (defn bench-preparers 94 | [] 95 | (let [mean-msec #(long (* 1000 (first (:sample-mean %))))] 96 | (->> (for [napples [100000 1000000] 97 | :let [apples (into [] (gen-apples {:n napples :type :golden :stickers 1 :edible 0.8}))] 98 | sym '[prepare-with-seqs prepare-with-reduce prepare-with-fold]] 99 | (do 100 | (print "Testing " sym " " napples ": ") (flush) 101 | (let [result (bench #((resolve sym) apples))] 102 | (println (mean-msec result) " msec") 103 | {:op sym 104 | :napples napples 105 | :result result}))) 106 | (map 107 | (fn [{:keys [op napples result]}] 108 | {"Operation" op 109 | "Apples" napples 110 | "Mean Time (msec)" (mean-msec result)})) 111 | (pprint/print-table)))) 112 | 113 | (bench-preparers) 114 | 115 | (doseq [n (range 5 9)] 116 | (doseq [v [#'prepare-with-partition-then-fold #'prepare-with-partition-pmap]] 117 | (dotimes [_ 2] 118 | (println "Timing " v " with 1e" n) 119 | (time 120 | (prepare-with-partition-then-fold 121 | (gen-apples {:n (long (Math/pow 10 n)) :type :golden :stickers 1 :edible 0.8}) 122 | 1000000))))) 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /examples/exploring/codebreaker.clj: -------------------------------------------------------------------------------- 1 | ;; iteratively develop and test a codebreaker scorer at a Clojure REPL 2 | ;; compare with the example-based version described in The RSpec Book 3 | ;; http://pragprog.com/book/achbd/the-rspec-book 4 | 5 | (ns exploring.codebreaker 6 | (:require 7 | [clojure.repl :refer :all] 8 | [clojure.java.io :as io] 9 | [clojure.math.combinatorics :as comb] 10 | [clojure.pprint :as pp] 11 | [clojure.set :as set] 12 | [clojure.data.generators :as gen] 13 | [clojure.test.generative :refer (defspec)] 14 | [clojure.test.generative.runner :as runner])) 15 | 16 | (def secret [:r :g :b :y]) 17 | (def guess [:g :g :g :r]) 18 | ;; score should be 1 black, 1 white 19 | 20 | (->> (map = secret guess) 21 | (filter identity) 22 | count) 23 | 24 | (defn exact-matches 25 | "Returns the count of matches in the same position in 26 | both a and b." 27 | [a b] 28 | (->> (map = a b) 29 | (filter identity) 30 | count)) 31 | 32 | (exact-matches secret guess) 33 | 34 | ;; iteratively develop all-matches 35 | (merge-with min 36 | (frequencies secret) 37 | (frequencies guess)) 38 | 39 | (defn all-matches 40 | "Returns the count of matches regardless of position in 41 | a and b." 42 | [a b] 43 | (->> (merge-with min 44 | (select-keys (frequencies a) b) 45 | (select-keys (frequencies b) a)) 46 | vals 47 | (reduce +))) 48 | 49 | (all-matches secret guess) 50 | 51 | (defn create-scorer 52 | "Given a secret, return a function of a guess that scores 53 | that guess." 54 | [secret] 55 | (fn [guess] 56 | (let [exact (exact-matches secret guess) 57 | all (all-matches secret guess)] 58 | {:exact exact :unordered (- all exact)}))) 59 | 60 | (def scorer (create-scorer secret)) 61 | (scorer guess) 62 | (scorer secret) 63 | 64 | ;; property-based test 65 | (defn validate-score 66 | "Validate that exact, unordered are the correct score for 67 | secret/guess combo a,b." 68 | [a b {:keys [exact unordered]}] 69 | (let [desc {:a a, :b b, :exact exact, :unordered unordered} 70 | fail #(throw (ex-info % desc))] 71 | (when-not (<= 0 exact) 72 | (fail "Exact matches should not be negative")) 73 | (when-not (<= 0 unordered) 74 | (fail "Unordered matches should not be negative")) 75 | (when-not (<= (+ exact unordered) (count a)) 76 | (fail "Total matches should be <= size of secret")) 77 | (let [match-set (set/intersection (into #{} a) (into #{} b))] 78 | (when-not (<= (count match-set) (+ exact unordered)) 79 | (fail "Set intersection count should be <= total match count"))))) 80 | 81 | (validate-score secret guess (scorer secret)) 82 | 83 | (set! *print-length* 100) 84 | 85 | (comb/selections [:r :b] 3) 86 | 87 | (map vec (comb/selections [:r :b] 3)) 88 | 89 | (-> (map vec (comb/selections [:r :b] 2)) 90 | (comb/selections 2)) 91 | 92 | ;; exhaustive testing 93 | (defn all-turns 94 | "Returns all possible pairs of score, guess for a codebreaker 95 | game with colors and n columns" 96 | [colors n] 97 | (-> (map vec (comb/selections colors n)) 98 | (comb/selections 2))) 99 | 100 | (all-turns [:r :b] 3) 101 | 102 | (defn test-scoring 103 | [[secret guess]] 104 | (let [scorer (create-scorer secret) 105 | score (scorer guess)] 106 | ;; validates the score 107 | (validate-score secret guess score) 108 | ;; returns secret, guess, and score 109 | {:secret secret 110 | :guess guess 111 | :score score})) 112 | 113 | (->> (all-turns [:r :g :b] 2) 114 | (map test-scoring) 115 | pp/print-table) 116 | 117 | (time (->> (all-turns [:r :g :b :y] 3) 118 | (map test-scoring) 119 | count)) 120 | 121 | ;; ~15 seconds to test 6x4 122 | (time (->> (all-turns [:r :g :b :y :p :o] 4) 123 | (map test-scoring) 124 | count)) 125 | 126 | (def colors [:r :g :b :y :p :o]) 127 | 128 | ;; generative testing 129 | (gen/vec #(gen/rand-nth colors) 2) 130 | 131 | (defn gen-guess 132 | [colors n] 133 | (gen/vec #(gen/rand-nth colors) n)) 134 | 135 | (gen-guess colors 6) 136 | 137 | (defn gen-turn 138 | "Returns a [secret, guess] pair." 139 | [colors n] 140 | [(gen-guess colors n) 141 | (gen-guess colors n)]) 142 | 143 | (gen-turn colors 6) 144 | 145 | ;; more than 1000x bigger job than 6x4 146 | (defn turn-6x6 147 | [] 148 | (gen-turn colors 6)) 149 | 150 | (def t (turn-6x6)) 151 | 152 | (defn score-turn 153 | [[secret guess]] 154 | (let [scorer (create-scorer secret)] 155 | (scorer guess))) 156 | 157 | (score-turn t) 158 | 159 | (defspec test-scoring 160 | score-turn 161 | [^{:tag `turn-6x6} turn] 162 | (let [[secret guess] turn] 163 | (validate-score secret guess %) 164 | {:secret secret 165 | :guess guess 166 | :score %})) 167 | 168 | ;; example 169 | (test-scoring t) 170 | 171 | ;; interactive 172 | (runner/run 1 1000 #'test-scoring) 173 | (ex-data *e) 174 | (runner/run 2 1000 #'test-scoring) 175 | (runner/run 16 1000 #'test-scoring) 176 | 177 | ;; ci usage 178 | (runner/run-suite {:nthreads 2 :msec 1000} 179 | (runner/get-tests #'test-scoring)) 180 | 181 | (defn win-6x6 182 | "Returns a winning 6x6 turn." 183 | [] 184 | (let [g (gen-guess colors 6)] 185 | [g g])) 186 | 187 | (def win (win-6x6)) 188 | 189 | (defspec test-winning-score 190 | score-turn 191 | [^{:tag `win-6x6} turn] 192 | (when-not (= % {:exact 6 :unordered 0}) 193 | (throw (ex-info "Winning turn did not get a winning score" {:turn turn :score %})))) 194 | 195 | (test-winning-score win) 196 | 197 | (runner/run 2 1000 #'test-winning-score) 198 | 199 | ;; using test.check 200 | (do 201 | (require '[clojure.test.check :as tc]) 202 | (require '[clojure.test.check.generators :as tcgen]) 203 | (require '[clojure.test.check.properties :as tcprop])) 204 | 205 | (defn tcgen-turn 206 | "Create a test.check generator for turn." 207 | [colors n] 208 | (let [play (tcgen/vector (tcgen/elements colors) n)] 209 | (tcgen/tuple play play))) 210 | 211 | ;; test the generator 212 | (tcgen/sample (tcgen-turn colors 6) 2) 213 | 214 | (def matches-not-greater-than-secret-prop 215 | (tcprop/for-all 216 | [turn (tcgen-turn colors 6)] 217 | (let [{:keys [exact unordered]} (score-turn turn)] 218 | (<= (+ exact unordered) (count (first turn)))))) 219 | 220 | (tc/quick-check 100 matches-not-greater-than-secret-prop) 221 | 222 | ;; N.B. this is deliberately untrue 223 | (def matches-less-than-secret-prop 224 | (tcprop/for-all 225 | [turn (tcgen/shrink-2 (tcgen-turn colors 6))] 226 | (let [{:keys [exact unordered]} (score-turn turn)] 227 | (< (+ exact unordered) (count (first turn)))))) 228 | 229 | (tc/quick-check 1000 matches-less-than-secret-prop) 230 | 231 | 232 | 233 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /resources/public/css/pure-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.3.0 3 | Copyright 2013 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v1.1.2 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v1.1.2 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}.pure-button{display:inline-block;*display:inline;zoom:1;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-size:100%;*font-size:90%;*overflow:visible;padding:.5em 1.5em;color:#444;color:rgba(0,0,0,.8);*color:#444;border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px;-webkit-transition:.1s linear -webkit-box-shadow;-moz-transition:.1s linear -moz-box-shadow;-ms-transition:.1s linear box-shadow;-o-transition:.1s linear box-shadow;transition:.1s linear box-shadow}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-ms-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;font-size:.8em;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-transition:.3s linear border;-moz-transition:.3s linear border;-ms-transition:.3s linear border;-o-transition:.3s linear border;transition:.3s linear border;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;outline:thin dotted \9;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin dotted #333;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border:1px solid #ee5f5b}.pure-form input:focus:invalid:focus,.pure-form textarea:focus:invalid:focus,.pure-form select:focus:invalid:focus{border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em;font-size:90%}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;font-size:125%;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 10em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input{display:block;padding:10px;margin:0;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus{z-index:2}.pure-form .pure-group input:first-child{top:1px;border-radius:4px 4px 0 0}.pure-form .pure-group input:last-child{top:-2px;border-radius:0 0 4px 4px}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:90%}.pure-form-message{display:block;color:#666;font-size:90%}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:80%;padding:.2em 0 .8em}}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-5-24,.pure-u-7-24,.pure-u-11-24,.pure-u-13-24,.pure-u-17-24,.pure-u-19-24,.pure-u-23-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1{width:100%}.pure-u-1-2{width:50%;*width:49.969%}.pure-u-1-3{width:33.3333%;*width:33.3023%}.pure-u-2-3{width:66.6667%;*width:66.6357%}.pure-u-1-4{width:25%;*width:24.969%}.pure-u-3-4{width:75%;*width:74.969%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-1-6{width:16.6667%;*width:16.6357%}.pure-u-5-6{width:83.3333%;*width:83.3023%}.pure-u-1-8{width:12.5%;*width:12.469%}.pure-u-3-8{width:37.5%;*width:37.469%}.pure-u-5-8{width:62.5%;*width:62.469%}.pure-u-7-8{width:87.5%;*width:87.469%}.pure-u-1-12{width:8.3333%;*width:8.3023%}.pure-u-5-12{width:41.6667%;*width:41.6357%}.pure-u-7-12{width:58.3333%;*width:58.3023%}.pure-u-11-12{width:91.6667%;*width:91.6357%}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-g-r{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap}.opera-only :-o-prefocus,.pure-g-r{word-spacing:-.43em}.pure-g-r [class *="pure-u"]{font-family:sans-serif}.pure-g-r img{max-width:100%;height:auto}@media (min-width:980px){.pure-visible-phone{display:none}.pure-visible-tablet{display:none}.pure-hidden-desktop{display:none}}@media (max-width:480px){.pure-g-r>.pure-u,.pure-g-r>[class *="pure-u-"]{width:100%}}@media (max-width:767px){.pure-g-r>.pure-u,.pure-g-r>[class *="pure-u-"]{width:100%}.pure-hidden-phone{display:none}.pure-visible-desktop{display:none}}@media (min-width:768px) and (max-width:979px){.pure-hidden-tablet{display:none}.pure-visible-desktop{display:none}}.pure-menu ul{position:absolute;visibility:hidden}.pure-menu.pure-menu-open{visibility:visible;z-index:2;width:100%}.pure-menu ul{left:-10000px;list-style:none;margin:0;padding:0;top:-10000px;z-index:1}.pure-menu>ul{position:relative}.pure-menu-open>ul{left:0;top:0;visibility:visible}.pure-menu-open>ul:focus{outline:0}.pure-menu li{position:relative}.pure-menu a,.pure-menu .pure-menu-heading{display:block;color:inherit;line-height:1.5em;padding:5px 20px;text-decoration:none;white-space:nowrap}.pure-menu.pure-menu-horizontal>.pure-menu-heading{display:inline-block;*display:inline;zoom:1;margin:0;vertical-align:middle}.pure-menu.pure-menu-horizontal>ul{display:inline-block;*display:inline;zoom:1;vertical-align:middle;height:2.4em}.pure-menu li a{padding:5px 20px}.pure-menu-can-have-children>.pure-menu-label:after{content:'\25B8';float:right;font-family:'Lucida Grande','Lucida Sans Unicode','DejaVu Sans',sans-serif;margin-right:-20px;margin-top:-1px}.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-separator{background-color:#dfdfdf;display:block;height:1px;font-size:0;margin:7px 2px;overflow:hidden}.pure-menu-hidden{display:none}.pure-menu-fixed{position:fixed;top:0;left:0;width:100%}.pure-menu-horizontal li{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-horizontal li li{display:block}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label:after{content:"\25BE"}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-horizontal li.pure-menu-separator{height:50%;width:1px;margin:0 7px}.pure-menu-horizontal li li.pure-menu-separator{height:1px;width:auto;margin:7px 2px}.pure-menu.pure-menu-open,.pure-menu.pure-menu-horizontal li .pure-menu-children{background:#fff;border:1px solid #b7b7b7}.pure-menu.pure-menu-horizontal,.pure-menu.pure-menu-horizontal .pure-menu-heading{border:0}.pure-menu a{border:1px solid transparent;border-left:0;border-right:0}.pure-menu a,.pure-menu .pure-menu-can-have-children>li:after{color:#777}.pure-menu .pure-menu-can-have-children>li:hover:after{color:#fff}.pure-menu .pure-menu-open{background:#dedede}.pure-menu li a:hover,.pure-menu li a:focus{background:#eee}.pure-menu li.pure-menu-disabled a:hover,.pure-menu li.pure-menu-disabled a:focus{background:#fff;color:#bfbfbf}.pure-menu .pure-menu-disabled>a{background-image:none;border-color:transparent;cursor:default}.pure-menu .pure-menu-disabled>a,.pure-menu .pure-menu-can-have-children.pure-menu-disabled>a:after{color:#bfbfbf}.pure-menu .pure-menu-heading{color:#565d64;text-transform:uppercase;font-size:90%;margin-top:.5em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#dfdfdf}.pure-menu .pure-menu-selected a{color:#000}.pure-menu.pure-menu-open.pure-menu-fixed{border:0;border-bottom:1px solid #b7b7b7}.pure-paginator{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;list-style:none;margin:0;padding:0}.opera-only :-o-prefocus,.pure-paginator{word-spacing:-.43em}.pure-paginator li{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-paginator .pure-button{border-radius:0;padding:.8em 1.4em;vertical-align:top;height:1.1em}.pure-paginator .pure-button:focus,.pure-paginator .pure-button:active{outline-style:none}.pure-paginator .prev,.pure-paginator .next{color:#C0C1C3;text-shadow:0 -1px 0 rgba(0,0,0,.45)}.pure-paginator .prev{border-radius:2px 0 0 2px}.pure-paginator .next{border-radius:0 2px 2px 0}@media (max-width:480px){.pure-menu-horizontal{width:100%}.pure-menu-children li{display:block;border-bottom:1px solid #000}}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:6px 12px}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child td,.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0} 12 | --------------------------------------------------------------------------------