├── Makefile
├── doc
└── intro.md
├── .gitignore
├── .dir-locals.el
├── src
└── clojure_through_code
│ ├── core.clj
│ ├── visualisation.clj
│ ├── juxt.clj
│ ├── 06_getting_comfy.clj
│ ├── database.clj
│ ├── main_namespace.clj
│ ├── xx-including-libraries.clj
│ ├── spec_definitions.clj
│ ├── parse_cli_options.clj
│ ├── xx_swing_example.clj
│ ├── xx_weird.clj
│ ├── terminology.md
│ ├── xx-drawing-demo.clj
│ ├── destructuring.clj
│ ├── spec.clj
│ ├── xx_emacs_fun.clj
│ ├── error_handling.clj
│ ├── debugging_with_cider.clj
│ ├── scip.clj
│ ├── xx-reactive-pattern-matching.clj
│ ├── keywords.clj
│ ├── benchmark.clj
│ ├── 09_calling_java.clj
│ ├── 05_core_functions.clj
│ ├── hhgttg-book-common-words.clj
│ ├── 07_list_comprehension.clj
│ ├── 11_macros.clj
│ ├── regular_expressions.clj
│ ├── 06a_lazy_evaluation.clj
│ ├── most_common_word.clj
│ ├── xx-colonial-blotto.clj
│ ├── thinking_functional_demo.clj
│ ├── collections.clj
│ ├── pretty_tables.clj
│ ├── xx-fibonachi.clj
│ ├── debug.clj
│ ├── 03a_changing_data_structures.clj
│ ├── composing_functions.clj
│ ├── abstractions_patterns.clj
│ ├── core_async.clj
│ ├── 10_changing_state.clj
│ ├── undefine.clj
│ ├── xx-control-flow.clj
│ ├── fifteen_minutes.clj
│ ├── 02_data_structures.clj
│ ├── 03-using-data-structures.clj
│ ├── clojurebridge_exercises.clj
│ └── working_with_maps.clj
├── deps.edn
├── ideas.md
├── resources
└── common-english-words.csv
├── test
└── clojure_through_code
│ └── core_test.clj
└── README.md
/Makefile:
--------------------------------------------------------------------------------
1 | ../Makefile
--------------------------------------------------------------------------------
/doc/intro.md:
--------------------------------------------------------------------------------
1 | # Introduction to devoxxuk
2 |
3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/)
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | pom.xml
5 | pom.xml.asc
6 | *.jar
7 | *.class
8 | /.lein-*
9 | /.nrepl-port
10 |
--------------------------------------------------------------------------------
/.dir-locals.el:
--------------------------------------------------------------------------------
1 | ((clojure-mode . ((cider-preferred-build-tool . "clojure-cli")
2 | (cider-clojure-cli-aliases . ":lib/reloaded"))))
3 |
--------------------------------------------------------------------------------
/src/clojure_through_code/core.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.core)
2 |
3 |
4 | (defn adder
5 | "A simple function to add two numbers"
6 | [a b]
7 | (+ a b))
8 |
--------------------------------------------------------------------------------
/src/clojure_through_code/visualisation.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.visualisation)
2 |
3 |
4 | ;; Example data
5 |
6 | {:england 511585 :scotland 37998 :wales 30077 :northern-ireland 18190}
7 |
--------------------------------------------------------------------------------
/src/clojure_through_code/juxt.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.juxt)
2 |
3 |
4 | ;; make a map out of some identifiers/values;
5 | ;; this that those -> {:this this :that that :those those}
6 |
7 | (into {}
8 | (mapv (juxt keyword identity) ['this 'that 'those]))
9 |
--------------------------------------------------------------------------------
/src/clojure_through_code/06_getting_comfy.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.06-getting-comfy)
2 |
3 |
4 | ;;
5 | ;; Getting more comfortable with Clojure
6 |
7 | ;; 4Clojure
8 | ;; Clojure Koans
9 |
10 | ;; Using the REPL to get fast feedback
11 |
12 | ;; LightTable Instarepl
13 | ;; Emacs REPL & evaluating in-line
14 |
15 | ;; Most Java IDE's have REPL support for Clojure
16 |
--------------------------------------------------------------------------------
/src/clojure_through_code/database.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.database)
2 |
3 |
4 | (defn login
5 | "A mock login function to access a database"
6 | [username password]
7 | (str "You logged in using " username " & " password))
8 |
9 |
10 | (defn logout
11 | "A mock logout function to access a database"
12 | [username]
13 | (str username " just logged out"))
14 |
--------------------------------------------------------------------------------
/src/clojure_through_code/main_namespace.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.main-namespace
2 | (:require
3 | [clojure.string :as string]))
4 |
5 |
6 | (defn -main
7 | "Entry point into the application"
8 | [& args]
9 | (if (seq? args)
10 | (println "The application is running with these arguments:"
11 | (string/join " " args))
12 | (println "The application is running...")))
13 |
--------------------------------------------------------------------------------
/src/clojure_through_code/xx-including-libraries.clj:
--------------------------------------------------------------------------------
1 | ;; How to work with libraries in Clojure
2 |
3 | ;; What is in every namespace
4 |
5 | ;; clojure.core
6 | ;; java.lang
7 |
8 |
9 | ;; What just needs including in the code
10 |
11 | ;; Java SDK ?
12 | ;; Leiningen ?
13 |
14 | ;; Dependencies
15 |
16 | ;; Add dependencies using Leiningen in the project.clj file (see leiningen section)
17 | ;; or Maven pom.xml (see google)
18 |
19 |
20 | ;; require
21 |
22 | ;; use
23 |
24 | (use)
25 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "resources"]
2 |
3 | :deps
4 | {org.clojure/clojure {:mvn/version "1.12.0"}
5 |
6 | ;; Logic
7 | org.clojure/core.match {:mvn/version "1.1.0"}
8 |
9 | ;; table/table {:mvn/version "0.5.0"} ;; use clojure.pprint/print-table instead
10 |
11 | ;; Date and time
12 | clojure.java-time/clojure.java-time {:mvn/version "1.4.3"}
13 |
14 | ;; HTML generation
15 | hiccup/hiccup {:mvn/version "2.0.0-RC3"}}
16 |
17 | :aliases
18 | {};; Aliases from practicalli/clojure-deps-edn included via .dir-locals
19 |
20 | #_()}
21 |
--------------------------------------------------------------------------------
/ideas.md:
--------------------------------------------------------------------------------
1 | Concepts / Language Features
2 | =====
3 | new record syntax
4 | Agents
5 | Vars
6 | state identity lifetime
7 | Metadata
8 | Tuples - syntax
9 | immutability / side effects
10 | type hints
11 | Pre and Post conditions of functions
12 |
13 | Particular Functions
14 | =====
15 | fnil - creating_a_function
16 | juxt - creating_a_function
17 | constantly - creating_a_function
18 | partition
19 | flatten
20 |
21 | frequencies
22 | reductions
23 | group-by
24 | keep
25 | keep-indexed
26 | map-indexed
27 | partition-all
28 | partition-by
29 | repeatedly
30 |
31 |
--------------------------------------------------------------------------------
/resources/common-english-words.csv:
--------------------------------------------------------------------------------
1 | a,able,about,across,after,all,almost,also,am,among,an,and,any,are,as,at,be,because,been,but,by,can,cannot,could,dear,did,do,does,either,else,ever,every,for,from,get,got,had,has,have,he,her,hers,him,his,how,however,i,if,in,into,is,it,its,just,least,let,like,likely,may,me,might,most,must,my,neither,no,nor,not,of,off,often,on,only,or,other,our,own,rather,said,say,says,she,should,since,so,some,than,that,the,their,them,then,there,these,they,this,tis,to,too,twas,us,wants,was,we,were,what,when,where,which,while,who,whom,why,will,with,would,yet,you,your
--------------------------------------------------------------------------------
/src/clojure_through_code/spec_definitions.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.spec-definitions)
2 |
3 |
4 | #_(defspec score-cannot-decrease 100
5 | (prop/for-all
6 | [ops gen-score-ops]
7 | (let [counter (sut/new-counter)]
8 | (loop [ops ops]
9 | (if (empty? ops)
10 | true
11 | (let [prev-val (sut/value counter)]
12 | (try (apply-score-op counter (first ops))
13 | (catch Throwable _))
14 | (if (< (sut/value counter) prev-val)
15 | false
16 | (recur (rest ops)))))))))
17 |
--------------------------------------------------------------------------------
/test/clojure_through_code/core_test.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.core-test
2 | (:require
3 | [clojure-through-code.core :refer :all]
4 | [clojure.test :refer :all]))
5 |
6 |
7 | (deftest adder-test
8 | (testing "Using a range of numbers to test the adder"
9 | #_(is (= 0 1))
10 | (is (= (+ 1 2) (adder 1 2)) "Adding 1 and 2")
11 | (is (= (+ 1 -2) (adder 1 -2)) "Adding 1 and -2")
12 | #_(is (not (= (+ 1 2)) (adder "a" "b")) "Adding strings as negative test")
13 | (is (false? (= 0 1)) "A simple failing test")
14 | (is (false? (= 0 (adder 3 4))) "Purposefully using failing data")))
15 |
--------------------------------------------------------------------------------
/src/clojure_through_code/parse_cli_options.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.parse-cli-options)
2 |
3 |
4 | ;; TODO: review the common approach to parsing Clojure CLI command line options
5 |
6 | (defn parse-cli-tool-options
7 | "Return the merged default options with any provided as a hash-map argument"
8 | [arguments]
9 | (merge {:replace false :report true :paths ["."]}
10 | arguments))
11 |
12 |
13 | (parse-cli-tool-options {:paths ["src" "test"] :report false})
14 |
15 |
16 | ;; => {:replace false, :report false, :paths ["src" "test"]}
17 |
18 |
19 | (parse-cli-tool-options {})
20 |
21 |
22 | ;; => {:replace false, :report true, :paths ["."]}
23 |
--------------------------------------------------------------------------------
/src/clojure_through_code/xx_swing_example.clj:
--------------------------------------------------------------------------------
1 | (ns xx-swing-example
2 | (:import
3 | (java.awt.event
4 | WindowListener)
5 | (javax.swing
6 | JButton
7 | JFrame
8 | JLabel)))
9 |
10 |
11 | (defn swing
12 | []
13 | (let [frame (JFrame. "Fund manager")
14 | label (JLabel. "Exit on close")]
15 | (doto frame
16 | (.add label)
17 | (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
18 | (.addWindowListener
19 | (proxy [WindowListener] []
20 | (windowClosing
21 | [evt]
22 | (println "Whoop"))))
23 | (.setVisible true))))
24 |
25 |
26 | (defn -main
27 | [& args]
28 | (swing))
29 |
--------------------------------------------------------------------------------
/src/clojure_through_code/xx_weird.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.xx-weird)
2 |
3 |
4 | ;;
5 | ;; Constantly
6 | ;; Returns a function that takes any number of arguments and returns the value originally passed to the constantly function.
7 | ;; https://clojuredocs.org/clojure.core/constantly
8 |
9 | ;; Call constantly function with the value of 42, wrapping the result of this call in () makes it call the returning function with [1 2 3 4 5] as an argument
10 | ((constantly 42) [1 2 3 4 5])
11 |
12 |
13 | ;; A cleaner example is to bind a name to the function that constantly returns. Then use that name to call the function with any argument. The original value of 42 is returned.
14 | (def the-answer (constantly 42))
15 |
16 | (the-answer "What is 6 times 9")
17 |
--------------------------------------------------------------------------------
/src/clojure_through_code/terminology.md:
--------------------------------------------------------------------------------
1 | # Clojure terminology explained
2 |
3 |
4 | # Symbol
5 |
6 | What do you think of when we say the word symbol?
7 | - an egyption hyrogliph
8 | - a road sign telling your that children are crossing
9 | - a complany logo
10 |
11 | All these are actually symbols. The have more meaning than just themselves. They are a way of simply expressing a value, thought or idea.
12 |
13 | In clojure a symbol is a way of refering to something else. We often use a symbol when we want to refer to something in more than one place.
14 |
15 | For example, if we have a value, we can use a symbol to essentally give that value a name.
16 |
17 | In a simple example its
18 | (def miles-walked-today 12)
19 |
20 | (def my-birthday "1st January 1990")
21 |
--------------------------------------------------------------------------------
/src/clojure_through_code/xx-drawing-demo.clj:
--------------------------------------------------------------------------------
1 | ;; A very simple example of using Java Swing library.
2 | ;; Convert to a LightTable instarepl to see the graphics change as you change the code
3 | ;; Use the "live" toggle button if you want to make a change without generating graphics
4 |
5 | (ns xx-drawing-demo
6 | (:import
7 | (java.awt
8 | Dimension)
9 | (javax.swing
10 | JFrame
11 | JPanel)))
12 |
13 |
14 | (defn make-panel
15 | []
16 | (let [panel (proxy [JPanel] []
17 | (paintComponent
18 | [g]
19 | (.drawLine g 50 50 100 200)))]
20 | (doto panel
21 | (.setPreferredSize (Dimension. 300 400)))))
22 |
23 |
24 | (defn make-frame
25 | [panel]
26 | (doto (new JFrame)
27 | (.add panel)
28 | .pack
29 | .show))
30 |
31 |
32 | (make-frame (make-panel))
33 |
--------------------------------------------------------------------------------
/src/clojure_through_code/destructuring.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.destructuring)
2 |
3 |
4 | ;; A simple example of destructuring is assigning the values of a collection, in this case a vector.
5 |
6 | (def co-ordinates [3 8])
7 |
8 |
9 | (let [[x y] co-ordinates]
10 | (str "x: " x "y: " y))
11 |
12 |
13 | ;; => "x: 3y: 8"
14 |
15 |
16 | ;; Sometimes we do not need all the information, so we can just use the elements we need.
17 | (def three-dee-co-ordinates [2 7 4])
18 |
19 |
20 | (let [[x y] three-dee-co-ordinates]
21 | (str "I only need the 2D co-ordinates, X: " x " and Y: " y))
22 |
23 |
24 | ;; => "I only need the 2D co-ordinates, X: 2 and Y: 7"
25 |
26 |
27 | ;; Its quite common to take the first element as a specific name and use another name for the rest of the elements
28 |
29 | (def shopping-list ["oranges" "apples" "spinach" "carrots" "potatoes" "beetroot"])
30 |
31 |
32 | (defn get-item
33 | [items]
34 | (let [[next-item & other-items] items]
35 | (str "The next item to get is: " next-item)))
36 |
37 |
38 | (get-item shopping-list)
39 |
40 |
41 | ;; => "The next item to get is: oranges"
42 |
--------------------------------------------------------------------------------
/src/clojure_through_code/spec.clj:
--------------------------------------------------------------------------------
1 | ;; ---------------------------------------------------------
2 | ;; Common specifications across the project
3 | ;;
4 | ;; Brief description
5 | ;; ---------------------------------------------------------
6 |
7 | ;; ---------------------------------------
8 | ;; Namespace definition and requires
9 |
10 | (ns clojure-through-code.spec
11 | "Specifications for data models and functions.
12 | Specs are be used to generate random data for unit tests"
13 | (:require
14 | [clojure.spec.alpha :as spec]))
15 |
16 |
17 | ;; ---------------------------------------
18 |
19 | ;; ---------------------------------------
20 | ;; Organisation specifications
21 |
22 | (spec/def :org/legal-name string?)
23 | (spec/def :org/primary-contact string?)
24 | (spec/def :org/joining-date inst?)
25 |
26 |
27 | ;; ---------------------------------------
28 |
29 | ;; ---------------------------------------
30 | ;; Validate specifications
31 |
32 | (comment
33 | (spec/valid? :org/legal-name "Johnny Practicalli")
34 | (spec/valid? :org/primary-contact "Johnny Practicalli")
35 | (spec/valid? :org/joining-date (java.util.Date.)))
36 |
37 |
38 | ;; ---------------------------------------
39 |
--------------------------------------------------------------------------------
/src/clojure_through_code/xx_emacs_fun.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.xx-emacs-fun
2 | (:require
3 | [clojure.string :as clj-string]))
4 |
5 |
6 | ;; yasnippets
7 |
8 | (defn expanded-snippet
9 | "This function was created by typing defn and using the binding M-x yas-expand.
10 | Adding snippets to the autocomplete configuration removes the need to call yas-expand"
11 | [tab-through-sections]
12 | (str "I can tab through each of the sections of the snippet to complete it, yay!"))
13 |
14 |
15 | #_(defn meta-forward-slash
16 | "Code faster with yasnippets, pressing meta forward-slash to expand the snippet"
17 | [tab-through-sections for-fun-and-profit]
18 | )
19 |
20 |
21 | ;; Snippets for clojure-mode
22 | ;; bench defm deft for import map ns print test when
23 | ;; bp defn doseq if is map.lambda opts reduce try whenl
24 | ;; def defr fn ifl let mdoc pr require use
25 |
26 |
27 |
28 | ;; FIXME: surprisingly some of my code needs fixing
29 |
30 | (comment
31 | ;; Reload the current namespace and print loaded namespaces in REPL buffer
32 | (require '[clojure-through-code.xx-emacs-fun] :reload :verbose)
33 |
34 | #_()) ; End of rich comment
35 |
--------------------------------------------------------------------------------
/src/clojure_through_code/error_handling.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.error-handling
2 | (:require
3 | [clojure.string :as string]))
4 |
5 |
6 | ;; Error handling when using filter?
7 | ;; e.g 2 vectors and filter one based on elements of the other
8 |
9 | (def tmsall
10 | [{:givenName nil :surname nil :email "admin@system.com"}
11 | {:givenName "Alice" :surname "Spring" :email "alice@al.al"}
12 | {:givenName "Bob" :surname "Summer" :email "bob@b.b"}
13 | {:givenName "Carol" :surname "Autumn" :email "carol@c.c"}
14 | {:givenName nil :surname nil :email "cs@cs.cs"}
15 | {:givenName "Danny" :surname "Winter" :email "danny@d.d"}])
16 |
17 |
18 | (def slack ["Bob Best Summer" "Alice Spring"])
19 |
20 |
21 | (try
22 | (filter
23 | (fn [m]
24 | (some
25 | (fn [s]
26 | (and (string/includes? s (:givenName m))
27 | (string/includes? s (:surname m)))) slack))
28 | tmsall)
29 | (catch Exception e
30 | (str "exception: " (.getMessage e))))
31 |
32 |
33 | ;; clojure.string/include? throws java.lang.NullPointerException
34 | ;; filter returns a lazy seq so it won't throw the exception until that is later realized
35 | ;; move the try/catch into the fn (or better, test for nil)
36 |
37 | ;; Or add another filter to just one with both a :givenName and a :surname
38 |
--------------------------------------------------------------------------------
/src/clojure_through_code/debugging_with_cider.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.debugging-with-cider)
2 |
3 |
4 | (defn bar
5 | [x]
6 | (inc x))
7 |
8 |
9 | (defn foo
10 | [x]
11 | (bar x))
12 |
13 |
14 | (foo 9)
15 |
16 |
17 | ;; Debugging workflow - work-around for the lack of support for Evil mode in cider debugging
18 |
19 | ;; Evaluate the two function, foo and bar - `, e f` in Evil normal state
20 | ;; `, d b` with the cursor on `(def foo ,,,)` to automatically add breakpoints to the foo function definition.
21 |
22 | ;; `, e f` with the cursor on (foo 9) to call the function and start the cider debugger
23 |
24 | ;; `C-z` to switch to Emacs editing state
25 |
26 | ;; `i` with the cursor still on x to jump into the bar function, the cursor jumps to the x in the bar definition
27 | ;; (its too late to step into the bar function when the cursor is on bar, as its already evaluated bar and showing the result)
28 |
29 | ;; `n` to show the result of inc x
30 | ;; `n` to show the result of bar x
31 | ;; `n` to finish with the result showing on (foo 9) function call...
32 |
33 | ;; You could instrument both functions, which is viable in small example, however, it would be impractical when there are many functions you want to step into that are called by a function, or when those function call other functions that you want to step into... etc.
34 |
--------------------------------------------------------------------------------
/src/clojure_through_code/scip.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.scip)
2 |
3 |
4 | ;; Pascals Triange
5 | ;;
6 |
7 | (defn next-row
8 | [last-row]
9 | (let [sum-neighbor (map-indexed (fn [idx x]
10 | (+ x (get last-row (+ 1 idx) 0)))
11 | last-row)]
12 | (into [1] sum-neighbor)))
13 |
14 |
15 | (defn pascal
16 | [n]
17 | (condp = n
18 | 0 []
19 | 1 [[1]]
20 | (let [prev-p (pascal (- n 1))]
21 | (conj prev-p (next-row (last prev-p))))))
22 |
23 |
24 | ;; Would be curious to hear if you think there's a more idiomatic clojure way to do this : }
25 |
26 | ;; seancorfield 01:08
27 |
28 | (defn pascal
29 | [n]
30 | (take n (iterate #(mapv (fn [[x y]] (+ x y))
31 | (partition 2 1 (cons 0 (into % [0])))) [1])))
32 |
33 |
34 | ;; For a given row, if you prepend and append 0 and then break into (overlapping) pairs, then add each pair, you get the next rows.
35 | ;; So you can have an infinite sequence of Pascal's triangle rows and you just take as many rows as you want.
36 |
37 | ;; You could safely use (concat [0] % [0]) instead of (cons 0 (into % [0])) if you find that clearer -- mapv is eager so you won't get a stack overflow from a lazy computation.
38 |
39 | ;; concat (is more forgiving about) doesn't have subtle bug possibilities via seq / vector conj location (edited)
40 |
--------------------------------------------------------------------------------
/src/clojure_through_code/xx-reactive-pattern-matching.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.xx-reactive-pattern-matching)
2 |
3 |
4 | (def data
5 | {:firstName "John",
6 | :lastName "Smith",
7 | :address {:streetAddress "21 2nd Stree",
8 | :state "NY",
9 | :postalCode 10021}
10 | :phoneNumbers [{:type "home" :number "212 555-1234"}
11 | {:type "fax" :number "646 555-4567"}]})
12 |
13 |
14 | ;; Utility Functions
15 | (defn pretty_str
16 | [s]
17 | (str "\"" s "\""))
18 |
19 |
20 | (defn comma_interposed_str
21 | [l]
22 | (apply str (interpose ", " l)))
23 |
24 |
25 | (defn folded_str
26 | [[s e] l]
27 | (str s (comma_interposed_str l) e))
28 |
29 |
30 | (def folded_list_str (partial folded_str "[]"))
31 |
32 | (def folded_map_str (partial folded_str "{}"))
33 |
34 |
35 | ;; Show implementation in clojure as multi methods
36 | (defmulti pretty_json type)
37 |
38 |
39 | (defmethod pretty_json clojure.lang.PersistentHashMap
40 | [m]
41 | (folded_map_str
42 | (map (fn [[k v]]
43 | (str (pretty_str (name k)) ":" (pretty_json v)))
44 | (seq m))))
45 |
46 |
47 | (defmethod pretty_json clojure.lang.PersistentVector
48 | [s]
49 | (folded_list_str (map pretty_json s)))
50 |
51 |
52 | (defmethod pretty_json String [e] (pretty_str e))
53 |
54 | (defmethod pretty_json nil [n] "null")
55 |
56 | (defmethod pretty_json :default [s] (str s))
57 |
--------------------------------------------------------------------------------
/src/clojure_through_code/keywords.clj:
--------------------------------------------------------------------------------
1 | ;; keywords
2 |
3 | (ns clojure-through-code.keywords)
4 |
5 |
6 | ;; keywords and strings
7 | ;;
8 |
9 | ;; `keyword` will convert a string to a keyword and add the colon at the start of the name
10 |
11 | ;; The keyword function can convert a string to a keyword
12 | ;; adding the : to the start of the string to make the keyword
13 |
14 | (keyword "keyword-ify-me")
15 |
16 |
17 | ;; => :keyword-ify-me
18 |
19 | ;; What if the string has spaces
20 |
21 | (keyword "i should be a single keyword")
22 |
23 |
24 | ;; => :i should be a single keyword
25 |
26 | ;; first replace the spaces with a dash and then convert
27 |
28 | (clojure.string/replace "i should be a single keyword" " " "-")
29 |
30 |
31 | ;; => "i-should-be-a-single-keyword"
32 |
33 | ;; and then convet to a keyword
34 |
35 | (keyword
36 | (clojure.string/replace "i should be a single keyword" " " "-"))
37 |
38 |
39 | ;; => :i-should-be-a-single-keyword
40 |
41 |
42 | ;; What if keyword is inside a string
43 |
44 | (keyword ":trapped-in-a-string")
45 |
46 |
47 | ;; => ::trapped-in-a-string
48 |
49 | ;; This could be okay if an auto-resolved keyword were suitable,
50 | ;; the resulting keyword would be qualified by the name of the current namespace
51 |
52 | ;; Use `clojure.edn/read-string` to extract a keyword that is wrapped in a string
53 |
54 | (require '[clojure.edn])
55 |
56 | (clojure.edn/read-string ":get-me-out-of-this-string")
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ```
2 |
3 | ██████╗██╗ ██████╗ ██╗██╗ ██╗██████╗ ███████╗
4 | ██╔════╝██║ ██╔═══██╗ ██║██║ ██║██╔══██╗██╔════
5 | ██║ ██║ ██║ ██║ ██║██║ ██║██████╔╝█████╗
6 | ██║ ██║ ██║ ██║██ ██║██║ ██║██╔══██╗██╔══╝
7 | ╚██████╗███████╗╚██████╔╝╚█████╔╝╚██████╔╝██║ ██║███████╗
8 | ╚═════╝╚══════╝ ╚═════╝ ╚════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
9 | ████████╗██╗ ██╗██████╗ ██████╗ ██╗ ██╗ ██████╗ ██╗ ██╗
10 | ╚══██╔══╝██║ ██║██╔══██╗██╔═══██╗██║ ██║██╔════╝ ██║ ██║
11 | ██║ ███████║██████╔╝██║ ██║██║ ██║██║ ███╗███████║
12 | ██║ ██╔══██║██╔══██╗██║ ██║██║ ██║██║ ██║██╔══██║
13 | ██║ ██║ ██║██║ ██║╚██████╔╝╚██████╔╝╚██████╔╝██║ ██║
14 | ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
15 | ██████╗ ██████╗ ██████╗ ███████╗
16 | ██╔════╝██╔═══██╗██╔══██╗██╔════╝
17 | ██║ ██║ ██║██║ ██║█████╗
18 | ██║ ██║ ██║██║ ██║██╔══╝
19 | ╚██████╗╚██████╔╝██████╔╝███████╗
20 | ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
21 | ```
22 |
23 |
24 | A collection of Clojure code examples to help demonstrate how the language works and encourage further discovery and experimentation with the language
25 |
26 | [](http://github.com/syl20bnr/spacemacs)
27 |
28 | ## Usage
29 |
30 | Open the project in your favourite [Clojure aware editor](https://practical.li/clojure/clojure-editors/) and start a REPL. Evaluate the code expression by expression to see what value is returned each time.
31 |
32 | Feel free to experiement with the code and submit pull requests with additional examples and any corrections.
33 |
34 | ## License
35 |
36 | Copyright © 2022 Practicalli
37 |
38 | Distributed under the Creative Commons Attribution Share-Alike 4.0 International.
39 |
--------------------------------------------------------------------------------
/src/clojure_through_code/benchmark.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.benchmark
2 | (:require
3 | [criterium.core :refer [quick-bench]]))
4 |
5 |
6 | ;; cond
7 | (let [number 5]
8 | (quick-bench (cond = 5 1 1 2 2 3 3 4 4 5 5)))
9 |
10 |
11 | ;; Evaluation count : 50788488 in 6 samples of 8464748 calls.
12 | ;; Execution time mean : 2.535916 ns
13 | ;; Execution time std-deviation : 0.096838 ns
14 | ;; Execution time lower quantile : 2.435814 ns ( 2.5%)
15 | ;; Execution time upper quantile : 2.686146 ns (97.5%)
16 | ;; Overhead used : 9.431514 ns
17 |
18 | ;; Found 1 outliers in 6 samples (16.6667 %)
19 | ;; low-severe 1 (16.6667 %)
20 | ;; Variance from outliers : 13.8889 % Variance is moderately inflated by outliers
21 |
22 |
23 | (let [number 5]
24 | (quick-bench (condp = 5 1 1 2 2 3 3 4 4 5 5)))
25 |
26 |
27 | ;; Evaluation count : 3625284 in 6 samples of 604214 calls.
28 | ;; Execution time mean : 156.813816 ns
29 | ;; Execution time std-deviation : 2.560629 ns
30 | ;; Execution time lower quantile : 154.222522 ns ( 2.5%)
31 | ;; Execution time upper quantile : 160.425030 ns (97.5%)
32 | ;; Overhead used : 9.431514 ns
33 |
34 | ;; Found 1 outliers in 6 samples (16.6667 %)
35 | ;; low-severe 1 (16.6667 %)
36 | ;; Variance from outliers : 13.8889 % Variance is moderately inflated by outliers
37 |
38 |
39 |
40 | (let [number 5]
41 | (quick-bench (case 5 1 1 2 2 3 3 4 4 5 5)))
42 |
43 |
44 | ;; Evaluation count : 56533626 in 6 samples of 9422271 calls.
45 | ;; Execution time mean : 1.158650 ns
46 | ;; Execution time std-deviation : 0.187322 ns
47 | ;; Execution time lower quantile : 1.021431 ns ( 2.5%)
48 | ;; Execution time upper quantile : 1.471115 ns (97.5%)
49 | ;; Overhead used : 9.431514 ns
50 |
51 | ;; Found 1 outliers in 6 samples (16.6667 %)
52 | ;; low-severe 1 (16.6667 %)
53 | ;; Variance from outliers : 47.5092 % Variance is moderately inflated by outliers
54 |
--------------------------------------------------------------------------------
/src/clojure_through_code/09_calling_java.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.09-calling-java
2 | (:import
3 | java.util.Date))
4 |
5 |
6 | ;;
7 | ;; From java.lang
8 |
9 | (.toUpperCase "fred")
10 |
11 | (.getName String)
12 |
13 |
14 | ;; (java.util.Date)
15 | (Date.)
16 |
17 |
18 | ;; From java.lang.System getProperty() as documented at:
19 | ;; http://docs.oracle.com/javase/8/docs/api/java/lang/System.html
20 | (System/getProperty "java.vm.version")
21 |
22 | Math/PI
23 |
24 | (def pi Math/PI)
25 |
26 |
27 | ;; Using java.util.Date import (usually include as part of the namespace definition)
28 |
29 | ;; (import java.util.Date)
30 |
31 | (Date.)
32 |
33 | (def *now* (Date.))
34 |
35 | (str *now*)
36 |
37 |
38 | ;; calling static methods in java
39 |
40 | (let [d (java.util.Date.)]
41 | (. d getTime))
42 |
43 |
44 | ;; or more commonly use object instance
45 |
46 | (let [d (java.util.Date.)]
47 | (.getTime d))
48 |
49 |
50 | ;; Mixing Clojure and Java together in code
51 |
52 | (map (memfn toUpperCase) ["a" "short" "message"])
53 |
54 |
55 | ;; The map function applies the function/method toUpperCase to each element in ["a" "short" "message"]
56 |
57 | ;; You can also use the bean function to wrap a Java bean in an immutable Clojure map.
58 |
59 | ;; (bean (new Person "Alexandre" "Martins"))
60 | ;; -> {:firstName "Alexandre", :lastName "Martins"}
61 |
62 | ;; Once converted, you can manipulate the new map using any of Clojure’s map functions, like:
63 |
64 | ;; (:firstName (bean (new Person "Alexandre" "Martins")))
65 | ;; -> Alexandre
66 |
67 |
68 | ;; Miscellaneous
69 |
70 | ;; Java calls are not first class functions, so you need to wrap them in a function (either named or anonymous) before you can use them with functions like map, apply etc
71 |
72 | ;; So in this example, the direct use of Math/sqrt will fail because its not a first class function.
73 |
74 | ;; (map Math/sqrt (range 1 10))
75 |
76 | ;; however if you wrap the call to Math/sqrt in a function, in this case an anonymous function, then it will become a first calss function that can be mapped over a collection.
77 |
78 | (map #(Math/sqrt %) (range 1 10))
79 |
--------------------------------------------------------------------------------
/src/clojure_through_code/05_core_functions.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.05-core-functions)
2 |
3 |
4 | ;;
5 | ;; Clojure Core
6 |
7 | ;; There is a core set of functions included by default in any Clojure program
8 |
9 | ;; https://clojure.github.io/clojure/clojure.core-api.html
10 |
11 | ;; these are the building blocks for your own code and other Clojure libraries
12 |
13 | ;; There are a great number of functions defined in Clojure core
14 | (count (ns-publics 'clojure.core))
15 |
16 | *clojure-version*
17 |
18 |
19 | ;; when I checked on 7th April 2015 there were 599 functions defined in Clojure Core for version 1.6. Checking on the 19th April 2016, there were 621 functions in Clojure Core version 1.8.
20 |
21 |
22 |
23 | ;;
24 | ;; Java
25 |
26 | ;; java.lang is included in the namespace by default
27 |
28 | ;; easy to import any other Java / JVM library (see calling java)
29 |
30 | ;; TODO java.lang commonly used functions
31 |
32 |
33 | ;;
34 | ;; Data structures behave a little like functions
35 |
36 | ;; we have already seen this
37 |
38 | ;; TODO examples of using data structures as functions
39 |
40 |
41 | ;;
42 | ;; Working with data structures
43 |
44 | ;; This section will become far too long, so need to refine the topic
45 |
46 | ;; Apply a function over the individual elements a data structure
47 |
48 |
49 |
50 | ;; Mapping a function over one or more collections, applying that function to the element of each collection
51 |
52 | ;; user> (doc map)
53 | ;; -------------------------
54 | ;; clojure.core/map
55 | ;; ([f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])
56 | ;; Returns a lazy sequence consisting of the result of applying f to the set of first items of each coll, followed by applying f to the set of second items in each coll, until any one of the colls is exhausted. Any remaining items in other colls are ignored. Function f should accept number-of-colls arguments.
57 | ;; nil
58 |
59 |
60 | (map + [1 2 3] [1 2 3])
61 |
62 | (map + [1 2 3] [1 2])
63 |
64 | (map + [1 2 3] [1])
65 |
66 | (map + [1 2 3] [])
67 |
68 | (map + [1 2 3])
69 |
70 |
71 | (take 8 (cycle ["foo" "bar"]))
72 |
73 |
74 | ;; => ("foo" "bar" "foo" "bar" "foo" "bar" "foo" "bar")
75 |
76 |
77 |
78 | (def fibonacci-sequence [1 2 3 5 8 13 21 34 55 89 144 278])
79 |
80 | (take 20 fibonacci-sequence)
81 |
82 | (take 10 [])
83 |
84 | (first [1 2 3])
85 | (first '())
86 | (first (list))
87 |
88 | (vector (map * (range 10) fibonacci-sequence))
89 |
90 |
91 | ;; return a vector for this
92 |
93 | (lazy-seq)
94 |
95 | (rand-int 10)
96 |
--------------------------------------------------------------------------------
/src/clojure_through_code/hhgttg-book-common-words.clj:
--------------------------------------------------------------------------------
1 | ;; Example of threading macros and the use of a connected REPL to give fast feedback
2 | ;; as you are developing code.
3 | ;; Original concept from Misophistful: Understanding thread macros in clojure
4 | ;; https://www.youtube.com/watch?v=qxE5wDbt964
5 |
6 | ;; (str "this is live evaluation via a connected repl")
7 |
8 | ;; Lets get the text of a book from a website using the slurp function
9 |
10 | (ns clojure-through-code.hhgttg-book-common-words)
11 |
12 | (def book (slurp "./hhgttg.txt"))
13 |
14 |
15 | (def common-english-words
16 | (-> (slurp "common-english-words.txt")
17 | (clojure.string/split #",")
18 | set))
19 |
20 |
21 | ;; The same as above but in normal lisp style
22 | ;; (set
23 | ;; (clojure.string/split
24 | ;; (slurp "common-english-words.txt")
25 | ;; #"," ))
26 |
27 |
28 |
29 | (->> book
30 | (re-seq #"[a-zA-Z0-9|']+" ,,,)
31 | (map #(clojure.string/lower-case %))
32 | (remove common-english-words)
33 | frequencies
34 | (sort-by val)
35 | reverse)
36 |
37 |
38 | (def popularity
39 | (->> book
40 | (re-seq #"[\w |'-]+" ,,,)
41 | (map #(clojure.string/lower-case %))
42 | (remove common-english-words)
43 | frequencies
44 | (sort-by val)
45 | reverse))
46 |
47 |
48 | ;; The same as above but in normal lisp style
49 | ;; (reverse
50 | ;; (sort-by
51 | ;; val
52 | ;; (frequencies
53 | ;; (remove common-english-words
54 | ;; (map #(clojure.string/lower-case %)
55 | ;; (re-seq #"[a-zA-Z0-9|']+" book))))))
56 |
57 |
58 | ;; cider-eval-pprint-last-sexp will show output in a separate buffer
59 | ;; , e P in Spacemacs
60 | ;; C-c C-p in Emacs
61 |
62 |
63 | ;; Algorithm
64 | ;; * Use regular expression to create a collection of individual words
65 | ;; * Convert all the words to lower case (so they match with common words source)
66 | ;; * Remove the common English words used in the book, leaving more context specific words
67 | ;; * Calculate the requencies of the remaining words, returning a map of word & word count pairs
68 | ;; * Sort the map by word count values
69 | ;; * Reverse the collection so the most commonly used word is the first element in the map
70 |
71 |
72 | ;; TODO: Create a transducers version of the above code
73 |
74 |
75 | ;;
76 | ;; On-line sources of book and common English words
77 |
78 | ;; (def book (slurp "http://clearwhitelight.org/hitch/hhgttg.txt"))
79 |
80 | ;; (def common-english-words
81 | ;; (-> (slurp "http://www.textfixer.com/resources/common-english-words.txt")
82 | ;; (clojure.string/split #",")
83 | ;; set))
84 |
85 |
86 | (re-seq #"\w+" "Morning-room in Algernon's flat in Half-Moon Street.")
87 |
88 |
89 | ;; => ("Morning" "room" "in" "Algernon" "s" "flat" "in" "Half" "Moon" "Street")
90 |
91 |
92 | (re-seq #"[a-zA-Z0-9]+" "Morning-room in Algernon's flat in Half-Moon Street.")
93 |
94 |
95 | ;; => ("Morning" "room" "in" "Algernon" "s" "flat" "in" "Half" "Moon" "Street")
96 |
97 | (re-seq #"[a-zA-Z0-9|'-]+" "Morning-room in Algernon's flat in Half-Moon Street."); => ("Morning-room" "in" "Algernon's" "flat" "in" "Half-Moon" "Street")
98 |
99 |
100 | (re-seq #"[\w |'-]+" "Morning-room in Algernon's flat in Half-Moon Street.")
101 |
102 |
103 | ;; => ("Morning-room in Algernon's flat in Half-Moon Street")
104 |
--------------------------------------------------------------------------------
/src/clojure_through_code/07_list_comprehension.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.07-list-comprehension)
2 |
3 |
4 | ;; Lots of powerful functions in Clojure for working with a list data structure.
5 | ;; List comprehension lets you work with multiple list.
6 |
7 |
8 |
9 | ;;
10 |
11 | ;; Some functions can return a very large collection, if you dont use lazy evaluation
12 | ;; around this kind of function you can blow up your repl, or have to break out of your repl.
13 | ;; For example if you run the following
14 |
15 | ;; (iterate inc 0)
16 |
17 | ;; (set! *print-length* 10)
18 | ;; 10
19 |
20 | ;; Now it's perfectly fine. Yay!
21 | ;; (iterate inc 0)
22 | ;; (0 1 2 3 4 5 6 7 8 9 ...)
23 |
24 |
25 |
26 | ;; Sequence / list comprehension
27 |
28 | (for [n (range 10)]
29 | (inc n))
30 |
31 |
32 | ;; loop through the first 10 numbers, assigning an x & y values in an array to form a list result
33 | (for [x (range 10) y (range 10)]
34 | [x y])
35 |
36 |
37 | ;; create a list for x and y of the first 10 numbers as an array.
38 | (let [x (range 10)
39 | y (range 10)]
40 | [x y])
41 |
42 |
43 | ;; add conditions using :when and :while
44 | (for [x (range 10) y (range 10)
45 | :when (even?
46 | (+ x y))]
47 | [x y])
48 |
49 |
50 | ;; From http://integrallis.com/2010/12/on-mini-languages-and-clojure#.VorOFJfFs3y
51 |
52 | (for [x (range 10)] x)
53 |
54 |
55 | ;; > (0 1 2 3 4 5 6 7 8 9)
56 |
57 | ;; filter out values :when they do not meet the condition
58 | (for [x (range 10) :when (even? x)] x)
59 |
60 |
61 | ;; > (0 2 4 6 8)
62 |
63 | ;; iterate :while condition is met
64 | (for [x (range 10) :while (even? x)] x)
65 |
66 |
67 | ;; > (0)
68 |
69 |
70 |
71 | ;;
72 | ;; Merge elements of collections to form game board grids
73 | ;; - using list comprehension
74 |
75 | (def rows "ABCDEFGHI")
76 | (def columns "123456789")
77 |
78 |
79 | (defn grid-reference
80 | "Generate all the combinations for a given collection of row and column names.
81 |
82 | The collection of names can be in a string, set, list or vector.
83 |
84 | The function will return all combinations up to the length of the collection with the fewest elements."
85 |
86 | [row-names column-names]
87 |
88 | (for [row-element row-names
89 | column-element column-names]
90 | (str row-element column-element)))
91 |
92 |
93 | (def board (grid-reference rows columns))
94 |
95 | board
96 |
97 |
98 | (defn grid-reference-as-keys
99 | [row-names column-names]
100 | (for [row-element row-names
101 | column-element column-names]
102 | (keyword (str row-element column-element))))
103 |
104 |
105 | (def board-with-keywords (grid-reference-as-keys rows columns))
106 |
107 | board-with-keywords
108 |
109 |
110 | (def alphabet "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
111 |
112 |
113 | (defn tictactoe-board-grid
114 | "Create a TicTacToe board grid so each cell in the board has a unique reference.
115 | Boards have the same number of rows and columns, so a typical 3x3 grid would have a row-column-size of 3"
116 | [row-column-size]
117 | (let [row-names (take row-column-size alphabet)
118 | column-names (range 1 (+ row-column-size 1))]
119 | (grid-reference-as-keys row-names column-names)))
120 |
121 |
122 | (tictactoe-board-grid 3)
123 |
124 |
125 | ;; Simple sequence generator
126 | (comment
127 | (for [x (range 10)]
128 | [x x x])
129 |
130 | ;; combination
131 | (let [dimension 3]
132 | (for [x (range dimension)
133 | y (range dimension)
134 | z (range dimension)]
135 | [x y z]))
136 |
137 | #_())
138 |
--------------------------------------------------------------------------------
/src/clojure_through_code/11_macros.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.11-macros)
2 |
3 |
4 | ;; The language of Clojure is built on a small number special forms
5 |
6 | ;; if let loop recur do set! quote var
7 |
8 | ;; and to make Java/JVM interop work really nicely
9 | ;; new . throw try
10 |
11 | ;; Everything else can be built with macros, data structures & functions
12 |
13 | ;;
14 | ;; Example: defn
15 | ;; defn is a macro to make it easier to create functions as well as keeping
16 | ;; the clojure code nice and clean
17 |
18 | ;; So lets define a function taking one argumentand a simple body:
19 |
20 | (defn my-function
21 | [args]
22 | (str args " " "is a macro"))
23 |
24 |
25 | ;; To write this out without defn, we would use def function
26 | (def my-function (fn [args] (str args " " "is a macro")))
27 |
28 | (my-function "defn")
29 |
30 |
31 | ;; We can check that we understand what the macro expands to
32 | ;; by using the function macroexpand.
33 | ;; The expression we want to expand needs to be quoted so its not evaluated
34 |
35 | (macroexpand
36 | '(defn my-function
37 | [args]
38 | (str args " " "is a macro")))
39 |
40 |
41 | ;; Is def a macro ?
42 |
43 | (macroexpand '(def my-string "Is def a macro"))
44 |
45 |
46 | ;; Leiningen also uses a macro for the project configuration
47 |
48 | ;; [TODO: it would seem that defproject is not a macro, doh!].
49 |
50 | ;; you may have noticed that the project.clj file is written using clojure
51 | ;; The defproject expression looks like a map, as it uses key value pairs
52 | ;; to define most of the project. However, defproject is called as a function.
53 |
54 | ;; Lets take a look at what happens when defproject is called
55 |
56 | ;; (macroexpand-1
57 | ;; '(defproject clojure-through-code "20.1.5-SNAPSHOT"
58 | ;; :description "Learning Clojure by evaluating code on the fly"
59 | ;; :url "http://jr0cket.co.uk"
60 | ;; :license {:name "Eclipse Public License"
61 | ;; :url "http://www.eclipse.org/legal/epl-v10.html"}
62 | ;; :dependencies [[org.clojure/clojure "1.7.0"]]))
63 |
64 |
65 | ;;
66 | ;; Example: or
67 | ;; Lets see how or is actually created
68 |
69 | ;; or will evaluate the things passed to it one by one,
70 | ;; returning the first that is true or the last.
71 | (or 1 2)
72 |
73 |
74 | ;; how is this basically implemented as a macro false?
75 |
76 | ;; (let [local-param x]
77 | ;; (if local-param local-param y))
78 |
79 | ;; set local-param to equal x
80 | ;; if x is true then return x, otherwise return y
81 |
82 | ;; if has to be a special op because only one
83 |
84 |
85 | ;; You can also see what this function looks like under the covers
86 |
87 | (macroexpand '(or x y))
88 |
89 |
90 | ;; You can make the output formatted, however using pprint will send the output
91 | ;; to the console if you are in LightTable or another IDE.
92 | (clojure.pprint/pprint (macroexpand '(or x y)))
93 |
94 |
95 | (macroexpand '(let [local-param x]
96 | (if local-param local-param y)))
97 |
98 |
99 | ;; Many common functions are actually macros, built using the core primatives
100 | ;; of clojure (if def let ...)
101 |
102 | ;; Take a look at the cond function, its behaviour can be created using one of
103 | ;; these core primatives of Clojure. Can you work out which one ?
104 |
105 | (macroexpand
106 | '(cond
107 | (< n 0) "negative"
108 | (> n 0) "positive"
109 | :else "zero"))
110 |
111 |
112 | ;; Time is an example of a more complex macro
113 | (macroexpand '(time (print "timing")))
114 |
115 |
116 | ;; [TODO] not sure what this expression does
117 | (time (print "timing"))
118 |
--------------------------------------------------------------------------------
/src/clojure_through_code/regular_expressions.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.regular-expressions)
2 |
3 |
4 | ;; Reference: https://gist.github.com/clojens/7932406
5 |
6 | (def ptrn
7 | {:a {:pattern #"a(?!b)"
8 | :purpose "Only allow a if it is not preceded by a 'b' (negative lookahead)"
9 | :samples ["acdefg" ; ok
10 | "abcdef"]} ; nil
11 |
12 | :b {:pattern #"(?i)(
)(.+?)()"
13 | :purpose "Grouping and backreference to get the text from a HTML tag."
14 | :samples ["titles are just dumb" ; nil
15 | "1337"]} ; ok
16 |
17 | :c {:pattern #"(\w)(\s+)([\.,])"
18 | :purpose "Remove whitespace between word and . or ,"
19 | :samples ["word ." ; ok
20 | "to ," ; ok
21 | "test."]} ; nil
22 |
23 | :d {:pattern #"[a-zA-Z]{3}"
24 | :purpose "Get exactly 3 characters from A to Z in both upper- and lower-case. Not less."
25 | :samples ["FU" ; nil
26 | "John" ; Joh
27 | "Joe"]} ; Joe
28 |
29 | :e {:pattern #"[tT]rue|[yY]es"
30 | :purpose "returns true if the string matches exactly 'true' or 'True' or 'yes' or 'Yes"
31 | :samples ["" ; ?
32 | "" ; ?
33 | ""]} ; ?
34 |
35 | :f {:pattern #"^[^\d].*"
36 | :purpose "Returns true if the string does not have a number at the beginning"
37 | :samples ["1 beer please" ; nil
38 | "More beer for this 1!!" ; ok
39 | "One beer two beer"]} ; ok
40 |
41 | :g {:pattern #"[^0-9]*[12]?[0-9]{1,2}[^0-9]*"
42 | :purpose "Returns true if the string contains a number less then 300"
43 | :samples ["123456789101112134" ; ok
44 | "Someone rented 300" ; ?
45 | "99 bottles of beer"]} ; ?
46 |
47 | :h {:pattern #"([\w&&[^b]])*"
48 | :purpose "Returns true if the string contains a arbitrary number of characters except b"
49 | :samples ["acdefghijklmnopqrstuvwxyz" ; ok
50 | "ABBA was a mid 70's band" ; nil
51 | "Berenbotje kon niet varen"]} ; nil
52 |
53 | ;;
54 |
55 | ;;
56 | :X {:pattern #""
57 | :purpose ""
58 | :samples ["" ; ?
59 | "" ; ?
60 | ""]}}) ; ?
61 |
62 | (defn step
63 | [k]
64 | (map #(re-matches (get-in ptrn [k :pattern]) %) (get-in ptrn [k :samples])))
65 |
66 |
67 | ;; Java built-in regex support in string objects
68 |
69 | ;; A little threading macro to show how string instances have native
70 | ;; methods to deal with regular expressions.
71 | (-> "foo bar" String. (.matches "fo.*"))
72 |
73 | (step :h)
74 |
75 |
76 | ;; Filtering strings
77 | ;;
78 |
79 | ;; Starting with the following
80 |
81 | (ns disemvowel-trolls)
82 |
83 | (def vowels #{\a \e \i \o \u \A \E \I \O \U})
84 |
85 |
86 | (defn disemvowel
87 | [string]
88 | (apply str (filter (complement vowels) (seq string))))
89 |
90 |
91 | ;; Refactor
92 |
93 | (def vowels
94 | (let [vowels "aeiou"]
95 | (into (set vowels) (clojure.string/upper-case vowels))))
96 |
97 |
98 | (defn disemvowel
99 | [string]
100 | (clojure.string/join (remove vowels string)))
101 |
102 |
103 | (disemvowel "Hello, World!")
104 | "Hll, Wrld!"
105 |
106 |
107 | ;; Regular expressions and case insensitivity
108 | ;; Use the regex case-insensitivity flag with clojure.string/replace function
109 | ;; Replace all characters in the pattern `[aeiou]` regardless of case `(?i)` with an empty strings
110 |
111 | (defn disemvowel-replace-case-insensitive
112 | [string]
113 | (clojure.string/replace string #"(?i)[aeiou]" ""))
114 |
--------------------------------------------------------------------------------
/src/clojure_through_code/06a_lazy_evaluation.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.06a-lazy-evaluation)
2 |
3 |
4 | ;;
5 | ;; Lazy evaluation
6 |
7 | ;; Building up behaviour by passng functions to functions
8 |
9 | ;; Get the first 3 elements of a vector (a vector can be thought of as an immutable array)
10 | (take 3 [1 2 3 4 5 6])
11 |
12 |
13 | ;; Instead of using a data structure, lets generate one on the fly
14 |
15 | (take 10 (iterate inc 0))
16 | (take 10 (iterate dec 0))
17 |
18 |
19 | ;; Seeing Lazy evaluation in action
20 |
21 | ;; By creating a lazy sequence
22 |
23 | (defn lazy-random-numbers
24 | "Lazy evaluation of a random number generator, generates only the numbers required at evaluation time. Once a number is generated though, it does not need to be generated again."
25 | [maximum-number]
26 | (lazy-seq
27 | (println "I just created a number in the lazy sequence")
28 | (cons (rand-int maximum-number)
29 | (lazy-random-numbers maximum-number))))
30 |
31 |
32 | (def random-numbers
33 | (take 10 (lazy-random-numbers 100)))
34 |
35 |
36 | ;; to see the laziness of the sequence (list of numbers), just take a few of them
37 |
38 | (first random-numbers)
39 |
40 |
41 | ;; sends 1 println to the REPl
42 | ;; => 36
43 |
44 | (nth random-numbers 3)
45 |
46 |
47 | ;; sends 3 println to the REPl
48 |
49 | (count random-numbers)
50 |
51 |
52 | ;; sends 6 println to the REPl first time this is called
53 | ;; sends 0 println to the REPl all consecutive times as all the numbers are now cached
54 |
55 | (defn accumulate
56 | [operation value]
57 | (loop [result []
58 | args value]
59 | (if (empty? args)
60 | result
61 | (recur (conj result (operation (first args))) (rest args)))))
62 |
63 |
64 | (accumulate #(* %) [1 2 3])
65 |
66 |
67 | (defn accumulate_v1
68 | [operation arguments]
69 | (loop [result [] args arguments]
70 | (if (empty? args)
71 | (reverse result)
72 | (recur (cons (operation (first args)) result) (rest args)))))
73 |
74 |
75 | (accumulate_v1 #(* %) [1 2 3])
76 |
77 |
78 | ;; Move to list section
79 |
80 | ;; add elements to a list using cons - I think of this as construct a list
81 | (cons :z [:a :b])
82 |
83 |
84 | ;; Treating data as data with quote '
85 |
86 | ;; Conj needs to have a collection as the first argument
87 | (conj (:a :b) :c) ; fail
88 |
89 | (conj '(:a :b) :c)
90 | (conj {:a "A" :c "C"} {:b "B"})
91 |
92 |
93 | ;; concat returns a list as it needs to create a new data structure
94 | ;; its more efficient to create new data structures as lists
95 | (concat [:a :b :c] [:d :e])
96 |
97 |
98 | ;; this returns a vector, althought there is a performance hit with this
99 | ;; In clojure you can decide for your self what sort of performance you want with your app
100 | (vec (concat [:a :b :c] [:d :e]))
101 |
102 |
103 | (count [1 2 3])
104 | (count #{42})
105 |
106 |
107 | ;; reduce takes a sequence and returns a scala
108 | (reduce + (map inc (range 10)))
109 |
110 |
111 | ;; filter in terms of reduce
112 | ;; otfrom tip: use acc and new for your parameters - accumulator and new value
113 | (filter even? (range 10))
114 |
115 |
116 | (reverse
117 | (reduce (fn [x y]
118 | (if (even? y) (conj x y) x))
119 | (list) (range 10)))
120 |
121 |
122 | (first [1 2 3])
123 | (next [1 2 3])
124 | (rest [1 2 3])
125 | (next [])
126 | (rest [])
127 | (last [1 2 3])
128 | (get [1 2 3 4] 2)
129 |
130 | (max 1 2 3)
131 |
132 | (apply min [1 2 3 4 8 0 -30])
133 |
134 |
135 | ;; apply uses ariatic arguments, reduce can be faster - otfrom
136 |
137 | (reduce min [1 2 3 4 8 0 -30])
138 |
139 | (reductions min [1 2 3 4 8 0 -30])
140 |
141 | (frequencies [1 2 3 2 4 4 5 1 1 2 4 6 5 4 4 2 3])
142 |
143 | (partition 4 (range 10))
144 |
145 |
146 | ;; be weary of loosing data that does not fit into the partition
147 | ;; use partition-all or use a pad
148 |
149 | (partition 4 3 (repeat :a) (range 10))
150 |
151 |
152 | (type {})
153 | (type (transient {}))
154 |
155 |
156 | ;; (doc split-at)
157 |
158 | ;; most seuences are evaluated in blocks of 32
159 |
160 |
161 | ;; (map inc (range))
162 |
163 | (take 10 (cycle [:hearts]))
164 |
165 |
166 | ;; find if something is in a sequence, not performant as their is no index
167 | (some #{:b} [:a :b :c])
168 | (filter #{:b} [:a :b :c])
169 |
170 |
171 | ;; (get-in [{:a "A" :b "B"} {0 :a}])
172 |
--------------------------------------------------------------------------------
/src/clojure_through_code/most_common_word.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.most-common-word
2 | (:require
3 | [clojure.string]))
4 |
5 |
6 | ;; Algorithm
7 | ;; * Use regular expression to create a collection of individual words
8 | ;; * Convert all the words to lower case (so they match with common words source)
9 | ;; * Remove the common English words used in the book, leaving more context specific words
10 | ;; * Calculate the requencies of the remaining words, returning a map of word & word count pairs
11 | ;; * Sort the map by word count values
12 | ;; * Reverse the collection so the most commonly used word is the first element in the map
13 |
14 | ;;
15 | ;; Most Common Words - Lisp Style
16 |
17 | ;; Book: The importance of being Earnest, Oscar Wilde
18 | ;; Source: Project Guttenburg (UTF-8 format)
19 | ;; Download from: http://www.gutenberg.org/cache/epub/844/pg844.txt
20 | ;; Local copy: resources/importance-of-being-earnest.txt
21 |
22 | ;; List of common works from the English language
23 | ;; Download http://www.textfixer.com/resources/common-english-words.txt
24 | ;; Local copy: resources/common-english-words.csv
25 |
26 | ;; Create an identity for the book
27 |
28 | (def importance-of-being-earnest
29 | (slurp "resources/importance-of-being-earnest.txt"))
30 |
31 |
32 | ;; Create an identity for the common words, transforming into a collection of strings
33 |
34 | (def common-english-words
35 | (set
36 | (clojure.string/split
37 | (slurp "resources/common-english-words.csv")
38 | #",")))
39 |
40 |
41 | ;; Process the book for the most common word
42 |
43 | (defn most-common-words
44 | [book]
45 | (reverse
46 | (sort-by val
47 | (frequencies
48 | (remove common-english-words
49 | (map
50 | #(clojure.string/lower-case %)
51 | (re-seq #"[a-zA-Z0-9|']+" book)))))))
52 |
53 |
54 | (most-common-words importance-of-being-earnest)
55 |
56 |
57 | (comment
58 | ;; Experimenting with regular expressions
59 |
60 | (re-seq #"[\w|'-]+" "Morning-room in Algernon's flat in Half-Moon Street.")
61 | (re-seq #"\w+" "Morning-room in Algernon's flat in Half-Moon Street.")
62 |
63 | )
64 |
65 |
66 | ;;
67 | ;; Most Common Words - Threading Macro Style
68 |
69 |
70 | (def common-english-words
71 | (-> (slurp "resources/common-english-words.csv")
72 | (clojure.string/split #",")
73 | set))
74 |
75 |
76 | (defn most-common-words
77 | [book]
78 | (->> book
79 | #_(re-seq #"[a-zA-Z0-9|']+")
80 | (re-seq #"[\w|'-]+")
81 | (map #(clojure.string/lower-case %))
82 | (remove common-english-words)
83 | frequencies
84 | (sort-by val)
85 | reverse))
86 |
87 |
88 | ;; Use importance-of-being-earnest book defined above
89 | (most-common-words importance-of-being-earnest)
90 |
91 |
92 | ;;
93 | ;; Deconstructing the code in the repl
94 |
95 | ;; To understand what each of the functions do in the most-common-words function you can comment out one or more expressions by placing the comment reader macro #_ in front of the expression
96 |
97 | (defn most-common-words
98 | [book]
99 | (->> book
100 | #_(re-seq #"[a-zA-Z0-9|']+")
101 | #_(map #(clojure.string/lower-case %))
102 | #_(remove common-english-words)
103 | #_frequencies
104 | #_(sort-by val)
105 | #_reverse))
106 |
107 |
108 | ;; Now the most-common-words function will only return the result of evaluating book (the full text of the book). To see what each of the other lines do, simply remove the #_ character from the front of an expression and re-evaluate the most-common-words function in the repl
109 |
110 |
111 |
112 | ;; Accessing the book directly at Project Gutenburg
113 | ;; using Java Interop to ensure a clean connection that closes when finished
114 |
115 | (with-open [in (java.util.zip.GZIPInputStream.
116 | (clojure.java.io/input-stream
117 | "https://www.gutenberg.org/cache/epub/844/pg844.txt"))]
118 | (slurp in))
119 |
120 |
121 | (defn decode
122 | [book-gzip]
123 | (with-open
124 | [uncompress-text (java.util.zip.GZIPInputStream.
125 | (clojure.java.io/input-stream book-gzip))]
126 | (slurp uncompress-text)))
127 |
128 |
129 | (defn common-words
130 | [csv]
131 | (-> (slurp csv)
132 | (clojure.string/split #",")
133 | set))
134 |
135 |
136 | (defn most-common-word
137 | [book-gzip common-words]
138 | (->> (decode book-gzip)
139 | (re-seq #"[\w|'-]+")
140 | (map #(clojure.string/lower-case %))
141 | (remove common-words)
142 | frequencies
143 | (sort-by val >)))
144 |
145 |
146 | (defn -main
147 | [book-gzip common-word-csv]
148 | (most-common-word book-gzip (common-words common-word-csv)))
149 |
150 |
151 | (comment
152 |
153 | (-main "https://www.gutenberg.org/cache/epub/844/pg844.txt" "common-english-words.txt")
154 |
155 | #_())
156 |
157 |
158 | (def fish)
159 |
160 |
161 | (defn blah
162 | (println "I am a bad function definition"))
163 |
--------------------------------------------------------------------------------
/src/clojure_through_code/xx-colonial-blotto.clj:
--------------------------------------------------------------------------------
1 | ;; Everyone's heard of the Prisoner's Dilemma, but I hadn't heard
2 | ;; until recently of Colonel Blotto, which was another foundation of
3 | ;; Game Theory.
4 |
5 | ;; Colonel Blotto and his opponent each have 100 troops, and between
6 | ;; them are 10 hills.
7 |
8 | ;; They must allocate troops to the hills, without knowing each
9 | ;; other's decisions.
10 |
11 | ;; On each hill, the larger number of troops wins, and the side which
12 | ;; wins the most battles wins overall.
13 |
14 | ;; Imagine that Blotto sends ten troops to each hill, but his cunning
15 | ;; enemy sends 11 troops to nine hills, and sends one poor soldier on
16 | ;; a suicide mission to the tenth.
17 |
18 | ;; Blotto loses 9-1, which is a pearl-handled revolver performance.
19 |
20 | ;; In the next battle, his successor cunningly sends 12 troops to the
21 | ;; first eight hills, four to hill nine, and none to hill 10.
22 |
23 | ;; The enemy, however, has anticipated this, and sent one man to claim
24 | ;; the undefended hill ten, five to the lightly defended hill nine,
25 | ;; and thirteen to most of the others. Another stunning victory.
26 |
27 | ;; Even though it was originally thought of as a simple war game,
28 | ;; Blotto is a better model for election campaigning!
29 |
30 | ;;
31 |
32 | ;; Suppose that a colonial empire (let us without loss of generality
33 | ;; call them "The British") is desperately trying to hang on to its
34 | ;; last foreign possession against the heroic guerilla tactics of a
35 | ;; bunch of terrorists (the "Rebels"). The terrorists are badly
36 | ;; outnumbered, but agile.
37 |
38 | ;; The British are a slow moving bunch, who will divide their large
39 | ;; force equally among the strategic targets that they need to
40 | ;; protect.
41 |
42 | ;; The Rebels have, as well as the second mover advantage of getting
43 | ;; to see their enemy's troop distribution, the choice of how many
44 | ;; fronts to fight on.
45 |
46 | ;; They win if they manage to win on a majority of fronts.
47 |
48 | ;; Suppose that the British have four thousand men
49 | (def colonial-troops 4000)
50 |
51 |
52 | ;; And the rebels have 1500
53 | (def rebel-troops 1500)
54 |
55 |
56 | ;; How many fronts should the rebels choose to fight on?
57 |
58 | ;; Suppose 7 is the rebel commander's lucky number
59 | (def fronts 7)
60 |
61 |
62 | ;; The British send 571 men to each front
63 | (quot colonial-troops fronts) ; -> 571
64 |
65 | ;; Which we might choose to represent as:
66 | (repeat fronts (quot colonial-troops fronts))
67 |
68 |
69 | ;; Which leaves them with a reserve of three
70 | (- colonial-troops (* fronts min-colonial)) ; -> 3
71 |
72 | ;; Which we might choose to represent as:
73 | (set! *print-length* 25)
74 | (concat (repeat 3 1) (repeat 0))
75 |
76 |
77 | ;; They distribute their reserve also:
78 | (def colonial-dist
79 | (map +
80 | (repeat fronts (quot colonial-troops fronts))
81 | (concat (repeat 3 1) (repeat 0))))
82 |
83 |
84 | ;; To leave a final distribution:
85 | colonial-dist ; -> (572 572 572 571 571 571 571)
86 |
87 | (assert (= (reduce + colonial-dist) colonial-troops) "grr")
88 |
89 |
90 | ;; We can summarize this in the following function:
91 | (defn colonial-troop-allocation [troops fronts]
92 | (let [min-colonial (quot troops fronts)
93 | excess (- troops (* min-colonial fronts))
94 | excess-dist (concat (repeat excess 1) (repeat 0))
95 | min-dist (repeat fronts min-colonial)
96 | colonial-dist (map + min-dist excess-dist)]
97 | (assert (= (reduce + colonial-dist) troops) "grr")
98 | colonial-dist))
99 |
100 |
101 | (colonial-troop-allocation 120 1) ; -> (120)
102 | (colonial-troop-allocation 120 2) ; -> (60 60)
103 | (colonial-troop-allocation 120 3) ; -> (40 40 40)
104 |
105 | (colonial-troop-allocation 225 3) ; -> (75 75 75)
106 | (colonial-troop-allocation 225 5) ; -> (45 45 45 45 45)
107 | (colonial-troop-allocation 225 7) ; -> (33 32 32 32 32 32 32)
108 | (colonial-troop-allocation 225 8) ; -> (29 28 28 28 28 28 28 28)
109 |
110 | (colonial-troop-allocation 7 8) ; -> (1 1 1 1 1 1 1 0)
111 |
112 | ;; Now the rebels have an easier task. They know how many fronts they
113 | ;; have to win on, and they only have to win by one soldier on each
114 | ;; front, so they can work out a winning allocation so:
115 |
116 | (defn rebels-allocation
117 | [colonial-allocation]
118 | (let [majority (inc (quot (count colonial-allocation) 2))]
119 | (map inc (take majority (sort colonial-allocation)))))
120 |
121 |
122 |
123 | (defn rebels-needed-for-rebellion
124 | [colonial-troops fronts]
125 | (let [ca (colonial-troop-allocation colonial-troops fronts)
126 | rn (rebels-allocation ca)]
127 | (reduce + rn)))
128 |
129 |
130 | (map (partial rebels-needed-for-rebellion colonial-troops) (iterate inc 1))
131 |
132 |
133 | ;; -> (4001 4002 2668 3003 2403 2670 2288 2505 2225 2406 2186 2338 2159 2292 2139 2259 2124 2230 2111 2211 2101 2192 2098 2176 2093 ...)
134 |
135 | ;; To win on one front, they need 4001 brave comrades.
136 | ;; To win on two, they need 4002 (one win and one draw doesn't quite cut it).
137 | ;; To win on three, they only need 2668.
138 | ;; As the number of fronts goes up, the number appears to be settling down
139 | ;; to around 2000, which is a nice result.
140 |
141 | ;; "A perfectly informed flexible force fighting an enemy with a
142 | ;; uniform order-of-battle needs more than half as many troops to win"
143 |
144 | ;; How would you advise the rebel commander?
145 |
146 | ;; What advice would you give the Brits?
147 |
--------------------------------------------------------------------------------
/src/clojure_through_code/thinking_functional_demo.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.thinking-functional-demo)
2 |
3 |
4 | ;;
5 | ;; Pure Functions
6 |
7 | (defn increment-numbers
8 | [number-collection]
9 | (map inc number-collection))
10 |
11 |
12 | (increment-numbers '(1 2 3 4 5))
13 |
14 |
15 | ;; takes in a specific argument and returns a deterministic value
16 |
17 | ;; impure function
18 |
19 | (def global-value '(5 4 3 2 1))
20 |
21 |
22 | (defn impure-increment-numbers
23 | [number-collection]
24 | (map inc global-value))
25 |
26 |
27 | (impure-increment-numbers '(1 2 3 4 5))
28 |
29 |
30 | ;; uses a global value rather than the argument so the result is indeterministic
31 |
32 | ;;
33 | ;; impure function - random numbers
34 |
35 | (:import java.util.Date)
36 |
37 |
38 | (defn current-date
39 | []
40 | (java.util.Date.))
41 |
42 |
43 | (current-date)
44 |
45 |
46 | ;; Random numbers should be generated outside the function and passed as an argument, keeping the function pure.
47 |
48 | (defn print-to-console
49 | [value-to-print]
50 | (println "The value is:" value-to-print))
51 |
52 |
53 | (print-to-console "a side effect")
54 |
55 |
56 | ;;
57 | ;; Persistent data structures
58 |
59 | (def persistent-vector [0 1 2 3 4])
60 |
61 |
62 | ;; lets add a value to our persistent vector
63 | (conj persistent-vector 5)
64 |
65 |
66 | ;; and another..
67 | (conj persistent-vector 6)
68 |
69 |
70 | ;; Chain two function calls using the threading macro
71 | (-> persistent-vector
72 | (conj 5)
73 | (conj 6))
74 |
75 |
76 | ;;
77 | ;; Higher Order functions
78 |
79 | ;; functions can be used as an arugument to other functions,
80 | ;; because a function always evaluates to a value
81 |
82 | (map inc [1 2 3 4 5])
83 |
84 | (reduce + (range 1 10))
85 |
86 | (reduce + '(1 2 3 4 5 6 7 8 9))
87 |
88 | (take 10 (range))
89 |
90 |
91 | (filter
92 | even?
93 | (range 1 10))
94 |
95 |
96 | ;;
97 | ;; recursion
98 |
99 | ;; processing a collection using recursion
100 |
101 | (defn recursively-use-a-collection
102 | [collection]
103 | (println (first collection))
104 | (if (empty? collection)
105 | (print-str "no more values to process")
106 | (recursively-use-a-collection (rest collection))))
107 |
108 |
109 | (recursively-use-a-collection [1 2 3])
110 |
111 |
112 | ;; Polymorphism
113 |
114 | (defn i-am-polly
115 | ([] (i-am-polly "My name is polly"))
116 | ([message] (str message)))
117 |
118 |
119 | (i-am-polly)
120 | (i-am-polly "I call different behaviour depeding on arguments sent")
121 |
122 |
123 | ;; recursion with polymorphism
124 |
125 | (defn sum
126 | ([vals] (sum vals 0))
127 | ([vals accumulating-total]
128 | (if (empty? vals)
129 | accumulating-total
130 | (sum (rest vals) (+ (first vals) accumulating-total)))))
131 |
132 |
133 | (sum [2 7 9 11 13])
134 | (sum [1])
135 | (sum [7 9 11 13] 9)
136 |
137 |
138 | ;; If we have a very large collection, we run the risk of blowing our heap space
139 |
140 | ;; (vec (range 0 9999999999)) ;; say goodbye to your heap space
141 |
142 | ;; Using tail call optomisation allows us to reuse a memory location when we call a function recursively.
143 | ;; Recur allows the processing of a very large data set without blowing the heap space.
144 |
145 | (defn sum
146 | ([vals] (sum vals 0))
147 | ([vals accumulating-total]
148 | (if (empty? vals)
149 | accumulating-total
150 | (recur (rest vals) (+ (first vals) accumulating-total)))))
151 |
152 |
153 | (sum (vec (range 0 9999999)))
154 |
155 |
156 | ;;
157 | ;; Lazy Evaluation
158 |
159 | ;; Dividing an integer value by another results in a Ratio, a legal value in Clojure
160 |
161 | (/ 22 7)
162 |
163 |
164 | ;; We can also just express a value as a ratio. This works because of the prefix notation of Clojure
165 |
166 | 22/7
167 |
168 |
169 | ;; The lazyness can be overridden by specifying a precision, eg coersing the result into a specific type
170 |
171 | (/ 22 7.0)
172 | (double (/ 22 7))
173 | (float (/ 22 7))
174 |
175 | (take 10 (range))
176 |
177 |
178 | ;;
179 | ;; Sequence / List comprehension
180 |
181 | (range 10)
182 |
183 | (for [x (range 10) :when (even? x)] x)
184 |
185 | (for [x (range 10) :while (even? x)] x)
186 |
187 |
188 | (for [x (range 10)
189 | y (range 10)]
190 | [x y])
191 |
192 |
193 | ;;
194 | ;; managing state
195 |
196 | ;; updating single values with atom
197 |
198 | ;; To create a player table I would define a vector to hold the player name.
199 | ;; As we are going to change state, we want to do it in a safe way,
200 | ;; so we define that vector with an atom
201 |
202 | (def players (atom []))
203 |
204 |
205 | ;; We also add a :validator as a condition to ensure we dont put more than
206 | ;; 2 players into the game
207 |
208 | (def players (atom [] :validator #(<= (count %) 2)))
209 |
210 |
211 | ;; Add players by name
212 | (defn join-game
213 | [name]
214 | (swap! players conj name))
215 |
216 |
217 | (join-game "Rachel")
218 | (join-game "Harriet")
219 | (join-game "Terry") ; the :validator condition on atom only allows 2 players
220 |
221 | (reset! players ["Player 1"])
222 | (reset! players [])
223 |
224 | @players
225 |
226 |
227 | ;; updating multiple values with ref
228 |
229 | (def players-ref (ref [] :validator #(<= (count %) 2)))
230 | (def game-account (ref 1000))
231 | (def harriet-account (ref 0))
232 |
233 |
234 | (defn join-game-safely
235 | [name player-account game-account]
236 | (dosync
237 | (alter players-ref conj name)
238 | (alter player-account + 100)
239 | (alter game-account - 100)))
240 |
241 |
242 | (join-game-safely "Harriet" harriet-account game-account)
243 |
244 | @harriet-account
245 | @game-account
246 | @players-ref
247 |
--------------------------------------------------------------------------------
/src/clojure_through_code/collections.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.collections)
2 |
3 |
4 | ;; The built in data structures of Clojure are great for representing simple and complex collections.
5 |
6 | ;; So what are examples of collecitons? It could be all the movies you own or your music library. It could be all the books you own, both paper and electronic.
7 |
8 | ;; How would you represent a simple collecion of music?
9 |
10 | ;; You could simply list all the albums you have, in this case using a Clojure vector
11 |
12 | ["Smells like Teen Spirit", "Trash", "Vision Thing"]
13 |
14 |
15 | ;; and then give this collection a name
16 |
17 | (def albums ["Smells like Teen Spirit", "Trash", "Vision Thing" "Tubular Bells"])
18 |
19 |
20 | ;; If you have a small collection, this could be okay. However, it doesnt capture the names of the artists. You may want to listen to something by Mike Oldfield but forget which albums they created
21 |
22 | ;; You could use a map data structure in Clojure that links the album with artist
23 |
24 | {"Tubular Bells" "Mike Oldfield"}
25 |
26 |
27 | ;; or you could make this map more expressive by explictly adding keys that give some context to this data.
28 |
29 | {:album-name "Tubular Bells" :artist "Mike Oldfield"}
30 |
31 |
32 | ;; To hold all the albums we have in our music collection we could have each map as an item in our vector
33 |
34 | [{:album-name "Tubular Bells" :artist "Mike Oldfield"}
35 | {:album-name "Smells like teen spirit" :artist "Nirvana"}
36 | {:album-name "Vision Thing" :artist "Sisters of Mercy"}
37 | {:album-name "Here comes the war" :artist "New Model Army"}
38 | {:album-name "Thunder & Consolation" :artist "New Model Army"}]
39 |
40 |
41 | ;; If we give this music collection a name then it makes it easy to use with functions.
42 | ;; We will use this `album-collection' name to for the next examples to keep the examples simple to read.
43 |
44 | (def album-collection
45 | [{:album-name "Tubular Bells" :artist "Mike Oldfield"}
46 | {:album-name "Smells like teen spirit" :artist "Nirvana"}
47 | {:album-name "Vision Thing" :artist "Sisters of Mercy"}
48 | {:album-name "Here comes the war" :artist "New Model Army"}
49 | {:album-name "Thunder & Consolation" :artist "New Model Army"}])
50 |
51 |
52 | ;; We could get a specific album by its position in the collection, like pulling out a specific album out of the shelf that holds our collection.
53 |
54 | ;; lets get the first album from our collection. The first album is in position 0.
55 | (get album-collection 0)
56 |
57 |
58 | ;; => {:album-name "Tubular Bells", :artist "Mike Oldfield"}
59 |
60 | ;; Or we can use the collection functions to also get specific albums
61 | (first album-collection)
62 |
63 |
64 | ;; => {:album-name "Tubular Bells", :artist "Mike Oldfield"}
65 |
66 | (last album-collection)
67 |
68 |
69 | ;; => {:album-name "Thunder & Consolation", :artist "New Model Army"}
70 |
71 |
72 | ;; If we want to get just the album name from a specific album. we first get the album as a result, as a map of key value pairs.
73 | ;; Then using the relevant key in the map we extract just the album name
74 | (get (get album-collection 0) :album-name)
75 |
76 |
77 | ;; => "Tubular Bells"
78 |
79 |
80 | ;; Lets say we want all the album titles in our collection
81 | (map #(get % :album-name) album-collection)
82 |
83 |
84 | ;; => ("Tubular Bells" "Smells like teen spirit" "Vision Thing" "Here comes the war" "Thunder & Consolation")
85 |
86 |
87 | ;;
88 | ;; what if we just want to see all our albums for a specific artist ?
89 |
90 | ;; We can filter out all the album maps in our collection and just return those maps that contain the given artist.
91 |
92 | ;; First, lets remind ourselves how to get a specific value from a map using the get function and a key from the map
93 | (get {:album-name "Thunder & Consolation" :artist "New Model Army"} :artist)
94 |
95 |
96 | ;; => "New Model Army"
97 |
98 | ;; Now we can test the result to see if it is the name of the artist, by using the = function with the name of the artist we are looking for.
99 | (= "New Model Army" (get {:album-name "Thunder & Consolation" :artist "New Model Army"} :artist))
100 |
101 |
102 | ;; => true
103 |
104 | ;; So we could take this test and apply it in turn to all of the album maps in our music collection
105 |
106 | ;; A common function for this is called filter
107 |
108 | ;; first try at is-by-artist? function - problem: trying to parse the whole collection here, rather than just a function to parse each album map in the calling expression.
109 | #_(defn is-by-artist?
110 | "Checks album to see if it is by a particular artist"
111 | [artist collection]
112 | (if-some [result (= (map #(get % :artist) collection) artist)]
113 | result))
114 |
115 |
116 | ;; Lets define a function that takes the name of an artist and an album map and checks if that album is by the said artist. This function will only take one album map, not the collection of album maps.
117 |
118 | (defn is-album-by-artist?
119 | "simplified version"
120 | [artist album-map]
121 | (if (= artist (get album-map :artist))
122 | true
123 | false))
124 |
125 |
126 | (is-album-by-artist? "New Model Army" {:album-name "Thunder & Consolation" :artist "New Model Army"})
127 |
128 |
129 | ;; => true
130 |
131 |
132 |
133 | (filter (partial is-album-by-artist? "New Model Army") album-collection)
134 |
135 |
136 | ;; => ({:album-name "Here comes the war", :artist "New Model Army"} {:album-name "Thunder & Consolation", :artist "New Model Army"})
137 |
138 | ;; not quite right...
139 |
140 | ;; (get (filter (partial is-album-by-artist? "New Model Army") album-collection) :album-name)
141 | ;; => nil
142 |
--------------------------------------------------------------------------------
/src/clojure_through_code/pretty_tables.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.pretty-tables)
2 |
3 |
4 | ;; Displaying resuts in ascii tables
5 | ;; https://github.com/cldwalker/table
6 | ;; In SpacEmacs the tables appear in the REPL window
7 | ;; The table function returns nil, just like println function
8 |
9 | ;; Clojure since 1.5 comes with clojure.pprint/print-table
10 |
11 | ;; (:require [table.core :refer [table]])
12 |
13 | (require '[table.core :refer [table]])
14 |
15 | (table [["1" "2"] ["3" "4"]])
16 |
17 | (table '((1 2) (3 4)))
18 | (table #{[1 2] [3 4]})
19 |
20 | (table [{:a 11} {:a 3 :b 22}])
21 |
22 |
23 | ;; unicode
24 | (table [[1 2] [3 4]] :style :unicode)
25 |
26 |
27 | ;; org-mode format
28 | (table [[1 2] [3 4]] :style :org)
29 |
30 |
31 | ;; Generate tables for github's markdown
32 | (table [[10 20] [3 4]] :style :github-markdown)
33 |
34 |
35 | ;; custom styles - cool
36 | (table [[10 20] [3 4]] :style {:top ["◤ " " ▼ " " ◥"]
37 | :top-dash "✈︎"
38 | :middle ["▶︎ " " " " ◀︎"]
39 | :dash "✂︎"
40 | :bottom ["◣ " " ▲ " " ◢"]
41 | :bottom-dash "☺︎"
42 | :header-walls [" " " " " "]
43 | :body-walls [" " " " " "]})
44 |
45 |
46 | ;; Table can handle plain maps and vectors of course:
47 |
48 | (require '[clojure.repl :refer :all])
49 | (table (meta #'doc))
50 |
51 |
52 | ;; looking at the Java Class loader
53 | (table (seq (.getURLs (java.lang.ClassLoader/getSystemClassLoader))))
54 |
55 |
56 | ;;
57 | ;; clojure.pprint/print-table
58 | ;; Prints a table from the data held in a collection (vector) of maps [{"key" "value"}]
59 | ;; When keys are provided as the first argument (also in a vector) they are used to control wich headers and colums of values are printed (and in what order)
60 | (doc clojure.pprint/print-table)
61 | (source clojure.pprint/print-table)
62 |
63 |
64 | (clojure.pprint/print-table [{"name" "John"}])
65 |
66 |
67 | ;; You can specify the keys to use from the map collections
68 | (clojure.pprint/print-table ["name" "address" "postcode"] [{"name" "john" "address" "Bromley" "postcode" "90210"}])
69 |
70 |
71 | ;; If you ommit a key, its header and values are not included in the output
72 | (clojure.pprint/print-table ["name" "postcode"] [{"name" "john" "address" "Bromley" "postcode" "90210"}])
73 |
74 |
75 | ;; if you include a key that does not exist, a header is create for that key, but no values are included (as they do not exist)
76 | (clojure.pprint/print-table ["name" "age"] [{"name" "john" "address" "Bromley" "postcode" "90210"}])
77 |
78 |
79 | ;; If you have a collection of maps whith a whole range of data about contacts, then its easy to print out just the information you need, in the order you want to see it.
80 |
81 | (def my-customers
82 | [{:name "Sarah Smith" :location "London" :company "SalesForTheWin"}
83 | {:name "James Smith" :location "London" :company "SalesForTheWin"}
84 | {:name "Olive Oills" :location "Bristol" :company "Spinach4You"}
85 | {:name "Betty Boops" :location "Bristol" :company "Poodle Groom"}])
86 |
87 |
88 | (clojure.pprint/print-table [:name :company] my-customers)
89 |
90 |
91 | ;; | :name | :company |
92 | ;; |-------------+----------------|
93 | ;; | Sarah Smith | SalesForTheWin |
94 | ;; | James Smith | SalesForTheWin |
95 | ;; | Olive Oills | Spinach4You |
96 | ;; | Betty Boops | Poodle Groom |
97 |
98 |
99 | ;; more examples at https://clojuredocs.org/clojure.pprint/print-table
100 |
101 |
102 | ;; You can control the headings by specifying the keys in a vector as the first argument to print-table. The keys must be the same as those in the maps.
103 | (clojure.pprint/print-table [:name :address :postcode]
104 | [{:name "john" :address "Bromley" :postcode "90210"}
105 | {:name "fred" :address "london" :postcode "12345"}])
106 |
107 |
108 | ;; chaning the order of the keys changes the order of the headings
109 | (clojure.pprint/print-table [:name :postcode :address]
110 | [{:name "john" :address "Bromley" :postcode "90210"}
111 | {:name "fred" :address "london" :postcode "12345"}])
112 |
113 |
114 | ;; if you include an incorrect key, that key will be used to create the columm, and contain no informatoin as there are no valus associated with the key in the maps.
115 | (clojure.pprint/print-table [:name :address :postcode]
116 | [{:name "john" :adress "Bromley" :postcode "90210"}
117 | {:name "fred" :adress "london" :postcode "12345"}])
118 |
119 |
120 | (clojure.pprint/print-table [{"name" "John" "address" "bromley" "postcode" 123}
121 | {"name" "Fred" "address" "london" "postcode" 321}])
122 |
123 |
124 | ;; If you use a string as the keys argument, it will treat the string as a vector of characters and create a heading for each. However, as none of those keys match the keys in the map, no values are printed in the rows.
125 | (clojure.pprint/print-table "position" [{"1" "2"} {"3" "4"}])
126 |
127 |
128 | ;; | p | o | s | i | t | i | o | n |
129 | ;; |---+---+---+---+---+---+---+---|
130 | ;; | | | | | | | | |
131 | ;; | | | | | | | | |
132 |
133 |
134 |
135 | ;; Kanban coaching eachange
136 |
137 | ;; should a scrum master in a large org be like quantum leap... except he help other people solve their own problems
138 |
139 | ;; this is not a blame culture, but that was your fault
140 | ;; this is not a blame culture, so we will be watching you...
141 | ;; this is not a blame culture, so if things go wrong its your fault
142 |
143 |
144 | ;; amy kuddy - fact it til you make it - TED talk
145 |
146 | ;; IQ vs EQ - just being intelligent is not enough, to be successful we need to EQ and IQ....
147 |
--------------------------------------------------------------------------------
/src/clojure_through_code/xx-fibonachi.clj:
--------------------------------------------------------------------------------
1 | ;; Fibonachi series
2 | ;; simple description: the next number is the sum of the previous two, starting with 1 and 2
3 |
4 | ;; Some references
5 | ;; http://squirrel.pl/blog/2010/07/26/corecursion-in-clojure/
6 |
7 |
8 | ;; could define a data structure, large enough for any series length you want
9 | (def fibonachi-data #{1 2 3 5 8 13 21 34 55 89 144 223})
10 |
11 |
12 | ;; The evaluation of the set is not ordered, so use a function to create a sorted output
13 | ;; We can sort in accending < order, or decending > order
14 | (sorted-set-by > 1 2 3 5 8 13 21 34 55 89 144 223)
15 |
16 |
17 | ;; We can of course also create an ordered set to start with and give it a name,
18 | ;; so when we refer to the set by name it always gives us an ordered set
19 | (def fibonachi-data (sorted-set 1 2 3 5 8 13 21 34 55 89 144 223))
20 |
21 | fibonachi-data
22 |
23 |
24 | ;; Rather than generate a long set of numbers, we can use lazy evaluation to generate just the series we need each time.
25 | ;; Clojure is very good at generating data structures and only calculating the part of the data structure needed at that time
26 |
27 | ;; need to work out a good algorithm for generating the fibonachi series
28 | (defn fibonachi-series
29 | [series-length]
30 | (let [x series-length]
31 | x))
32 |
33 |
34 | (fibonachi-series 8)
35 |
36 |
37 | ;; we just want to have a certain number of integers in the series, so we can use take
38 | (take 2 '(1 2 3 5))
39 |
40 |
41 | ;; How do we generate a number sequence ?
42 |
43 | (inc 3)
44 |
45 |
46 | ;; lets create a sequence to play with
47 | (def my-sequence '(1 2 3 4 5))
48 |
49 |
50 | ;; I can get the first element of the sequence, or the rest of the sequence
51 | (first my-sequence)
52 | (rest my-sequence)
53 |
54 |
55 | ;; Add a condition, based on whether there is anything in the sequence
56 | (if (not-empty ()) 1 -1)
57 | (if (not-empty (rest my-sequence)) 1 2)
58 |
59 |
60 | (defn gen-lazy-sequence
61 | [length]
62 | (let [fib
63 | [length]]
64 | fib))
65 |
66 |
67 | (gen-lazy-sequence 4)
68 |
69 |
70 | ;;
71 | ;; Other solutions to fibonachi
72 |
73 | ;; Programming in Clojure p. 136
74 |
75 | ;; (defn fibo [] (map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))
76 |
77 | ;; (defn proj-euler2 []
78 | ;; (reduce + (for [x (fibo) :when (even? x) :while (< x 4000000)] x)))
79 |
80 |
81 | ;; Solutions from http://en.wikibooks.org/wiki/Clojure_Programming/Examples/Lazy_Fibonacci
82 |
83 | ;; A function which lazily produces Fibonacci numbers:
84 |
85 | ;; (def fib-seq
86 | ;; ((fn rfib [a b]
87 | ;; (lazy-seq (cons a (rfib b (+ a b)))))
88 | ;; 0 1))
89 |
90 | ;; (take 20 fib-seq)
91 |
92 | ;; We can use iterate to generate pairs of [a b] and then take the first of each one.
93 |
94 | ;; (defn fib-step [[a b]]
95 | ;; [b (+ a b)])
96 |
97 | ;; (defn fib-seq []
98 | ;; (map first (iterate fib-step [0 1])))
99 |
100 | ;; (take 20 (fib-seq))
101 |
102 |
103 | ;; Recursive Fibonacci with any start point and the amount of numbers that you want[edit]
104 | ;; (defn fib [start range]
105 | ;; "Creates a vector of fibonnaci numbers"
106 | ;; (if (<= range 0)
107 | ;; start
108 | ;; (recur (let[subvector (subvec start (- (count start) 2))
109 | ;; x (nth subvector 0)
110 | ;; y (nth subvector 1)
111 | ;; z (+ x y)]
112 | ;; (conj start z))
113 | ;; (- range 1))))
114 |
115 |
116 | ;; Self-Referential Version[edit]
117 | ;; Computes the Fibonacci sequence by mapping the sequence with itself, offset by one element.
118 |
119 | ;; (def fib (cons 1 (cons 1 (lazy-seq (map + fib (rest fib))))))
120 |
121 |
122 | ;; From stack exchange
123 |
124 | ;; initial solution
125 |
126 | ;; (defn fib [x, n]
127 | ;; (if (< (count x) n)
128 | ;; (fib (conj x (+ (last x) (nth x (- (count x) 2)))) n)
129 | ;; x))
130 |
131 | ;; To use this I need to seed x with [0 1] when calling the function. My question is, without wrapping it in a separate function, is it possible to write a single function that only takes the number of elements to return?
132 |
133 | ;; Doing some reading around led me to some better ways of achieving the same funcionality:
134 |
135 | ;; (defn fib2 [n]
136 | ;; (loop [ x [0 1]]
137 | ;; (if (< (count x) n)
138 | ;; (recur (conj x (+ (last x) (nth x (- (count x) 2)))))
139 | ;; x)))
140 |
141 | ;; and
142 |
143 | ;; (defn fib3 [n]
144 | ;; (take n
145 | ;; (map first (iterate (fn [[a b]] [b (+ a b)]) [0 1]))))
146 |
147 | ;; Multi-arity approach
148 |
149 | ;; (defn fib
150 | ;; ([n]
151 | ;; (fib [0 1] n))
152 | ;; ([x, n]
153 | ;; (if (< (count x) n)
154 | ;; (fib (conj x (+ (last x) (nth x (- (count x) 2)))) n)
155 | ;; x)))
156 |
157 |
158 | ;; Thrush operator
159 |
160 | ;; You can use the thrush operator to clean up #3 a bit (depending on who you ask; some people love this style, some hate it; I'm just pointing out it's an option):
161 |
162 | ;; (defn fib [n]
163 | ;; (->> [0 1]
164 | ;; (iterate (fn [[a b]] [b (+ a b)]))
165 | ;; (map first)
166 | ;; (take n)))
167 | ;; That said, I'd probably extract the (take n) and just have the fib function be a lazy infinite sequence.
168 |
169 | ;; (defn fib
170 | ;; (->> [0 1]
171 | ;; (iterate (fn [[a b]] [b (+ a b)]))
172 | ;; (map first)))
173 |
174 | ;; ;;usage
175 | ;; (take 10 fib)
176 | ;; ;;output (0 1 1 2 3 5 8 13 21 34)
177 |
178 |
179 | ;; Avoiding recursion ?
180 |
181 | ;; In Clojure it's actually advisable to avoid recursion and instead use the loop and recur special forms.
182 | ;; This turns what looks like a recursive process into an iterative one, avoiding stack overflows and
183 | ;; improving performance.
184 |
185 | ;; Here's an example of how you'd implement a Fibonacci sequence with this technique:
186 |
187 | ;; (defn fib [n]
188 | ;; (loop [fib-nums [0 1]]
189 | ;; (if (>= (count fib-nums) n)
190 | ;; (subvec fib-nums 0 n)
191 | ;; (let [[n1 n2] (reverse fib-nums)]
192 | ;; (recur (conj fib-nums (+ n1 n2)))))))
193 |
194 | ;; The loop construct takes a series of bindings, which provide initial values, and one or more body forms.
195 | ;; In any of these body forms, a call to recur will cause the loop to be called recursively with the
196 | ;; provided arguments.
197 |
--------------------------------------------------------------------------------
/src/clojure_through_code/debug.clj:
--------------------------------------------------------------------------------
1 | ;;
2 | ;; Debug clojure
3 | ;;
4 | ;; Functions and tools to help debug Clojure and understand errors
5 | ;;
6 |
7 |
8 | ;; Simple debug and breakpoint directives
9 | ;;
10 |
11 |
12 | (dotimes [index 10]
13 | #dbg ^{:break/when (= index 7)}
14 | (prn index))
15 |
16 |
17 | (dotimes [i 10]
18 | #break (= i 7)
19 | (prn i))
20 |
21 |
22 | (dotimes [index 10]
23 | #break (prn index)
24 | index)
25 |
26 |
27 | ;; break on each iteration of the sequence,
28 | ;; showing the local value of the index
29 | (dotimes [index 10]
30 | #break index)
31 |
32 |
33 | ;; evaluate until the condition is met and then break
34 | (dotimes [index 10]
35 | #dbg ^{:break/when (= index 7)}
36 | index)
37 |
38 |
39 | ;; the break condition is met multiple times
40 | (dotimes [index 10]
41 | #dbg ^{:break/when (odd? index)}
42 | index)
43 |
44 |
45 | ;; Clojure 1.10 error messages
46 | ;; https://insideclojure.org/2018/12/17/errors/
47 |
48 | ;; updated Clojure error reporting calls out where in the code or repl something went wrong
49 |
50 |
51 | ;; :::bad-keyword-syntax
52 |
53 | ;; clj-kondo identifies invalid keyword
54 |
55 | ;; cider error report
56 |
57 | ;; Show: Project-Only All
58 | ;; Hide: Clojure Java REPL Tooling Duplicates (9 frames hidden)
59 |
60 | ;; 3. Unhandled clojure.lang.ExceptionInfo
61 | ;; (No message)
62 | ;; {:clojure.error/phase :read-source}
63 |
64 | ;; main.clj: 433 clojure.main/repl/read-eval-print/fn
65 | ;; main.clj: 432 clojure.main/repl/read-eval-print
66 | ;; main.clj: 458 clojure.main/repl/fn
67 | ;; main.clj: 458 clojure.main/repl
68 | ;; main.clj: 368 clojure.main/repl
69 | ;; RestFn.java: 1523 clojure.lang.RestFn/invoke
70 | ;; interruptible_eval.clj: 84 nrepl.middleware.interruptible-eval/evaluate
71 | ;; interruptible_eval.clj: 56 nrepl.middleware.interruptible-eval/evaluate
72 | ;; interruptible_eval.clj: 152 nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
73 | ;; AFn.java: 22 clojure.lang.AFn/run
74 | ;; session.clj: 218 nrepl.middleware.session/session-exec/main-loop/fn
75 | ;; session.clj: 217 nrepl.middleware.session/session-exec/main-loop
76 | ;; AFn.java: 22 clojure.lang.AFn/run
77 | ;; Thread.java: 833 java.lang.Thread/run
78 |
79 | ;; 2. Caused by clojure.lang.LispReader$ReaderException
80 | ;; java.lang.RuntimeException: Invalid token: :::bad-keyword-syntax
81 | ;; {:clojure.error/column 1, :clojure.error/line 19}
82 |
83 | ;; LispReader.java: 314 clojure.lang.LispReader/read
84 | ;; LispReader.java: 216 clojure.lang.LispReader/read
85 | ;; LispReader.java: 205 clojure.lang.LispReader/read
86 | ;; core.clj: 3756 clojure.core/read
87 | ;; core.clj: 3729 clojure.core/read
88 | ;; interruptible_eval.clj: 108 nrepl.middleware.interruptible-eval/evaluate/fn
89 | ;; main.clj: 433 clojure.main/repl/read-eval-print/fn
90 | ;; main.clj: 432 clojure.main/repl/read-eval-print
91 | ;; main.clj: 458 clojure.main/repl/fn
92 | ;; main.clj: 458 clojure.main/repl
93 | ;; main.clj: 368 clojure.main/repl
94 | ;; RestFn.java: 1523 clojure.lang.RestFn/invoke
95 | ;; interruptible_eval.clj: 84 nrepl.middleware.interruptible-eval/evaluate
96 | ;; interruptible_eval.clj: 56 nrepl.middleware.interruptible-eval/evaluate
97 | ;; interruptible_eval.clj: 152 nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
98 | ;; AFn.java: 22 clojure.lang.AFn/run
99 | ;; session.clj: 218 nrepl.middleware.session/session-exec/main-loop/fn
100 | ;; session.clj: 217 nrepl.middleware.session/session-exec/main-loop
101 | ;; AFn.java: 22 clojure.lang.AFn/run
102 | ;; Thread.java: 833 java.lang.Thread/run
103 |
104 | ;; 1. Caused by java.lang.RuntimeException
105 | ;; Invalid token: :::bad-keyword-syntax
106 |
107 | ;; Util.java: 221 clojure.lang.Util/runtimeException
108 | ;; LispReader.java: 412 clojure.lang.LispReader/interpretToken
109 | ;; LispReader.java: 305 clojure.lang.LispReader/read
110 | ;; LispReader.java: 216 clojure.lang.LispReader/read
111 | ;; LispReader.java: 205 clojure.lang.LispReader/read
112 | ;; core.clj: 3756 clojure.core/read
113 | ;; core.clj: 3729 clojure.core/read
114 | ;; interruptible_eval.clj: 108 nrepl.middleware.interruptible-eval/evaluate/fn
115 | ;; main.clj: 433 clojure.main/repl/read-eval-print/fn
116 | ;; main.clj: 432 clojure.main/repl/read-eval-print
117 | ;; main.clj: 458 clojure.main/repl/fn
118 | ;; main.clj: 458 clojure.main/repl
119 | ;; main.clj: 368 clojure.main/repl
120 | ;; RestFn.java: 1523 clojure.lang.RestFn/invoke
121 | ;; interruptible_eval.clj: 84 nrepl.middleware.interruptible-eval/evaluate
122 | ;; interruptible_eval.clj: 56 nrepl.middleware.interruptible-eval/evaluate
123 | ;; interruptible_eval.clj: 152 nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
124 | ;; AFn.java: 22 clojure.lang.AFn/run
125 | ;; session.clj: 218 nrepl.middleware.session/session-exec/main-loop/fn
126 | ;; session.clj: 217 nrepl.middleware.session/session-exec/main-loop
127 | ;; AFn.java: 22 clojure.lang.AFn/run
128 | ;; Thread.java: 833 java.lang.Thread/run
129 |
130 |
131 |
132 | ;; Can the value be printed along with the exception in some cases like below
133 |
134 | ;; Current error on 1.10
135 |
136 | (map identity (map 123 (range 10)))
137 |
138 |
139 | ;; Error printing return value (ClassCastException) at clojure.core/map$fn (core.clj:2753).
140 | ;; java.lang.Long cannot be cast to clojure.lang.IFn
141 |
142 | ;; Adding the value that cannot be cast might give better context
143 |
144 | (map identity (map 123 (range 10)))
145 |
146 |
147 | ;; Error printing return value (ClassCastException) at clojure.core/map$fn (core.clj:2753).
148 | ;; java.lang.Long (123) cannot be cast to clojure.lang.IFn
149 |
150 |
151 | ;; The error message phase here is in printing the return value. So it can’t be printed, because that is where the error occurs. A lot of lazy sequence errors end up looking like this.
152 |
153 | ;; This particular error is a case where the invocation is wrong (args swapped), which is really a perfect case for a spec or precondition, to avoid constructing a broken lazy seq. I’ve been thinking we should probably improve the particular “can’t cast to IFn” too since it’s so common.
154 |
155 | ;; https://github.com/slipset/speculative is ok and has a spec that would catch this.
156 |
--------------------------------------------------------------------------------
/src/clojure_through_code/03a_changing_data_structures.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.03a-changing-data-structures)
2 |
3 |
4 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
5 | ;; Changing data in data structures
6 |
7 | ;; Wait, I thought you said that data structures were immutable!
8 | ;; So how can we change them then?
9 |
10 | ;; Yes, lists, vectors, maps and sets are all immutable. However,
11 | ;; you can get a new data structure that has the changes you want.
12 | ;; To make this approach efficient, the new data structure contains only
13 | ;; the new data and links back to the existing data structure for shared
14 | ;; data elements.
15 |
16 | ;; We will see some of the most common functions that work with data
17 | ;; structures in this secion. In actuality, everything can be
18 | ;; considered a function that works on a data structure though,
19 | ;; as that is the language design of clojure.
20 |
21 | ;; First we will use the conj function, think of this as the english phrase conjoin
22 | ;; conj will join a data structure and a value together to form a new data structure.
23 |
24 | ;; conj works on all Clojure persistent data structures (list, vector, map, set)
25 |
26 | ;; Lets add a number to a list of 4 numbers using the function conj
27 | ;; I think of cons as "constructs" a new data structure from the
28 | ;; existing data structure
29 |
30 | (conj '(1 2 3 4) 5)
31 |
32 | ;; As a Clojure list is represented a linked list, then its is more efficient to add new elements to the start of a list.
33 |
34 | ;; conj does not change the existing list, it create a new list
35 | ;; that contains the number 5 and a link to all the elements of the
36 | ;; existing list.
37 |
38 | ;; You can also use conj on other data structures and data types
39 |
40 | ;; vectors
41 | (conj [1 2 3 4] 5)
42 |
43 | ;; list of strings
44 | (conj '("and" "chips") "fish")
45 |
46 | (conj #{1 2 3} 4)
47 |
48 | ;; If you use cons (think construct) this returns a list, regardless of the original data structure type.
49 |
50 |
51 |
52 | ;; notice that conjoining to a vector is done at the end
53 | (conj [1 2 3] 4)
54 | ;;=> [1 2 3 4]
55 |
56 | ;; notice conjoining to a list is done at the beginning
57 | (conj '(1 2 3) 4)
58 | ;;=> (4 1 2 3)
59 |
60 | (conj ["a" "b" "c"] "d")
61 | ;;=> ["a" "b" "c" "d"]
62 |
63 | ;; conjoining multiple items is done in order
64 | (conj [1 2] 3 4)
65 | ;;=> [1 2 3 4]
66 |
67 | (conj '(1 2) 3 4)
68 | ;;=> (4 3 1 2)
69 |
70 | (conj [[1 2] [3 4]] [5 6])
71 | ;;=> [[1 2] [3 4] [5 6]]
72 |
73 | ;; conjoining to maps only take items as vectors of length exactly 2
74 | (conj {1 2, 3 4} [5 6])
75 | ;;=> {5 6, 1 2, 3 4}
76 |
77 | (conj {:firstname "John" :lastname "Doe"} {:age 25 :nationality "Chinese"})
78 | ;;=> {:nationality "Chinese", :age 25, :firstname "John", :lastname "Doe"}
79 |
80 | ;; conj on a set
81 | (conj #{1 3 4} 2)
82 | ;;=> #{1 2 3 4}
83 |
84 |
85 |
86 | ;; from Clojure.org
87 |
88 | ;; conjoin shows similar behaviour to cons
89 | ;; The main difference being that conj works on collections
90 | ;; but cons works with seqs.
91 | (conj ["a" "b" "c"] ["a" "b" "c"] )
92 | ;;=> ["a" "b" "c" ["a" "b" "c"]]
93 |
94 | link
95 |
96 | ;; conjoin nil with x or xs
97 | (conj nil 3)
98 | ;;=> (3)
99 |
100 | (conj nil 3 4)
101 | ;;=> (4 3)
102 |
103 | link
104 |
105 | ;; maps and sets are treated differently
106 | (conj {1 2} {3 4})
107 | ;;=> {3 4, 1 2} ; the contents of {3 4} are added to {1 2}
108 |
109 | (conj #{1 2} #{3})
110 | ;;=> #{1 2 #{3}} ; the whole set #{3} is added to #{1 2}
111 |
112 | (clojure.set/union #{1 2} #{3})
113 | ;;=> #{1 2 3} ; must use (clojure.set/union) to merge sets, not conj
114 |
115 | link
116 |
117 | ;; When conjoining into a map, vector pairs may be provided:
118 | (conj {:a 1} [:b 2] [:c 3])
119 | ;;=> {:c 3, :b 2, :a 1}
120 |
121 | ;; Or maps may be provided, with multiple pairings:
122 | (conj {:a 1} {:b 2 :c 3} {:d 4 :e 5 :f 6})
123 | ;;=> {:f 6, :d 4, :e 5, :b 2, :c 3, :a 1}
124 |
125 | ;; But multiple pairings cannot appear in vectors:
126 | (conj {:a 1} [:b 2 :c 3])
127 | ;;=> IllegalArgumentException Vector arg to map conj must be a pair...
128 |
129 | ;; And pairs may not be provided in lists:
130 | (conj {:a 1} '(:b 2))
131 | ;;=> ClassCastException ...Keyword cannot be cast to ...Map$Entry...
132 |
133 |
134 | ;; Returns a new seq where x is the first element and seq is the rest.
135 |
136 |
137 |
138 |
139 |
140 |
141 | #######################################
142 | ### In practice
143 |
144 | ;; Lets define a simple list and give it a name
145 | (def list-one '(1 2 3))
146 |
147 | ;; the name evaluates to what we expect
148 | list-one
149 |
150 | ;; If we add the number 4 using the cons function, then we
151 | ;; get a new list in return, with 4 added to the front (because thats how lists work with cons)
152 | (cons 4 list-one)
153 |
154 | ;; If we want to keep the result of adding to the list, we can assign it a different name
155 | (def list-two (cons 4 list-one))
156 | ;; and we get the result we want
157 | listtwo
158 |
159 | ;; we can also assing the original name we used for the list to the new list
160 | (def list-one (cons 4 list-one))
161 |
162 | ;; If we re-evaluate the definition above, then each time we will get an extra
163 | ;; number 4 added to the list.
164 |
165 | list-one
166 |
167 | ;; Again, this is not changing the original list, we have just moved the name
168 | ;; of the list to point to the new list.
169 | ;; Any other function working with this data structure before reassigning the name
170 | ;; will not be affected by the re-assignment and will use the unchanged list.
171 |
172 |
173 |
174 |
175 | ;;;; Changing Maps
176 |
177 | (def alphabet-soup {:a 1 :b 2 :c 3})
178 |
179 | (assoc alphabet-soup :d 4)
180 |
181 |
182 | ;;;; Creating default maps from a known set of keys
183 |
184 | (zipmap [:foo :bar :baz] (repeat nil))
185 | ;; => {:foo nil, :bar nil, :baz nil}
186 |
187 | ;; alternatively
188 | (into {} (for [k [:foo :bar :baz]] [k nil]))
189 | ;; => {:foo nil, :bar nil, :baz nil}
190 |
191 | ;; creating a map with random integer values (use rand for decimal)
192 | (zipmap [:foo :bar :baz] (repeatedly #(rand-int 11)))
193 | ;; => {:foo 9, :bar 2, :baz 9}
194 |
195 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
196 | ;; Creating vectors
197 |
198 | (into [] (take 10 (range)))
199 |
200 |
201 | (def player-data [{:name "Oliver", :score 100} {:name "Revilo", :score 50}])
202 |
203 |
204 | (require '[hiccup.page :refer [html5]])
205 |
206 | ;; Whilst you could use map to iterate over the hiccup data structure
207 | (html5 (for [row player-data]
208 | [:tr (map (fn [x] [:td (val x)]) row)]))
209 | ;; => "\n| Oliver | 100 |
| Revilo | 50 |
"
210 |
211 | ;; Its more idiomatic to use a let form to define local names that are then used in the hiccup data structure
212 | (html5 (for [row player-data
213 | :let [player (:name row)
214 | score (:score row)]]
215 | [:tr [:td player] [:td score]]))
216 | ;; => "\n| Oliver | 100 |
| Revilo | 50 |
"
217 |
--------------------------------------------------------------------------------
/src/clojure_through_code/composing_functions.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.composing-functions)
2 |
3 |
4 | ;;
5 | ;; map over collections with partial
6 |
7 | ;; We can map over a collection of words and increment them by writing an anonymous function.
8 |
9 | (map (fn [animal] (str animal "s")) ["pig" "cow" "goat" "cat" "dog" "rabbit"])
10 |
11 |
12 | ;; or using the syntactic sugar form of an anonymous function we get
13 |
14 | (map #(str % "s") ["pig" "cow" "goat" "cat" "dog" "rabbit"])
15 |
16 |
17 | ;; by default map returns a list/sequence. We can specify a vector be returned instead using `mapv'
18 |
19 | (mapv #(str % "s") ["pig" "cow" "goat" "cat" "dog" "rabbit"])
20 |
21 |
22 | ;; what about sheep, where the plural is sheep? We would want to add a condition or filter somewhere
23 |
24 | ;; first lets abstact out the annonymous function
25 |
26 | (defn pluralise
27 | "Pluralise a given string value"
28 | [animal]
29 | (str animal "s"))
30 |
31 |
32 | ;; and give a name to our collection of animals
33 |
34 | (def animals ["pig" "cow" "goat" "cat" "dog" "rabbit"])
35 |
36 | (map pluralise animals)
37 |
38 |
39 | ;; using an if statement as a filter
40 | (defn pluralise
41 | "Pluralise a given string value"
42 | [string]
43 | (if (= string "sheep")
44 | string
45 | (str string "s")))
46 |
47 |
48 | (map pluralise animals)
49 |
50 |
51 | ;; but there are quite a lot of animals that do not have a plural form
52 |
53 | ;; define a collection of animals that are not plural
54 |
55 | (def non-plural-words ["deer" "sheep" "shrimp"])
56 |
57 |
58 | (defn pluralise
59 | "Pluralise a given string value"
60 | [string]
61 | (if (some #{string} non-plural-words)
62 | string
63 | (str string "s")))
64 |
65 |
66 | (def animals ["pig" "cow" "goat" "cat" "dog" "rabbit" "sheep" "shrimp" "deer"])
67 |
68 | (map pluralise animals)
69 |
70 |
71 | ;; to keep the function pure, we should really pass the non-plural-words as an argument
72 |
73 | (defn pluralise
74 | "Pluralise a given string value"
75 | [string non-plural-words]
76 | (if (some #{string} non-plural-words)
77 | string
78 | (str string "s")))
79 |
80 |
81 | ;; using an anonymous function we can send the two arguments required to the pluralise function, as map will replace the % character with an element from the animals collection for each element in the collection.
82 | (map #(pluralise % non-plural-words) animals)
83 |
84 | (map (fn [animal] (pluralise animal non-plural-words)) animals)
85 |
86 |
87 | ;; we could also use a partial function, saving on having to create an anonymous
88 |
89 | (defn pluralise
90 | "Pluralise a given string value"
91 | [non-plural-words string]
92 | (if (some #{string} non-plural-words)
93 | string
94 | (str string "s")))
95 |
96 |
97 | ;; Now we can call pluralise by wrapping it as a partical function. The argument that is the non-plural-words is constant, its the individual elements of animals I want to get out via map. So when map runs it gets an element from the animals collection and adds it to the call to pluralise, along with non-plural-words
98 | (map (partial pluralise non-plural-words) animals)
99 |
100 |
101 | ;; Its like calling (pluralise non-plural-words ,,,) but each time including an element from animals where the ,,, is.
102 |
103 | ;; at first I was getting incorrect output, ["deer" "sheep" "shrimp"], then I realised that it was returning the non-plural-words as string, so the arguements from the partial function were being sent in the wrong order. So I simply changed the order in the pluralise function and it worked.
104 |
105 | ;; I checked this by adding some old-fashioned print statement. There are probably better ways to do that in Clojure though.
106 |
107 | (defn pluralise
108 | "Pluralise a given string value"
109 | [non-plural-words string]
110 | (if (some #{string} non-plural-words)
111 | (do
112 | (println (str string " its true"))
113 | string)
114 | (do
115 | (println (str string " its false"))
116 | (str string "s"))))
117 |
118 |
119 | ;; comp
120 |
121 | ;; Takes a set of functions and returns a fn that is the composition
122 | ;; of those fns. The returned fn takes a variable number of args,
123 | ;; applies the rightmost of fns to the args, the next
124 | ;; fn (right-to-left) to the result, etc.
125 |
126 | ((comp str +) 8 8 8)
127 |
128 | (filter (comp not zero?) [0 1 0 2 0 3 0 4])
129 |
130 |
131 | (defn f1
132 | "append 1 to string x"
133 | [x]
134 | (str x "1"))
135 |
136 |
137 | (defn f2
138 | "append 2 to string x"
139 | [x]
140 | (str x "2"))
141 |
142 |
143 | (defn f3
144 | "append 3 to string x"
145 | [x]
146 | (str x "3"))
147 |
148 |
149 | (f1 "a") ; "a1"
150 |
151 | (def g (comp f3 f2 f1))
152 |
153 | (g "x") ; "x123"
154 |
155 | ;; (g "a" "x")
156 | ;; 1. Unhandled clojure.lang.ArityException
157 | ;; Wrong number of args (2) passed to: user/f1
158 |
159 | ;; because f1 only takes 1 arg
160 |
161 |
162 |
163 | ;; example of apply
164 |
165 | ;; apply just takes all of its args, and feed to the function as multiple args, like unwrap the bracket
166 |
167 | (apply str ["a" "b"]) ; "ab"
168 |
169 | (apply str "1" ["a" "b"]) ; "1ab"
170 |
171 | (apply str "1" "2" ["a" "b"]) ; "12ab"
172 |
173 | ;; here's what str does
174 |
175 | ;; str takes 1 or more args of string, and return a joined string
176 | (str "a" "b") ; "ab"
177 |
178 | ;; can be just 1 arg
179 | (str "a") ; "a"
180 |
181 | ;; if arg is not a string, it's converted to string
182 | (str "a" ["a" "b"]) ; "a[\"a\" \"b\"]"
183 |
184 | (str "a" 1) ; "a1"
185 |
186 |
187 |
188 | ;; Applying a sequence with functions that take individual arguments
189 |
190 | (def my-square [10 8 20 12 4])
191 |
192 |
193 | (defn draw-square
194 | [x y width height corner]
195 | (str "X: " x " Y: " y " Width: " width " Height: " height " Corner: " corner))
196 |
197 |
198 | (apply draw-square my-square)
199 |
200 |
201 | ;; => "X: 10 Y: 8 Width: 20 Height: 12 Corner: 4"
202 |
203 |
204 |
205 | ;;
206 | ;; Misc
207 |
208 | ;; clojure.core/trampoline
209 | ;; (trampoline f args)
210 | ;; call f with args. If it returns a function, call it again (with no args). Repeat until it returns a value that is not a function, return that value.
211 |
212 | ;; clojure.core/constantly
213 | ;; (constantly x)
214 | ;; Returns a function that takes any number of arguments and returns x.
215 |
216 | ;; clojure.core/complement
217 | ;; (complement f)
218 | ;; Takes a fn f and returns a fn that takes the same arguments as f, has the same effects, if any, and returns the opposite truth value.
219 |
220 | ;; clojure.core/memoize
221 | ;; (memoize f)
222 | ;; Returns a memoized version of a referentially transparent function. The memoized version of the function keeps a cache of the mapping from arguments to results and, when calls with the same arguments are repeated often, has higher performance at the expense of higher memory use.
223 |
224 |
225 |
226 | ;; Processing maps problem ???
227 |
228 | ;; you have several maps which have name and id keys. The value of the id becomes the new key and the name becomes the new value.
229 |
230 | [{:name "mike" :id 0} {:name "mike" :id 0} {:name "mike" :id 0}]
231 |
--------------------------------------------------------------------------------
/src/clojure_through_code/abstractions_patterns.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.abstractions-patterns
2 | (:require
3 | [clojure-through-code.database :as database]))
4 |
5 |
6 | ;;
7 | ;;
8 | ;; Functional abstractions
9 | ;;
10 | ;;
11 |
12 | ;; see clojure-through-code/functional-concepts
13 |
14 | ;; - how pure are our functions ?
15 |
16 |
17 | ;;
18 | ;;
19 | ;; Clojure patterns / idioms ?
20 | ;;
21 | ;;
22 |
23 |
24 | ;; Alan J Perlis “It is better to have 100 functions operating on one data structure than to have 10 functions operate on 10 data structures.”
25 |
26 | ;; macros let,
27 |
28 | ;; Components
29 | ;; Stuart Siera's Components
30 | ;; - see duct project for example
31 | ;; Modular - JUXT
32 |
33 | ;; Schema - Prismatic
34 |
35 | ;; Transducers
36 |
37 |
38 | ;;
39 | ;;
40 | ;; OO Design Patterns
41 | ;;
42 | ;;
43 |
44 | ;; Amsterdam Clojure meetup verdict: patterns are a sign of a limited language
45 |
46 | ;; Read "The Joy of Clojure" for a comparison of OO design patterns & functional approach in Clojure - its verdict, different paradigms.
47 |
48 | ;; Concepts and ideas from http://mishadoff.com/blog/clojure-design-patterns/
49 |
50 | ;;
51 | ;; Singleton
52 |
53 | ;; Assuming a list, vector, map or set can be used as a singleton.
54 |
55 | ;; However, if you bind a name using def, that def can be redefined elsewhere. A redefinition typically only takes place during development time, but could also happen at runtime.
56 |
57 | (def singleton-as-value 42)
58 |
59 |
60 | ;; when redefining we afect all functions that use this value via the symbol name.
61 | (def singleton-as-value 47)
62 |
63 |
64 | ;; local bindings over-ride global def values
65 |
66 | (def favourite-number 42)
67 |
68 |
69 | (defn whats-my-favourte-number
70 | [number]
71 | (println "You said your favourite number was" favourite-number)
72 | (let [favourite-number (/ number 2)]
73 | (str "Your real favourite number is " favourite-number)))
74 |
75 |
76 | (whats-my-favourte-number favourite-number)
77 |
78 |
79 | ;; defonce allows us to create a name for a value, assuming its not already been bound.
80 | (defonce singleton-as-value 24)
81 |
82 |
83 | ;; Using the constantly function
84 |
85 | (def the-answer (constantly 42))
86 |
87 | (the-answer "What is 6 times 9")
88 |
89 |
90 | ;; it does not matter what arguments we give to
91 | ;;
92 | ;; Command pattern
93 |
94 | ;; In OO, the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time. This information includes the method name, the object that owns the method and values for the method parameters
95 |
96 | (defn execute
97 | [command & args]
98 | (apply command args))
99 |
100 |
101 | (execute database/login "fey" "force@wakens")
102 | (execute database/logout "fey")
103 |
104 |
105 | ;; Or using a shortcut form
106 | (defn execute
107 | [command]
108 | (command))
109 |
110 |
111 | (execute #(database/login "rey" "force@wakens"))
112 | (execute #(database/logout "rey"))
113 |
114 |
115 | ;; Where it could be useful in Clojure
116 | ;; - persistent storage - passing actions, connection & access details to minimise the code that needs specific database libraries
117 |
118 |
119 |
120 | ;;
121 | ;; Strategy pattern
122 |
123 | ;; enables an algorithm's behavior to be selected at runtime. The strategy pattern defines a family of algorithms, encapsulates each algorithm, and makes the algorithms interchangeable within that family.
124 |
125 | ;; Strategy lets the algorithm vary independently from clients that use it.
126 |
127 | ;; In this example we want to pass in the sorting strategy when we want to get a sorted list of users with subscribers at the top
128 |
129 |
130 | (def users
131 | [{:name "fred" :subscription false}
132 | {:name "john" :subscription true}
133 | {:name "sally" :subscription false}
134 | {:name "zaphod" :subscription true}])
135 |
136 |
137 | ;; forward sort with subscribers last
138 | (sort-by (juxt :subscription :name) users)
139 |
140 |
141 | ;; forward sort by name
142 | (sort-by (juxt :name :subscription) users)
143 |
144 |
145 | ;; forward sort by name
146 | (sort-by :name users)
147 |
148 |
149 | ;; forward sort with subscribers at top
150 | (sort-by (juxt (complement :subscription) :name) users)
151 |
152 | ((complement :subscription) (first users))
153 | ((complement :subscription) (second users))
154 | ((complement :subscription) {})
155 |
156 |
157 | ;; reverse sort of users with subscribers at top
158 | (sort-by (juxt :subscription :name) #(compare %2 %1) users)
159 |
160 |
161 | ;; too high level nonsense....
162 |
163 |
164 |
165 | ;;
166 | ;; State pattern
167 |
168 | ;; hmm... state pattern... its a bit like the stragegy pattern with slightly different encapsulation
169 |
170 |
171 |
172 | ;;
173 | ;;
174 | ;; Patterns I am weary of
175 | ;;
176 | ;;
177 |
178 |
179 | ;;
180 | ;; Visitor pattern
181 |
182 | ;; if a language support multiple dispatch, you don't need Visitor pattern
183 |
184 | (defmulti export
185 | (fn [item format] [(:type item) format]))
186 |
187 |
188 | ;; Message
189 | {:type :message :content "Say what again!"}
190 |
191 |
192 | ;; Activity
193 | {:type :activity :content "Quoting Ezekiel 25:17"}
194 |
195 |
196 | ;; Formats
197 | ;; :pdf, :xml
198 |
199 | ;; (defmethod export [:activity :pdf] [item format]
200 | ;; (exporter/activity->pdf item))
201 |
202 | ;; (defmethod export [:activity :xml] [item format]
203 | ;; (exporter/activity->xml item))
204 |
205 | ;; (defmethod export [:message :pdf] [item format]
206 | ;; (exporter/message->pdf item))
207 |
208 | ;; (defmethod export [:message :xml] [item format]
209 | ;; (exporter/message->xml item))
210 |
211 |
212 | ;; (defmethod export :default [item format]
213 | ;; (throw (IllegalArgumentException. "not supported")))
214 |
215 |
216 | ;; (derive ::pdf ::format)
217 | ;; (derive ::xml ::format)
218 |
219 |
220 | ;; (defmethod export [:activity ::pdf])
221 | ;; (defmethod export [:activity ::xml])
222 | ;; (defmethod export [:activity ::format])
223 |
224 | ;; ;; If some new format (i.e. csv) appears in the system
225 |
226 | ;; (derive ::csv ::format)
227 |
228 |
229 |
230 | ;;
231 | ;; Template pattern
232 |
233 |
234 |
235 | ;; Strategy
236 |
237 | (defn cooley-tukey
238 | [signal])
239 |
240 |
241 | (defn prime-factor
242 | [signal])
243 |
244 |
245 | (defn choose-fft
246 | []
247 | (if relatively-prime? prime-factor cooley-tukey))
248 |
249 |
250 | (defn main
251 | []
252 | (let [signal (get-signal)] ((choose-fft) signal)))
253 |
254 |
255 | ;; Adapter
256 |
257 | (defprotocol BarkingDog
258 | "this is a barking dog"
259 |
260 | (bark
261 | [this]
262 | "dog should bark"))
263 |
264 |
265 | (extend-protocol BarkingDog clojure.lang.IPersistentVector
266 | (bark [v] (conj v "bark!")))
267 |
268 |
269 | (def a-vector [1 2 3 4])
270 |
271 | (bark a-vector) [1 2 3 4 "bark!"]
272 |
273 |
274 | ;; Template Method
275 |
276 | (defn update-account-status
277 | [account-id get-fn status save-fn]
278 | (let [account (get-fn account-id)]
279 | (when (not= status (:status account))
280 | (log/info "Updating status for account:" account-id)
281 | (save-fn (assoc account :status status)))))
282 |
283 |
284 | ;; (defn get-account-from-mysql ,,,)
285 | ;; (defn get-account-from-datomic ,,,…)
286 | ;; (defn get-account-from-http ,,,)
287 |
--------------------------------------------------------------------------------
/src/clojure_through_code/core_async.clj:
--------------------------------------------------------------------------------
1 | ;; Include core.async in the project definition and the namespace it will be used in
2 |
3 | (ns clojure-through-code.core-async
4 | (:require
5 | [clojure.core.async
6 | :refer
7 | [! >!! alts!! chan go put! take! timeout]]))
8 |
9 |
10 | ;;
11 | ;; Manually putting and taking messages from a channel
12 |
13 | ;; Define a name for a channel
14 | ;; If you do not specify the size of the channel it defaults to 1024 messages
15 |
16 | (def manual-channel (chan))
17 |
18 |
19 | ;; use the take! function to listen to the channel.
20 | ;; the arguments include the channel name
21 | ;; and a function that will be used on the message taken from the channel
22 |
23 | (take! manual-channel #(println "Got a value:" %))
24 |
25 |
26 | ;; there is nothing yet on the channel to take, so lets put a message on the channel
27 |
28 | (put! manual-channel 42)
29 |
30 |
31 | ;; => Got a value: 42
32 |
33 | ;; Putting additional values on to the channel will queue up values on that channel
34 |
35 | (put! manual-channel 42 #(println "Putting number on the channel:" %))
36 |
37 | (put! manual-channel 43 #(println "Putting number on the channel:" %))
38 |
39 |
40 | ;; The put! operations return true as a signal that the put operation could be performed, even though the value hasn’t yet been taken.
41 |
42 |
43 | ;; Take a queued up messages from the channel, one at a time
44 | (take! manual-channel #(println % "taken from the channel"))
45 |
46 |
47 | ;; You can also queue up take commands, that will trigger as soon as something is put on the channel
48 |
49 |
50 | ;; Channels can be closed, which will cause the put operations to not succeed
51 |
52 | (close! manual-channel)
53 |
54 |
55 | ;; => nil
56 |
57 | (put! manual-channel 42)
58 |
59 |
60 | ;; => false
61 |
62 |
63 |
64 | ;;
65 | ;; Using Transducers with channels
66 |
67 | ;; Create a channel with a simple transducer.
68 | ;; The transducer increments the value of the message added to the channel.
69 |
70 | ;; A transducer is an expression to transform values, that does not immediately
71 | ;; need those values.
72 |
73 | ;; In this example, map used the inc function to transform the collection of numbers
74 |
75 | ;; (map inc [1 2 3 4])
76 |
77 | ;; To make this a transducer, we simply do not include the value to be transformed
78 | ;; (the collection in this example) in the expression
79 | ;; Our transducer equivalent is (map inc)
80 | ;; and the value to be transformed is passed at run time, when the transducer is called
81 |
82 | (def channel-increment (chan 1 (map inc)))
83 |
84 | (put! channel-increment 41)
85 |
86 | (take! channel-increment #(println % "taken from the channel"))
87 |
88 |
89 | (def channel-odd? (chan 4 (map odd?)))
90 |
91 |
92 | (do
93 | (put! channel-odd? 1)
94 | (put! channel-odd? 2)
95 | (put! channel-odd? 3)
96 | (put! channel-odd? 4))
97 |
98 |
99 | (take! channel-odd? #(println % "taken from the channel"))
100 |
101 |
102 | (def channel-filtered-values (chan 4 (filter odd?)))
103 |
104 |
105 | (do
106 | (put! channel-filtered-values 1)
107 | (put! channel-filtered-values 2)
108 | (put! channel-filtered-values 3)
109 | (put! channel-filtered-values 4))
110 |
111 |
112 | (take! channel-filtered-values #(println % "filtered results from channel"))
113 |
114 |
115 | ;; if we could see whats in the channel we would know when the transducer works...
116 |
117 |
118 | ;;
119 | ;; Processes and Go blocks
120 |
121 | ;; Create a channel as before
122 |
123 | (def channel-for-processes (chan))
124 |
125 |
126 | ;; asynchronous puts and takes from the channel are done in within a go expression
127 |
128 | ;; Use the take function, ! channel-for-processes "Hello core.async, how are you"))
136 |
137 |
138 | ;; Show how to two processes interact
139 |
140 | ;; Listen for a value to be put on the channel
141 | (go
142 | (println [:process-a] "Trying to take from the channel")
143 | (println [:process-a] "Taken value:" (! channel-for-processes 42)
152 | (Thread/sleep 1200)
153 | (println [:process-b] "Put on channel value 42"))
154 |
155 |
156 | ;; Results:
157 | ;; [:process-a] Trying to take from the channel
158 | ;; [:process-b] Preparing to put value onto channel
159 | ;; [:process-a] Taken value: 42
160 | ;; [:process-b] Put on channel value 42
161 |
162 |
163 | ;;
164 | ;; Adding a timeout to the channel
165 |
166 | ;; First define a function to get the current time
167 | (defn current-time
168 | []
169 | (.format
170 | (java.text.SimpleDateFormat. "yyyy-MM-dd'T'hh:mm:ss")
171 | (new java.util.Date)))
172 |
173 |
174 | ;; Could use Java 8 java.time if I could get the formatting working
175 | #_(java.time.LocalDateTime/now)
176 |
177 |
178 | ;; => #object[java.time.LocalDateTime 0x79c6028e "2018-04-02T18:04:42.398"]
179 | #_(java.time.format.DateTimeFormatter "ISO_INSTANT" (java.time.LocalDateTime/now))
180 |
181 |
182 | ;; Print the current time
183 | ;; take from a channel the call to core.async/timeout
184 | ;; wrap it all in a time expression to see how long it all took
185 | (time
186 | (go
187 | (println [:process-a] "In the go block at:" (current-time))
188 | (! channel-stream message)))
219 |
220 |
221 | ;;
222 | ;; Blocking channel in listening mode
223 |
224 | (def channel-buffered (chan 1))
225 |
226 |
227 | (go
228 | (dotimes [message 24]
229 | (>!! channel-buffered message)))
230 |
231 |
232 | (go
233 | (while true
234 | (Thread/sleep 3600)
235 | (println (str (! channel "Hello")))
249 |
250 | (dotimes [i number-of-channels]
251 | (let [[value channel] (alts!! channel-sequence)]
252 | (assert (= "Hello" value))))
253 |
254 | (println "Read" number-of-channels "messages in"
255 | (- (System/currentTimeMillis) begin) "ms"))
256 |
--------------------------------------------------------------------------------
/src/clojure_through_code/10_changing_state.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.10-changing-state)
2 |
3 |
4 | ;;
5 | ;; Changing state in Clojure
6 |
7 | ;; The most common way to manage changing state in Clojure is the atom.
8 |
9 | ;; Essentially an atom is a box that can hold any other clojure data. when you change the atom contents, you are essentially replacing one immutable value with another.
10 |
11 | ;; An atom is a mutable container for any valid data in Clojure. So you can put numbers, strings and collections into an atom.
12 |
13 | ;; Unlike numbers, strings and collections, the contents of an atom changes when you use a function that changes a value or collection.
14 |
15 | ;; However, the value or collection itself is immutable, so does not change. Its the atom that shows you a different value.
16 |
17 |
18 | ;;
19 | ;; Using fictional characters to show atoms in action
20 | ;; (work in progress)
21 |
22 | (def human-characters [])
23 |
24 | (def mutant-characters (atom []))
25 |
26 |
27 | (defn add-mutant
28 | [mutant]
29 | (swap! mutant-characters conj mutant))
30 |
31 |
32 | (add-mutant "Black Widow")
33 | (add-mutant "Hulk")
34 | (add-mutant "Ariel")
35 |
36 |
37 | ;;
38 | ;; Gambling Game example of state
39 |
40 |
41 | ;; The examples in this section relate to an online gambling game in which players compete against each other and against the tame itself.
42 | ;; The hands that the players are dealt and the money which they have in their account are mutable in these examples
43 |
44 | ;; Note: You could make the card hand that each player holds immutable using a new persistent data structure for each game and only make the deck of cards they are drawing from immutable for a particular round of games. Actually you could make both immutable.
45 |
46 | ;; The only value that seems to really benefit from state is the current amount of a players account, however, even that could be immutable, so long as changes are written to a persistent storage
47 |
48 |
49 | ;; Imagine we have a list of players and we want to add a new player.
50 | ;; We have a table and we only have two places at the table as a maximum.
51 | ;; Create a join-game function that will swap in a player
52 |
53 |
54 | ;; To create a player table I would define a vector to hold the player name.
55 | ;; As we are going to change state, we want to do it in a safe way,
56 | ;; so we define that vector with an atom
57 |
58 | (def players (atom []))
59 |
60 |
61 | ;; We also add a :validator as a condition to ensure we dont put more than
62 | ;; 2 players into the game
63 |
64 | (def players (atom [] :validator #(<= (count %) 2)))
65 |
66 |
67 | ;; the second definition of players point the var name to something new
68 | ;; we havent changed what players was, it just points to something diferent
69 |
70 | ;; Add players
71 | (swap! players conj "Player One")
72 | (deref players)
73 | @players
74 |
75 | (swap! players conj "Player Two")
76 |
77 | (reset! players ["Player One"])
78 | (reset! players [])
79 |
80 |
81 | ;; Add players by name
82 | (defn join-game
83 | [name]
84 | (swap! players conj name))
85 |
86 |
87 | (join-game "Rachel")
88 | (join-game "Harriet")
89 | (join-game "Terry") ; cant add a third name due to the :validator condition on the atom
90 | ;; (join-game "Sally" "Sam") ;; too many parameters
91 |
92 | @players
93 |
94 |
95 | ;; (defn reset-game
96 | ;; (reset! players [nil]))
97 | ;; reset! should be a vector
98 |
99 | (reset! players [])
100 |
101 |
102 | ;; Atom and assoc with multiple keyword updates
103 | ;;
104 |
105 |
106 | (def accounts
107 | {:betty 280
108 | :jenny 460
109 | :sammy 100
110 | :dealer 100000})
111 |
112 |
113 | (update accounts
114 | :betty (dec 50)
115 | :jenny (dec 75)
116 | :dealer (dec 100))
117 |
118 |
119 | ;; Clojure ref types
120 | ;;
121 |
122 |
123 | (def dick-account (ref 500))
124 | (def toms-account (ref 500))
125 | (def betty-account (ref 500))
126 |
127 | @betty-account
128 |
129 |
130 | (defn credit-table
131 | [player-account]
132 | (dosync
133 | (alter player-account - 100)
134 | (alter game-account + 100)))
135 |
136 |
137 | (defn add-to-table
138 | [name]
139 | (swap! players conj name))
140 |
141 |
142 | (defn join-game
143 | [name account]
144 | ;; (if (< account 100 )
145 | ;; (println "You're broke")
146 | (credit-table account)
147 | (add-to-table name))
148 |
149 |
150 | (join-game "Betty" betty-account)
151 |
152 |
153 | ;; NOTE: If a map is used in the atom that has all the relevant information that needs changing you may not need to use the following ref example
154 |
155 |
156 |
157 | ;;
158 | ;; Using ref to manage multiple state changes
159 |
160 |
161 | @player-ref
162 | @toms-account
163 |
164 | @betty-account
165 |
166 |
167 | (def game-world
168 | (atom {:players [{:id 0 :name "harriet" :account 100}]
169 | :game-account 0}))
170 |
171 |
172 | #_(swap! update-in game-world
173 | :game-account add-player
174 |
175 | )
176 |
177 |
178 | (def players-ref (ref [] :validator #(<= (count %) 2)))
179 | (def game-account (ref 1000))
180 | (def harriet-account (ref 0))
181 |
182 |
183 | (defn join-game-safely
184 | [name player-account game-account]
185 | (dosync
186 | (alter players-ref conj name)
187 | (alter player-account + 100)
188 | (alter game-account - 100)))
189 |
190 |
191 | (join-game-safely "Harriet" harriet-account game-account)
192 |
193 | @harriet-account
194 |
195 | @game-account
196 |
197 | @players-ref
198 |
199 |
200 | ;; (alter game-account 1000)
201 |
202 | (join-game-safely "Tom" toms-account game-account)
203 |
204 |
205 | ;; Refs are for Coordinated Synchronous access to "Many Identities".
206 | ;; Atoms are for Uncoordinated synchronous access to a single Identity.
207 |
208 | ;; Coordinated access is used when two Identities need to be changes together, the classic example being moving money from one bank account to another, it needs to either move completely or not at all.
209 |
210 | ;; Uncoordinated access is used when only one Identity needs to update, this is a very common case.
211 |
212 |
213 | ;; Agents are for Uncoordinated asynchronous access to a single Identity.
214 | ;; Vars are for thread local isolated identities with a shared default value.
215 |
216 | ;; Synchronous access is where the call expects to wait until all the identities are settled before continuing.
217 |
218 | ;; Asynchronous access is "fire and forget" and let the Identity reach its new state in its own time.
219 |
220 | ;; (join-game-safely "Betty" betty-account)
221 |
222 |
223 | ;; adding accounts example
224 | ;; (+ @account1 @account2)
225 |
226 |
227 | ;; Agents
228 | ;; (def flashes (agent {:green 0 :red 0 :blue 0}))
229 | ;; (send flashes update-in [:red] inc)
230 |
231 | ;; Create an agent called cashier (that has not state)
232 | ;; (send-off cashier str "Debit X with £" stake)
233 |
234 | ;; (io! (str "Debit John with £" stake))
235 |
236 |
237 | ;;
238 | ;; Futures
239 | ;; Can you do this in another thread
240 | ;; wrapper over the java futures
241 | ;; -- for when you are calculating in one thread and want to hand it over to another,
242 | ;; -- or create pipelines between threads
243 | (future (+ 2 2))
244 | (type (future (+ 2 2)))
245 | (def f (future (+ 2 2)))
246 | (realized? f)
247 | @f
248 |
249 | (def my-promise (promise))
250 | (realized? my-promise)
251 | (deliver my-promise 42)
252 | (realized? my-promise)
253 |
254 | (def my-delay (delay (+ 2 2)))
255 | (realized? my-delay)
256 | @my-delay
257 | (realized? my-delay)
258 |
--------------------------------------------------------------------------------
/src/clojure_through_code/undefine.clj:
--------------------------------------------------------------------------------
1 | ;;
2 | ;; Removing evaluated vars from a running REPL
3 | ;;
4 | ;; Keep the REPL clean of stale vars due to code changes
5 | ;;
6 |
7 |
8 | (ns clojure-through-code.undefine)
9 |
10 |
11 | (comment
12 |
13 | ;; Are any aliases currently defined
14 | (ns-aliases *ns*)
15 | ;; => {}
16 |
17 | ;; Require a neamespace
18 | (require '[practicalli.service :as service])
19 |
20 | ;; The service alias should now be in the current nammespace
21 | (ns-aliases *ns*)
22 | ;; => {service #namespace[practicalli.service]}
23 |
24 | (ns-unalias *ns* 'service)
25 | ;; => nil
26 |
27 | (ns-aliases *ns*)
28 | ;; => {}
29 |
30 | (require '[clojure.tools.namespace.repl :refer [refresh]])
31 |
32 | (refresh)
33 | ;; => #error {
34 | ;; :cause "Could not locate clojure/core/async__init.class, clojure/core/async.clj or clojure/core/async.cljc on classpath."
35 | ;; :via
36 | ;; [{:type clojure.lang.Compiler$CompilerException
37 | ;; :message "Syntax error compiling at (clojure_through_code/core_async.clj:1:1)."
38 | ;; :data #:clojure.error{:phase :compile-syntax-check, :line 1, :column 1, :source "clojure_through_code/core_async.clj"}
39 | ;; :at [clojure.lang.Compiler load "Compiler.java" 7652]}
40 | ;; {:type java.io.FileNotFoundException
41 | ;; :message "Could not locate clojure/core/async__init.class, clojure/core/async.clj or clojure/core/async.cljc on classpath."
42 | ;; :at [clojure.lang.RT load "RT.java" 462]}]
43 | ;; :trace
44 | ;; [[clojure.lang.RT load "RT.java" 462]
45 | ;; [clojure.lang.RT load "RT.java" 424]
46 | ;; [clojure.core$load$fn__6856 invoke "core.clj" 6115]
47 | ;; [clojure.core$load invokeStatic "core.clj" 6114]
48 | ;; [clojure.core$load doInvoke "core.clj" 6098]
49 | ;; [clojure.lang.RestFn invoke "RestFn.java" 408]
50 | ;; [clojure.core$load_one invokeStatic "core.clj" 5897]
51 | ;; [clojure.core$load_one invoke "core.clj" 5892]
52 | ;; [clojure.core$load_lib$fn__6796 invoke "core.clj" 5937]
53 | ;; [clojure.core$load_lib invokeStatic "core.clj" 5936]
54 | ;; [clojure.core$load_lib doInvoke "core.clj" 5917]
55 | ;; [clojure.lang.RestFn applyTo "RestFn.java" 142]
56 | ;; [clojure.core$apply invokeStatic "core.clj" 669]
57 | ;; [clojure.core$load_libs invokeStatic "core.clj" 5974]
58 | ;; [clojure.core$load_libs doInvoke "core.clj" 5958]
59 | ;; [clojure.lang.RestFn applyTo "RestFn.java" 137]
60 | ;; [clojure.core$apply invokeStatic "core.clj" 669]
61 | ;; [clojure.core$require invokeStatic "core.clj" 5996]
62 | ;; [clojure.core$require doInvoke "core.clj" 5996]
63 | ;; [clojure.lang.RestFn invoke "RestFn.java" 408]
64 | ;; [clojure_through_code.core_async$eval7357$loading__6737__auto____7358 invoke "core_async.clj" 3]
65 | ;; [clojure_through_code.core_async$eval7357 invokeStatic "core_async.clj" 3]
66 | ;; [clojure_through_code.core_async$eval7357 invoke "core_async.clj" 3]
67 | ;; [clojure.lang.Compiler eval "Compiler.java" 7181]
68 | ;; [clojure.lang.Compiler eval "Compiler.java" 7170]
69 | ;; [clojure.lang.Compiler load "Compiler.java" 7640]
70 | ;; [clojure.lang.RT loadResourceScript "RT.java" 381]
71 | ;; [clojure.lang.RT loadResourceScript "RT.java" 372]
72 | ;; [clojure.lang.RT load "RT.java" 459]
73 | ;; [clojure.lang.RT load "RT.java" 424]
74 | ;; [clojure.core$load$fn__6856 invoke "core.clj" 6115]
75 | ;; [clojure.core$load invokeStatic "core.clj" 6114]
76 | ;; [clojure.core$load doInvoke "core.clj" 6098]
77 | ;; [clojure.lang.RestFn invoke "RestFn.java" 408]
78 | ;; [clojure.core$load_one invokeStatic "core.clj" 5897]
79 | ;; [clojure.core$load_one invoke "core.clj" 5892]
80 | ;; [clojure.core$load_lib$fn__6796 invoke "core.clj" 5937]
81 | ;; [clojure.core$load_lib invokeStatic "core.clj" 5936]
82 | ;; [clojure.core$load_lib doInvoke "core.clj" 5917]
83 | ;; [clojure.lang.RestFn applyTo "RestFn.java" 142]
84 | ;; [clojure.core$apply invokeStatic "core.clj" 669]
85 | ;; [clojure.core$load_libs invokeStatic "core.clj" 5974]
86 | ;; [clojure.core$load_libs doInvoke "core.clj" 5958]
87 | ;; [clojure.lang.RestFn applyTo "RestFn.java" 137]
88 | ;; [clojure.core$apply invokeStatic "core.clj" 669]
89 | ;; [clojure.core$require invokeStatic "core.clj" 5996]
90 | ;; [clojure.core$require doInvoke "core.clj" 5996]
91 | ;; [clojure.lang.RestFn invoke "RestFn.java" 421]
92 | ;; [clojure.tools.namespace.reload$track_reload_one invokeStatic "reload.clj" 35]
93 | ;; [clojure.tools.namespace.reload$track_reload_one invoke "reload.clj" 21]
94 | ;; [clojure.tools.namespace.reload$track_reload invokeStatic "reload.clj" 52]
95 | ;; [clojure.tools.namespace.reload$track_reload invoke "reload.clj" 43]
96 | ;; [clojure.lang.AFn applyToHelper "AFn.java" 154]
97 | ;; [clojure.lang.AFn applyTo "AFn.java" 144]
98 | ;; [clojure.lang.Var alterRoot "Var.java" 308]
99 | ;; [clojure.core$alter_var_root invokeStatic "core.clj" 5499]
100 | ;; [clojure.core$alter_var_root doInvoke "core.clj" 5494]
101 | ;; [clojure.lang.RestFn invoke "RestFn.java" 425]
102 | ;; [clojure.tools.namespace.repl$do_refresh invokeStatic "repl.clj" 94]
103 | ;; [clojure.tools.namespace.repl$do_refresh invoke "repl.clj" 83]
104 | ;; [clojure.tools.namespace.repl$refresh invokeStatic "repl.clj" 145]
105 | ;; [clojure.tools.namespace.repl$refresh doInvoke "repl.clj" 128]
106 | ;; [clojure.lang.RestFn invoke "RestFn.java" 397]
107 | ;; [clojure_through_code.undefine$eval7353 invokeStatic "NO_SOURCE_FILE" 32]
108 | ;; [clojure_through_code.undefine$eval7353 invoke "NO_SOURCE_FILE" 32]
109 | ;; [clojure.lang.Compiler eval "Compiler.java" 7181]
110 | ;; [clojure.lang.Compiler eval "Compiler.java" 7136]
111 | ;; [clojure.core$eval invokeStatic "core.clj" 3202]
112 | ;; [clojure.core$eval invoke "core.clj" 3198]
113 | ;; [nrepl.middleware.interruptible_eval$evaluate$fn__1245$fn__1246 invoke "interruptible_eval.clj" 87]
114 | ;; [clojure.lang.AFn applyToHelper "AFn.java" 152]
115 | ;; [clojure.lang.AFn applyTo "AFn.java" 144]
116 | ;; [clojure.core$apply invokeStatic "core.clj" 667]
117 | ;; [clojure.core$with_bindings_STAR_ invokeStatic "core.clj" 1977]
118 | ;; [clojure.core$with_bindings_STAR_ doInvoke "core.clj" 1977]
119 | ;; [clojure.lang.RestFn invoke "RestFn.java" 425]
120 | ;; [nrepl.middleware.interruptible_eval$evaluate$fn__1245 invoke "interruptible_eval.clj" 87]
121 | ;; [clojure.main$repl$read_eval_print__9110$fn__9113 invoke "main.clj" 437]
122 | ;; [clojure.main$repl$read_eval_print__9110 invoke "main.clj" 437]
123 | ;; [clojure.main$repl$fn__9119 invoke "main.clj" 458]
124 | ;; [clojure.main$repl invokeStatic "main.clj" 458]
125 | ;; [clojure.main$repl doInvoke "main.clj" 368]
126 | ;; [clojure.lang.RestFn invoke "RestFn.java" 1523]
127 | ;; [nrepl.middleware.interruptible_eval$evaluate invokeStatic "interruptible_eval.clj" 84]
128 | ;; [nrepl.middleware.interruptible_eval$evaluate invoke "interruptible_eval.clj" 56]
129 | ;; [nrepl.middleware.interruptible_eval$interruptible_eval$fn__1278$fn__1282 invoke "interruptible_eval.clj" 152]
130 | ;; [clojure.lang.AFn run "AFn.java" 22]
131 | ;; [nrepl.middleware.session$session_exec$main_loop__1348$fn__1352 invoke "session.clj" 218]
132 | ;; [nrepl.middleware.session$session_exec$main_loop__1348 invoke "session.clj" 217]
133 | ;; [clojure.lang.AFn run "AFn.java" 22]
134 | ;; [java.lang.Thread run "Thread.java" 833]]}
135 |
136 | #_())
137 |
138 |
139 | ;; ;; You are having a problem loading a redefined namespace:
140 | ;; user=> (load "src/clj/com/tizra/layout_expander.clj")
141 | ;; #
142 |
143 | ;; ;; ns-unalias to the rescue!
144 | ;; user=> (ns-unalias (find-ns 'com.tizra.layout-expander) 'xml)
145 | ;; nil
146 |
147 | ;; user=> (load "src/clj/com/tizra/layout_expander.clj")
148 | ;; #'com.tizra.layout-expander/junk
149 |
--------------------------------------------------------------------------------
/src/clojure_through_code/xx-control-flow.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.xx-control-flow)
2 |
3 | (def salary 60000)
4 |
5 |
6 | ;; (if test then else)
7 | ;; to put that in rough english: if something is true, then do this, else do the other thing
8 | ;; The if function evaluates the test returning the value in the "then" position
9 | ;; and the value in the "else" position if false.
10 |
11 | ;; As if is a function, it must always return a value, even if that value is nil.
12 |
13 | ;; Test if the salary is over 50,000 - if it is then you are a fat cat.
14 | (if (> salary 50000) "You are a fat cat" "You are the salt of the earth")
15 |
16 | (if (> 50000 salary) "Fat cat" "Salt")
17 |
18 |
19 | ;; What happens with no values after the test ?
20 | ;; (if (> salary 40000))
21 |
22 | ;; Or just one value after the test ?
23 |
24 | ;; (if (> salary 50000) "Yay, you have a job")
25 |
26 | ;; (if (> salary 80000) "Yay, you have a job")
27 |
28 |
29 | ;; Using when instead of if
30 | ;; You can use the when function if you have multiple things you want to do if true,
31 | ;; and you know there will never be an else condition
32 |
33 | (when true
34 | (println "hello, i am a side effect, please destroy me")
35 | "return some value")
36 |
37 |
38 | (when (> 3 2)
39 | (println "3 is greater than 2")
40 | "Higher")
41 |
42 |
43 | ;;
44 | ;; for
45 |
46 | ;; process the items in a collection with destructing
47 |
48 | (for [[i j k]
49 | [[0 1 2] [0 2 3] [1 0 4] [2 1 4] [3 2 4] [0 3 4]]]
50 | (println "i: " i "j: " j " and k: " k))
51 |
52 |
53 | ;;
54 | ;; while
55 |
56 | ;; another example of a more procedural control flow
57 |
58 |
59 | ;; create a counter using a mutable counter
60 | (def counter (atom 10))
61 |
62 |
63 | ;; While the counter is positive (is a number greater than zero), print out the current value of the counter.
64 | (while (pos? @counter)
65 | (do
66 | (println @counter)
67 | (swap! counter dec)))
68 |
69 |
70 | ;;
71 | ;;
72 | ;; If you have multiple things to do, you can still use if with a little help from do or the threading macro
73 |
74 | (defn lower-public-spending
75 | []
76 | (println "No more silk ties for ministers"))
77 |
78 |
79 | (defn raise-public-spending
80 | []
81 | (println "Time for a new duck castle, including a moat"))
82 |
83 |
84 | (if (> salary 50000)
85 | (do
86 | (raise-public-spending)
87 | "Thank you for being a fat cat")
88 | (do
89 | (lower-public-spending)
90 | "You are the salt of the earth"))
91 |
92 |
93 | ;; You can also use the threading macro, especially if the expressions take each others result as an argument
94 |
95 | ;; > **comment** The threading macros are a way to read Clojure from left to right, rather than from inside out
96 |
97 | ;; a really simple example of the two threading macros
98 |
99 | ;; Thread-first macro
100 | ;; The result of the first evaluation is past as the first argument to the next function
101 | (->
102 | 7
103 | (/ 22))
104 |
105 |
106 | ;; Thread-last macro
107 | ;; The result of the first evaluation is past as the last argument to the next function
108 | (->>
109 | 7
110 | (/ 22))
111 |
112 |
113 | ;; Tip: if you find the threading macro hard to read,
114 | ;; you can place ,,, where the result of the evaluation would be in the following function
115 | ;; as commas are treated as whitespace
116 |
117 | (->
118 | 7
119 | (/ ,,, 22))
120 |
121 |
122 | (->>
123 | 7
124 | (/ 22 ,,,))
125 |
126 |
127 | ;; Its common to formatting the threading macro with each expression on a new line
128 |
129 |
130 |
131 | ;; Threading macro examples
132 |
133 |
134 |
135 | ;; (defn math-steps [x]
136 | ;; (-> x
137 | ;; (* 5)
138 | ;; (+ 3)))
139 |
140 |
141 | ;; (->> (seq accounts)
142 | ;; (filter #(= (:type %) 'savings))
143 | ;; (map :balance)
144 | ;; (apply +))
145 |
146 | ;; expands to
147 |
148 | ;; (apply +
149 | ;; (map :balance
150 | ;; (filter #(= (:type %) 'savings)
151 | ;; (seq accounts))))
152 |
153 |
154 | ;; You can see for yourself:
155 |
156 | ;; (macroexpand `(-> 42 inc dec))
157 |
158 |
159 | ;; (doto person
160 | ;; (.setFName "Joe")
161 | ;; (.setLName "Bob")
162 | ;; (.setHeight [6 2]))
163 |
164 |
165 |
166 | ;; Quite some time back I wrote a blog post on the Thrush Combinator implementation in Scala. Just for those who missed reading it, Thrush is one of the permuting combinators from Raymond Smullyan's To Mock a Mockingbird. The book is a fascinating read where the author teaches combinatorial logic using examples of birds from an enchanted forest. In case you've not yet read it, please do yourself a favor and get it from your favorite book store.
167 |
168 | ;; A Thrush is defined by the following condition: Txy = yx. Thrush reverses the order of evaluation. In our context, it's not really an essential programming tool. But if you're someone who takes special care to make your code readable to the human interface, the technique sometimes comes in quite handy.
169 |
170 | ;; Recently I came across Thrush in Clojure. You don't have to implement anything - it's there for you in the Clojure library implemented as a macro ..
171 |
172 | ;; Conside this simple example of bank accounts where we represent an account as a Map in Clojure ..
173 |
174 | ;; (def acc-1 {:no 101 :name "debasish" :type 'savings :balance 100})
175 | ;; (def acc-2 {:no 102 :name "john p." :type 'checking :balance 200})
176 |
177 |
178 | ;; We have a list of accounts and we need to find all savings accounts and compute the sum of their current balances .. well not too difficult in Clojure ..
179 |
180 | ;; (defn savings-balance
181 | ;; [& accounts]
182 | ;; (apply +
183 | ;; (map :balance
184 | ;; (filter #(= (:type %) 'savings)
185 | ;; (seq accounts)))))
186 |
187 |
188 | ;; To a programmer familiar with the concepts of functional programming, it's quite clear what the above function does. Let's read it out for all of us .. From a list of accounts, filter the ones with type as savings, get their balances and report the sum of them. That was easy .. but did you notice that we read it inside out from the implementation, which btw is a 4 level nested function ?
189 |
190 | ;; Enter Thrush (Threading macro)
191 |
192 | ;; Being a permuting combinator, Thrush enables us to position the functions outside in, in the exact sequence that the human mind interprets the problem. In our Scala version we had to implement something custom .. with Clojure, it comes with the standard library .. have a look ..
193 |
194 | ;; (defn savings-balance
195 | ;; [& accounts]
196 | ;; (->> (seq accounts)
197 | ;; (filter #(= (:type %) 'savings))
198 | ;; (map :balance)
199 | ;; (apply +)))
200 |
201 |
202 | ;; ->> is implemented as a macro in Clojure that does the following :
203 |
204 | ;; threads the first form (seq accounts) as the last argument of the second form (the filter), which makes (seq accounts) the last argument of filter
205 | ;; then makes the entire filter form, including the newly added argument the last argument of the map
206 | ;; .. and so on for all the forms present in the argument list. Effectively the resulting form that we see during runtime is our previous version using nested functions. The Thrush combinator only dresses it up nicely for the human eyes synchronizing the thought process with the visual implementation of the logic. And all this at no extra runtime cost! Macros FTW :)
207 |
208 | ;; ->> has a related cousin ->, which is same as ->>, but only threads the forms as the second argument of the next form instead of the last. These macros implement Thrush in Clojure. Idiomatic Clojure code is concise and readable and using a proper ubiquitous language of the domain, makes a very good DSL. Think about using Thrush when you feel that reordering the forms will make your API look more intuitive to the user.
209 |
210 | ;; Thrush also helps you implement the Decorator pattern in a very cool and concise way. In my upcoming book, DSLs In Action I discuss these techniques in the context of designing DSLs in Clojure.
211 |
212 |
213 |
214 | ;; Idea
215 | ;; A searchable website of examples in clojure by topic, function name, concept, or any combination of them
216 |
--------------------------------------------------------------------------------
/src/clojure_through_code/fifteen_minutes.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.fifteen-minutes)
2 |
3 |
4 | ;; Simple Values - Hello World and Maths
5 |
6 | ;; str will create a string out of all its arguments
7 |
8 | (str "Hello Clojure World," " " "happy " 11 " th birthday in 2018")
9 |
10 |
11 | ;; Math uses function calls rather than operators
12 | ;; parentheses () define the order of evaluation
13 | ;; parentheses ensure there is no ambiguity in the order of calculation
14 |
15 | (+ 1 1) ; => 2
16 | (- 24 4 10) ; => 10
17 | (* 1 2 (+ 1 2) (* 2 2)) ; => 24
18 |
19 | ;; A ratio is a value in Clojure, helping to maintain precision
20 | (/ 22 7) ; => 22/7
21 |
22 | ;; A ratio can be coerced into a number type
23 | (/ 22 7.0) ; => 3.142857142857143
24 |
25 | ;; Equality is comparing values with the = function
26 | ;; assignment is not done with the = function
27 | (= 1 1) ; => true
28 | (= 2 1) ; => false
29 | (odd? 1) ; => true
30 |
31 | ;; You need not for logic, too
32 | (not true) ; => false
33 | (not= 1 2 3) ; => true
34 |
35 |
36 | ;; Collections & Sequences
37 | ;;
38 |
39 | ;; A list would be written as just (1 2 3), but we have to quote
40 | ;; it to stop the reader thinking it's a function.
41 | ;; Also, (list 1 2 3) is the same as '(1 2 3)
42 |
43 | ;; Both lists and vectors are collections:
44 | (coll? '(1 2 3)) ; => true
45 | (coll? [1 2 3]) ; => true
46 |
47 | ;; Only lists are seqs.
48 | (seq? '(1 2 3)) ; => true
49 | (seq? [1 2 3]) ; => false
50 |
51 | ;; Seqs are an interface for logical lists, which can be lazy.
52 | ;; "Lazy" means that a seq can define an infinite series, like so:
53 | (range 4) ; => (0 1 2 3)
54 | (range) ; => (0 1 2 3 4 ...) (an infinite series)
55 | (take 4 (range)) ; (0 1 2 3)
56 |
57 | ;; Use cons to add an item to the beginning of a list or vector
58 | (cons 4 [1 2 3]) ; => (4 1 2 3)
59 | (cons 4 '(1 2 3)) ; => (4 1 2 3)
60 |
61 | ;; Use conj to add an item to the beginning of a list,
62 | ;; or the end of a vector
63 | (conj [1 2 3] 4) ; => [1 2 3 4]
64 | (conj '(1 2 3) 4) ; => (4 1 2 3)
65 |
66 | ;; Use concat to add lists or vectors together
67 | (concat [1 2] '(3 4)) ; => (1 2 3 4)
68 |
69 | ;; Use filter, map to interact with collections
70 | (map inc [1 2 3]) ; => (2 3 4)
71 | (filter even? [1 2 3]) ; => (2)
72 |
73 | ;; Use reduce to reduce them
74 | (reduce + [1 2 3 4])
75 |
76 |
77 | ;; = (+ (+ (+ 1 2) 3) 4)
78 | ;; => 10
79 |
80 | ;; Reduce can take an initial-value argument too
81 | (reduce conj [] '(3 2 1))
82 |
83 |
84 | ;; = (conj (conj (conj [] 3) 2) 1)
85 | ;; => [3 2 1]
86 |
87 | ;; Functions
88 | ;;
89 |
90 | ;; Use fn to create new functions. A function always returns
91 | ;; its last statement.
92 | (fn [] "Hello World")
93 |
94 |
95 | ;; => #function[clojure-through-code.fifteen-minutes/eval12498/fn--12499]
96 |
97 | ;; (You need extra parens to call it)
98 | ((fn [] "Hello World")) ; => "Hello World"
99 |
100 | ;; Give a name to values using the def function
101 | (def clojure-developer-salary 100000)
102 |
103 |
104 | ;; => #'clojure-through-code.fifteen-minutes/clojure-developer-salary
105 |
106 | ;; Use the name in other Clojure code
107 | (str "Clojure developers could earn up to " clojure-developer-salary)
108 |
109 |
110 | ;; => "Clojure developers could earn up to 100000"
111 |
112 | ;; Define a name for function so you can call it elsewhere in your code
113 | (def hello-world (fn [] "Hello World"))
114 | (hello-world) ; => "Hello World"
115 |
116 | ;; You can shorten this syntax using the defn function (macro)
117 | (defn hello-world
118 | []
119 | "Hello World")
120 |
121 |
122 | ;; The [] is the list of arguments for the function.
123 | ;; There can be zero or more arguments
124 | (defn hello
125 | [name]
126 | (str "Hello " name))
127 |
128 |
129 | (hello "Steve") ; => "Hello Steve"
130 |
131 |
132 | ;; #() is a shorthand for defining a functions,
133 | ;; most useful inline
134 | (def hello-terse #(str "Hello " %1))
135 | (hello-terse "Jenny") ; => "Hello Jenny"
136 |
137 | ;; You can have multi-variadic functions, useful when you have defaults
138 | (defn hello3
139 | ([] "Hello World")
140 | ([name] (str "Hello " name)))
141 |
142 |
143 | (hello3 "Jake") ; => "Hello Jake"
144 | (hello3) ; => "Hello World"
145 |
146 | ;; Functions can pack extra arguments up in a seq for you
147 | (defn count-args
148 | [& args]
149 | (str "You passed " (count args) " args: " args))
150 |
151 |
152 | (count-args 1 2 3) ; => "You passed 3 args: (1 2 3)"
153 |
154 | ;; You can mix regular and packed arguments
155 | (defn hello-count
156 | [name & args]
157 | (str "Hello " name ", you passed " (count args) " extra args"))
158 |
159 |
160 | (hello-count "Finn" 1 2 3)
161 |
162 |
163 | ;; => "Hello Finn, you passed 3 extra args"
164 |
165 | ;; More arguments can be captured using the & and a name
166 | ;; In the hello-advanced we capture the mandatory name and address
167 | ;; Anything-else is checked to see if its empty and if so, a standard messages is added
168 | ;; If anything-else has values, they are added to the string instead.
169 | (defn hello-advanced
170 | [name address & anything-else]
171 | (str "Hello " name
172 | ", I see you live at " address
173 | (if (nil? anything-else)
174 | ". That is all."
175 | (str "and you also do " (clojure.string/join ", " anything-else)))))
176 |
177 |
178 | ;; => #'clojure-through-code.fifteen-minutes/hello-advanced
179 |
180 | (hello-advanced "John Stevenson" "7 Paradise street")
181 |
182 |
183 | ;; => "Hello John Stevenson, I see you live at 7 Paradise street. That is all."
184 |
185 | (hello-advanced "John Stevenson" "7 Paradise street" "cycling" "swimming")
186 |
187 |
188 | ;; => "Hello John Stevenson, I see you live at 7 Paradise streetand you also do cycling, swimming"
189 |
190 |
191 |
192 | ;; Hashmaps
193 | ;;
194 |
195 | (class {:a 1 :b 2 :c 3}) ; => clojure.lang.PersistentArrayMap
196 |
197 | ;; Keywords are like strings with some efficiency bonuses
198 | (class :a) ; => clojure.lang.Keyword
199 |
200 | ;; Maps can use any type as a key, but usually keywords are best
201 | (def string-keys-map (hash-map "a" 1, "b" 2, "c" 3))
202 | string-keys-map ; => {"a" 1, "b" 2, "c" 3}
203 |
204 | (def keyword-keys-map (hash-map :a 1 :b 2 :c 3))
205 | keyword-keys-map ; => {:a 1, :c 3, :b 2} (order is not guaranteed)
206 |
207 | ;; Getting values from maps using keys
208 | (get keymap :c)
209 |
210 |
211 | ;; Maps can be called just like a function, with a key as the argument
212 | ;; This is a short-cut to the get function and useful inside inline functions
213 | (string-keys-map "a") ; => 1
214 | (keyword-keys-map :a) ; => 1
215 |
216 | ;; A Keyword key can also be used as a function that gets its associated value from the map
217 | (:b keyword-keys-map) ; => 2
218 |
219 | ;; Retrieving a non-present value returns nil
220 | (string-keys-map "d") ; => nil
221 |
222 | ;; Use assoc to add new keys to hash-maps
223 | (assoc keyword-keys-map :d 4) ; => {:a 1, :b 2, :c 3, :d 4}
224 |
225 | ;; But remember, clojure types are immutable!
226 | keyword-keys-map ; => {:a 1, :b 2, :c 3}
227 |
228 | ;; Use dissoc to remove keys
229 | (dissoc keymap :a :b) ; => {:c 3}
230 |
231 | ;; Sets
232 | ;;
233 |
234 | (class #{1 2 3}) ; => clojure.lang.PersistentHashSet
235 | (set [1 2 3 1 2 3 3 2 1 3 2 1]) ; => #{1 2 3}
236 |
237 | ;; Add a member with conj
238 | (conj #{1 2 3} 4) ; => #{1 2 3 4}
239 |
240 | ;; Remove one with disj
241 | (disj #{1 2 3} 1) ; => #{2 3}
242 |
243 | ;; Test for existence by using the set as a function:
244 | (#{1 2 3} 1) ; => 1
245 | (#{1 2 3} 4) ; => nil
246 |
247 | ;; There are more functions in the clojure.sets namespace.
248 |
249 | ;; Useful forms
250 | ;;
251 |
252 | ;; Logic constructs in clojure are just macros, and look like
253 | ;; everything else
254 | (if false "a" "b") ; => "b"
255 | (if false "a") ; => nil
256 |
257 | ;; Use let to create temporary bindings
258 | (let [a 1 b 2]
259 | (> a b)) ; => false
260 |
261 | ;; Group statements together with do
262 | (do
263 | (print "Hello")
264 | "World") ; => "World" (prints "Hello")
265 |
266 | ;; Functions have an implicit do
267 | (defn print-and-say-hello
268 | [name]
269 | (print "Saying hello to " name)
270 | (str "Hello " name))
271 |
272 |
273 | (print-and-say-hello "Jeff") ; => "Hello Jeff" (prints "Saying hello to Jeff")
274 |
275 | ;; So does let
276 | (let [name "Jenny"]
277 | (print "Saying hello to " name)
278 | (str "Hello " name)) ; => "Hello Jenny" (prints "Saying hello to Jenny")
279 |
280 | ;; Libraries
281 | ;;
282 |
283 | ;; Use "use" to get all functions from the module
284 | (use 'clojure.set)
285 |
286 |
287 | ;; Now we can use set operations
288 | (intersection #{1 2 3} #{2 3 4}) ; => #{2 3}
289 | (difference #{1 2 3} #{2 3 4}) ; => #{1}
290 |
291 | ;; You can choose a subset of functions to import, too
292 | (use '[clojure.set :only [intersection]])
293 |
294 |
295 | ;; Use require to import a module
296 | (require 'clojure.string)
297 |
298 |
299 | ;; Use / to call functions from a module
300 | (clojure.string/blank? "") ; => true
301 |
302 | ;; You can give a module a shorter name on import
303 | (require '[clojure.string :as str])
304 | (str/replace "This is a test." #"[a-o]" str/upper-case) ; => "THIs Is A tEst."
305 | ;; (#"" denotes a regular expression literal)
306 |
307 | ;; You can use require (and use, but don't) from a namespace using :require.
308 | ;; You don't need to quote your modules if you do it this way.
309 | (ns test
310 | (:require
311 | [clojure.set :as set]
312 | [clojure.string :as str]))
313 |
314 |
315 | ;; Types underlying Clojure
316 | ;;
317 |
318 | ;; Types are inferred by Clojure so mostly not specified
319 | ;; Interop with the host platform (i.e. Java) may benefit from explicit type coercion
320 |
321 | ;; Clojure uses Java's object types for booleans, strings and numbers as these are immutable.
322 | ;; Use `class` or `type` to inspect them.
323 | (class 1) ; Integer literals are java.lang.Long by default
324 | (class 1.); Float literals are java.lang.Double
325 | (class ""); Strings always double-quoted, and are java.lang.String
326 | (class false) ; Booleans are java.lang.Boolean
327 | (class nil); The "null" value is called nil
328 |
329 | ;; If you want to create a literal list of data, use ' to make a "symbol"
330 | '(+ 1 2) ; => (+ 1 2)
331 |
332 | ;; You can eval symbols.
333 | (eval '(+ 1 2)) ; => 3
334 |
335 | ;; Vectors and Lists are java classes too!
336 | (class [1 2 3]); => clojure.lang.PersistentVector
337 | (class '(1 2 3)); => clojure.lang.PersistentList
338 |
339 |
340 |
341 | ;; Java
342 | ;;
343 |
344 | ;; Java has a huge and useful standard library, so
345 | ;; you'll want to learn how to get at it.
346 |
347 | ;; Use import to load a java module
348 | (import java.util.Date)
349 |
350 |
351 | ;; You can import from an ns too.
352 | (ns test
353 | (:import
354 | (java.util
355 | Calendar
356 | Date)))
357 |
358 |
359 | ;; Use the class name with a "." at the end to make a new instance
360 | (Date.) ;
361 |
362 | ;; Use . to call methods. Or, use the ".method" shortcut
363 | (. (Date.) getTime) ;
364 | (.getTime (Date.)) ; exactly the same thing.
365 |
366 | ;; Use / to call static methods
367 | (System/currentTimeMillis) ; (system is always present)
368 |
369 | ;; Use doto to make dealing with (mutable) classes more tolerable
370 | (import java.util.Calendar)
371 |
372 |
373 | (doto (Calendar/getInstance)
374 | (.set 2000 1 1 0 0 0)
375 | .getTime) ; => A Date. set to 2000-01-01 00:00:00
376 |
--------------------------------------------------------------------------------
/src/clojure_through_code/02_data_structures.clj:
--------------------------------------------------------------------------------
1 | ;;
2 | ;; Creating data structures in Clojure
3 |
4 | (ns clojure-through-code.02-data-structures)
5 |
6 |
7 | ;; Clojure has several data structures as part of the language,
8 | ;; list (a linked list),
9 | ;; vector (indexed access like an array),
10 | ;; map (key / Value pairs, usually using clojure :keyword type for the keys,
11 | ;; set (unique elements, not ,ordered by default).
12 | ;; These data structures are typically used rather than defining your own types
13 | ;; Thise 4 data structures are extremely efficient immutable data structures.
14 |
15 | ;; You can also consider strings as collections to and use them in similar ways to the other built in collections.
16 |
17 |
18 | ;;
19 | ;; Strings
20 |
21 |
22 | ;; Strings act as a collection of characters, so you can use the functions typically used for other collections - first, second, rest. last.
23 |
24 | (first "Clojure")
25 |
26 |
27 | ;; => \C
28 |
29 | (second "Clojure")
30 |
31 |
32 | ;; => \l
33 |
34 | (rest "Clojure")
35 |
36 |
37 | ;; => (\l \o \j \u \r \e)
38 |
39 | ;; The rest function returns a collection of the remaining characters after the first one. This collection is in a list. If you wish to return the rest of the characters as a string instead, you can apply the str function over the result of calling rest on the string.
40 |
41 | (apply str (rest "Clojure"))
42 |
43 |
44 | ;; => "lojure"
45 |
46 | (last "Clojure")
47 |
48 |
49 | ;; => \e
50 |
51 | ;; The get function can also be used to return values at a specific position in the string (index). Like arrays (vectors in Clojure) strings are indexed from 0 onwards. So the first character in the string is at position 0.
52 |
53 | (get "Clojure" 3)
54 |
55 |
56 | ;; => \j
57 |
58 |
59 | ;; Note: Clojure also has built in regular expressions which is useful for finding strings, sub-strings and filtering text.
60 | ;; See the section on regular expressions (TODO)
61 |
62 | ;;
63 | ;; Clojure persistent data structures
64 |
65 |
66 | ;; list ()
67 | ;; a general set of elements with a sequential lookup time
68 |
69 | ;; you can use the list function to create a new list
70 | (list 1 2 3 4)
71 | (list -1 -0.234 0 1.3 8/5 3.1415926)
72 | (list "cat" "dog" "rabit" "fish" 12 22/7)
73 |
74 |
75 | ;; you can use any "types" in your list or any other Clojure collection
76 | (list :cat :dog :rabit :fish)
77 |
78 |
79 | ;; you can mix types because Clojure is dynamic and it will work it out later,
80 | ;; you can even have functions as elements, because functions always return a value
81 | (list :cat 1 "fish" 22/7 (str "fish" "n" "chips"))
82 |
83 |
84 | ;; further examples of mixing types
85 |
86 | (list)
87 | (list 1 2 3 4)
88 |
89 | (def five 5) ; bind the name five to the value 5, clojure's equivalent to assignment
90 |
91 | (list 1 2 "three" [4] five '(6 7 8 9))
92 |
93 |
94 | ;; (1 2 3 4) ;; This list causes an error when evaluated
95 |
96 | '(1 2 3 4)
97 |
98 | (quote (1 2 3 4))
99 |
100 |
101 | ;; one unique thing about lists is that the first element is always evaluated as a function call,
102 | ;; with the remaining elements as arguments.
103 |
104 | ;; So, defining a list just using () will cause an error
105 |
106 | ;; This list definition will fail, unless you have defined a function called 1
107 | ;; ( 1 2 3 4)
108 |
109 | ;; There is a special function called quote that tells Clojure to just treat the
110 | ;; list as data.
111 |
112 | (quote (1 2 3 4))
113 |
114 |
115 | ;; This syntax is actually more code to type than (list 1 2 3 4),
116 | ;; so there is a shortcut for the quote function using the ' character
117 |
118 | '(1 2 3 4)
119 | '(-1 -0.234 0 1.3 8/5 3.1415926)
120 | '("cat" "dog" "rabit" "fish")
121 | '(:cat :dog :rabit :fish)
122 | '(:cat 1 "fish" 22/7 (str "fish" "n" "chips"))
123 |
124 |
125 | ;; The quote shortcut is uses where ever you have a list that you want to treat just as data.
126 | ;; Another example is when you are including functions from other namespaces
127 | ;; (ns my-namespace.core
128 | ;; use 'my-namespace.library)
129 |
130 |
131 | ;; Duplicate elements in a list ?
132 |
133 | (list 1 2 3 4 1)
134 | (list "one" "two" "one")
135 | (list :fred :barney :fred)
136 |
137 |
138 | ;; Sets #{}
139 |
140 | ;; #{1 2 3 4 1}
141 | ;; duplicate key error
142 |
143 | (set [1 2 3 4 1])
144 |
145 |
146 | ;; only returns unique set from the collection
147 | (sorted-set 1 4 0 2 9 3 5 3 0 2 7 6 5 5 3 8)
148 |
149 |
150 | ;; Vector []
151 | ;; A vector looks like an array and is better for random access.
152 | ;; A vector has an index to look up elements at a specific point, speeding up random access
153 | ;; Vectors and maps are the most common data structures use to hold data in Clojure
154 |
155 | ;; you can use the vector function to create a new vector
156 | (vector 1 2 3 4)
157 |
158 |
159 | ;; Usually you just use the [] notation to create a vector to help keep your code readable
160 | [1 2 3 4]
161 | [1 2.4 3.1435893 11/4 5.0 6 7]
162 | [:cat :dog :rabit :fish]
163 | [:cat 1 "fish" 22/7 (str "fish" "n" "chips")]
164 | []
165 |
166 |
167 | ;; You can also put other data structures in vectors, in this example a list is an element of the vector
168 | [1 2 3 '(4 5 6)]
169 |
170 |
171 | ;; remember we defined five earlier in the code
172 | [1 2 3 4 (list five)]
173 |
174 |
175 | ;; Duplicate elements ?
176 | [1 2 3 4 1]
177 |
178 |
179 | ;; Map {}
180 | ;; A key / value pair data structure
181 | ;; keys are usually defined as a :keyword, although they can be anything
182 |
183 | ;; Typicall a :keyword is used for a the key in a map, with the value being
184 | ;; a string, number or another keyword
185 | {:key "value"}
186 | (:key 42)
187 | {:key symbol}
188 | {:key :value}
189 | {"key" "value"}
190 |
191 |
192 | ;; As with other collections, you can use anything as a key or a value,
193 | ;; they are all values underneath.
194 | {:key 42}
195 | {"key" "value"}
196 |
197 |
198 | {:a 1 :b 2 :c 3}
199 |
200 |
201 | ;; Define a simple shopping list, including how many of each items you want to buy
202 | (def shopping-list
203 | {"cat food" 10
204 | "soya milk" 4
205 | "bread" 1
206 | "cheese" 2})
207 |
208 |
209 | ;; ASCII code generator
210 | ;;
211 | ;; Defining ascii codes in a collection
212 | ;; Create an ascii code generator for an alphabet
213 |
214 | ;; Simple definition of ascii codes using a map.
215 | (def ascii-codes
216 | {65 "a" 66 "b" 67 "c"})
217 |
218 |
219 | (get ascii-codes 65)
220 |
221 |
222 | ;; => "a"
223 |
224 |
225 | ;; We want to convert an alphabet, which could be defined as follows
226 | (def english-alphabet
227 | ["a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"])
228 |
229 |
230 | ;; Or if we are give an alphabet in a single string, then we can create a more suitable colllection
231 | (clojure.string/split "a b c d e f g h i j k l m n o p q r s t u v w x y z" #" ")
232 |
233 |
234 | ;; => ["a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"]
235 |
236 |
237 | ;; Lets generate a hash-map of ascii codes for a given alphabet.
238 | ;; Lower case a is ascii code 65, so that will be our starting code value.
239 | ;; Using the map function we can take an element from each collection,
240 | ;; the alphabet and a generated range of numbers, and combine them in pairs.
241 | ;; The pairs are placed into a hashmap
242 | ;; The generated ascii codes are a generate range between the start code and the start code plus the number of letters in the alphabet.
243 | (defn generate-ascii-codes
244 | [alphabet code-start]
245 | (map hash-map
246 | alphabet
247 | (range 65 (+ code-start (count english-alphabet)))))
248 |
249 |
250 | (generate-ascii-codes english-alphabet 65)
251 |
252 |
253 | ;; Its also quite common to have maps made up of other maps
254 |
255 | ;; Here is an example of a map made up of a :keyword as the key and
256 | ;; a map as the value. The map representing the value is itself
257 | ;; defined with :keywords and strings
258 |
259 | {:starwars {:characters {:jedi ["Luke Skywalker"
260 | "Obiwan Kenobi"]
261 | :sith ["Darth Vader"
262 | "Darth Sideous"]
263 | :droids ["C3P0"
264 | "R2D2"
265 | "BB-8"]}
266 | :ships {:rebel-alliance ["Millenium Falcon"
267 | "X-wing figher"]
268 | :imperial-empire ["Intergalactic Cruser"
269 | "Destroyer"
270 | "Im just making these up now"]}}}
271 |
272 |
273 | ;; => {:starwars {:characters {:jedi ["Luke Skywalker" "Obiwan Kenobi"], :sith ["Darth Vader" "Darth Sideous"], :droids ["C3P0" "R2D2" "BB-8"]}, :ships {:rebel-alliance ["Millenium Falcon" "X-wing figher"], :imperial-empire ["Intergalactic Cruser" "Destroyer" "Im just making these up now"]}}}
274 |
275 |
276 |
277 | ;; Individual starwars characters can be defined using a map of maps
278 | {:luke {:fullname "Luke Skywarker" :skill "Targeting Swamp Rats"}
279 | :vader {:fullname "Darth Vader" :skill "Crank phone calls"}
280 | :jarjar {:fullname "JarJar Binks" :skill "Upsetting a generation of fans"}}
281 |
282 |
283 | ;; To make the starwars character information easier to use, lets give the collection of characters a name using the def function
284 |
285 | (def starwars-characters
286 | {:luke {:fullname "Luke Skywarker" :skill "Targeting Swamp Rats"}
287 | :vader {:fullname "Darth Vader" :skill "Crank phone calls"}
288 | :jarjar {:fullname "JarJar Binks" :skill "Upsetting a generation of fans"}})
289 |
290 |
291 | (get starwars-characters :luke)
292 | (get (get starwars-characters :luke) :skill)
293 | (get-in starwars-characters [:luke :fullname])
294 |
295 | (:skill (:luke starwars-characters))
296 |
297 |
298 | ;; updating maps with assoc-in
299 | (assoc-in starwars-characters [:vader :skill] "The Dark Side of the Force")
300 |
301 |
302 | ;; => {:luke {:fullname "Luke Skywarker", :skill "Targeting Swamp Rats"}, :vader {:fullname "Darth Vader", :skill "The Dark Side of the Force"}, :jarjar {:fullname "JarJar Binks", :skill "Upsetting a generation of fans"}}
303 |
304 | (update)
305 |
306 | (def alphabet-soup {:a 1 :b 2 :c 3})
307 |
308 | (assoc alphabet-soup :d 4)
309 |
310 |
311 | ;; => {:a 1, :b 2, :c 3, :d 4}
312 |
313 | (update alphabet-soup :a inc)
314 |
315 |
316 | ;; => {:a 2, :b 2, :c 3}
317 |
318 |
319 | ;; Now we can refer to the characters using keywords
320 |
321 | ;; Using the get function we return all the informatoin about Luke
322 | (get starwars-characters :luke)
323 |
324 |
325 | ;; By wrapping the get function around our first, we can get a specific
326 | ;; piece of information about Luke
327 | (get (get starwars-characters :luke) :fullname)
328 |
329 |
330 | ;; There is also the get-in function that makes the syntax a little easier to read
331 | (get-in starwars-characters [:luke :fullname])
332 | (get-in starwars-characters [:vader :fullname])
333 |
334 |
335 | ;; Or you can get really Clojurey by just talking to the map directly
336 | (starwars-characters :luke)
337 | (:fullname (:luke starwars-characters))
338 | (:skill (:luke starwars-characters))
339 |
340 | (starwars-characters :vader)
341 | (:skill (:vader starwars-characters))
342 | (:fullname (:vader starwars-characters))
343 |
344 |
345 | ;; And finally we can also use the threading macro to minimise our code further
346 |
347 | (-> starwars-characters
348 | :luke)
349 |
350 |
351 | (-> starwars-characters
352 | :luke
353 | :fullname)
354 |
355 |
356 | (-> starwars-characters
357 | :luke
358 | :skill)
359 |
360 |
361 | ;; More on Destructuring
362 | ;; https://gist.github.com/john2x/e1dca953548bfdfb9844
363 |
364 |
365 | ;; Duplicate keys in a map are not allowed, so the following maps...
366 |
367 | ;; {"fish" "battered" "chips" "fried" "fish" "battered and fried"}
368 | ;; {:fish "battered" :chips "fried" :fish "battered & fried"}
369 |
370 | ;; ...throw dupicate key errors
371 |
372 | ;; Duplicate values are okay though
373 | {"fish" "battered" "chips" "fried" "cod" "fried"}
374 |
375 |
376 | ;; What is the difference between Collections & Sequences
377 | ;;
378 |
379 | ;; Vectors and Lists are java classes too!
380 | (class [1 2 3])
381 | (class '(1 2 3))
382 |
383 |
384 | ;; A list would be written as just (1 2 3), but we have to quote
385 | ;; it to stop the reader thinking it's a function.
386 | ;; Also, (list 1 2 3) is the same as '(1 2 3)
387 |
388 | ;; Both lists and vectors are collections:
389 | (coll? '(1 2 3)) ; => true
390 | (coll? [1 2 3]) ; => true
391 |
392 | ;; Only lists are seqs.
393 | (seq? '(1 2 3)) ; => true
394 | (seq? [1 2 3]) ; => false
395 |
396 |
397 |
398 | ;;
399 | ;; Generating Data
400 |
401 | (take 20 (cycle ["foo" "bar"]))
402 |
403 |
404 | ;; Conjoining collections together
405 |
406 | (conj {:a 1} '(:b 2))
407 |
408 | (cons {:a 1} (map inc '(1 2)))
409 |
410 | (conj {:a 1} (map inc '(1 2)))
411 |
412 |
413 | (conj)
414 |
415 |
416 | ;; => []
417 |
418 | ;; why do we get a class cast error when we try to conj a list into a map,
419 | ;; however we can conj a map and a vector
420 | ;; situation occurred whilst show and tell of 4clojure exercises at February Thoughtworks dojo
421 |
--------------------------------------------------------------------------------
/src/clojure_through_code/03-using-data-structures.clj:
--------------------------------------------------------------------------------
1 | ;;
2 | ;; Using Clojure data structures
3 |
4 | (ns clojure-through-code.03-using-data-structures)
5 |
6 |
7 | ;; Defining things is easy in Clojure
8 |
9 | ;; Defining things is as simple as giving a name to a value, of course in Clojure the evaluation of a function is also a value.
10 |
11 | (def person "Jane Doe")
12 |
13 |
14 | ;; Names are of course case sensitive, so Person is not the same as person
15 | (def Person "James Doh")
16 |
17 |
18 | ;; Clojure uses dynamic typing, this means its trivial to mix and match different kinds of data.
19 | ;; Here we are defining a name for a vector, which contains numbers, a string and name of another def.
20 | (def my-data [1 2 3 "frog" person])
21 |
22 | my-data
23 |
24 |
25 | ;; You can also dynamically re-define a name to points to a different value
26 | (def my-data [1 2 3 4 5 "frog" person])
27 |
28 |
29 | ;; the original value that defined my-data remains unchanged (its immutable), so anything using that value remains unaffected. Essentially we are re-mapping my-data to a new value.
30 |
31 |
32 | ;; Lets define a name to point to a list of numbers
33 | (def my-list '(1 2 3))
34 |
35 |
36 | ;; We are returned that list of numbers when we evaluate the name
37 |
38 | my-list
39 |
40 |
41 | ;; We can use the cons function to add a number to our list,
42 | ;; however because lists are immutable, rather than changing the original list, a new one is returned
43 | ;; So if we want to keep on refering to our "changed" list, we need to give it a name
44 | (def my-list-updated (cons 4 my-list))
45 |
46 |
47 | ;; As you can see we have not changed the original list
48 | my-list
49 |
50 |
51 | ;; The new list does have the change though.
52 | my-list-updated
53 |
54 |
55 | ;; As names in Clojure are dynamic, we can of course point the original name to the new list
56 | (def my-list (cons 5 my-list))
57 |
58 |
59 | ;; so now when we evaluate the original name, we get the updated list
60 | my-list
61 |
62 |
63 | ;;
64 | ;; Practising with lists
65 |
66 |
67 | ;; Create a simple collection of developer event names
68 |
69 | ;; first lets use a list of strings at it seems the easiest straight forward
70 |
71 | (def developer-events-strings '("Devoxx UK" "Devoxx France" "Devoxx" "Hack the Tower"))
72 |
73 |
74 | ;; > Remember, in Clojure the first element of a list is treated as a function call, so we have used the quote ' character to tell Cloojure to also treat the first element as data. We could of course have used the list function to define our list `(def developer-events-strings2 (list "Devoxx UK" "Devoxx France" "Devoxx" "Hack the Tower"))`
75 |
76 | developer-events-strings
77 |
78 |
79 | ;; We can get just the first element in our collection of developer events
80 | (first developer-events-strings)
81 |
82 |
83 | ;; using a Clojure Vector data structure seems a little more Clojurey, especially when the vector contains keywords. Think of a Vector as an Array, although in Clojure it is again immutable in the same way a list is.
84 |
85 | (def developer-events-vector
86 | [:devoxxuk :devoxxfr :devoxx :hackthetower])
87 |
88 |
89 | ;; Lets create a slightly more involved data structure,
90 | ;; holding more data around each developer events.
91 | ;; This data structure is just a map, with each key being
92 | ;; the unique name of the developer event.
93 | ;; The details of each event (the value to go with the
94 | ;; event name key) is itself a map as there are several
95 | ;; pieces of data associated with each event name.
96 | ;; So we have a map where each value is itself a map.
97 | (def dev-event-details
98 | {:devoxxuk {:URL "http://jaxlondon.co.uk"
99 | :event-type "Conference"
100 | :number-of-attendees 700
101 | :call-for-papers true}
102 | :hackthetower {:URL "http://hackthetower.co.uk"
103 | :event-type "hackday"
104 | :number-of-attendees 60
105 | :call-for-papers false}})
106 |
107 |
108 | ;; lets call the data structre and see what it evaluates too, it should not be a surprise
109 | dev-event-details
110 |
111 |
112 | ;; We can ask for the value of a specific key, and just that value is returned
113 | (dev-event-details :devoxxuk)
114 |
115 |
116 | ;; In our example, the value returned from the :devoxxuk key is also a map,
117 | ;; so we can ask for a specific part of that map value by again using its key
118 | (:URL (dev-event-details :devoxxuk))
119 |
120 |
121 | ;; Lets define a simple data structure for stocks data
122 | ;; This is a vector of maps, as there will be one or more company stocks
123 | ;; to track. Each map represents the stock information for a company.
124 |
125 | (def portfolio
126 | [{:ticker "CRM" :lastTrade 233.12 :open 230.66}
127 | {:ticker "AAPL" :lastTrade 203.25 :open 204.50}
128 | {:ticker "MSFT" :lastTrade 29.12 :open 29.08}
129 | {:ticker "ORCL" :lastTrade 21.90 :open 21.83}])
130 |
131 |
132 | ;; We can get the value of the whole data structure by refering to it by name
133 | portfolio
134 |
135 |
136 | ;; As the data structure is a vector (ie. array like) then we can ask for a specific element by its position in the array using the nth function
137 |
138 | ;; Lets get the map that is the first element (again as a vector has array-like properties, the first element is referenced by zero)
139 | (nth portfolio 0)
140 |
141 |
142 | ;; The vector has 4 elements, so we can access the last element by referencing the vector using 3
143 | (nth portfolio 3)
144 |
145 |
146 | ;; As portfolio is a collection (list, vector, map, set), also known as a sequence, then we can use a number of functions that provide common ways of getting data from a data structure
147 |
148 | (first portfolio)
149 | (rest portfolio)
150 | (last portfolio)
151 |
152 |
153 | ;; To get specific information about the share in our portfolio, we can also use the keywords to get specific information
154 |
155 | (get (second portfolio) :ticker)
156 |
157 |
158 | ;; => "AAPL"
159 |
160 | (:ticker (first portfolio))
161 |
162 |
163 | ;; => "CRM"
164 |
165 | (map :ticker portfolio)
166 |
167 |
168 | ;; => ("CRM" "AAPL" "MSFT" "ORCL")
169 |
170 | ;; return the portfolio in a vector rather than a list using mapv function
171 | (mapv :ticker portfolio)
172 |
173 |
174 | ;; => ["CRM" "AAPL" "MSFT" "ORCL"]
175 |
176 |
177 | ;;
178 | ;; Map reduce
179 |
180 | (def my-numbers [1 2 3 4 5])
181 |
182 | (map even? my-numbers)
183 |
184 |
185 | ;; => (false true false true false)
186 |
187 | ;; Reduce to see if all the numbers are even, otherwise return false.
188 | ;; or is a macro so is quoted so its evaluated only when called by reduce
189 | (reduce 'or (map even? my-numbers))
190 |
191 |
192 | ;; => false
193 |
194 |
195 | ;;
196 | ;; Map reduce sandwich
197 |
198 | ;; http://www.datasciencecentral.com/forum/topics/what-is-map-reduce
199 |
200 | ;; As several functions
201 |
202 | (defn slice
203 | [item]
204 | (str "sliced " item))
205 |
206 |
207 | ;; => #'clojure-through-code.03-using-data-structures/slice
208 | ;; => #'clojure-through-code.03-using-data-structures/slice
209 |
210 | (def prepared-ingredience
211 | (map slice ["bread" "cucumber" "pepper" "tomato" "lettuce" "onion"]))
212 |
213 |
214 | ;; => #'clojure-through-code.03-using-data-structures/sandwich
215 |
216 | (def make-sandwich
217 | (reduce str (interpose ", " prepared-ingredience)))
218 |
219 |
220 | (str "I have a tasty sandwich made with " make-sandwich)
221 |
222 |
223 | ;; => "I have a tasty sandwich made with sliced bread, sliced cucumber, sliced pepper, sliced tomato, sliced lettuce, sliced onion"
224 |
225 |
226 | ;; Or as one function
227 | (str "I have a tasty sandwich made with "
228 | (reduce str (interpose ", " (map #(str "sliced " %) ["bread" "cucumber" "pepper" "tomato" "lettuce" "onion"]))))
229 |
230 |
231 | ;; Or using the threading macro
232 |
233 | (->> ["bread" "cucumber" "pepper" "tomato" "lettuce" "onion"]
234 | (map #(str "sliced " %) ,,,)
235 | (interpose ", " ,,,)
236 | (reduce str ,,,)
237 | (str "I have a tasty sandwich made with " ,,,))
238 |
239 |
240 | ;; => "I have a tasty sandwich made with sliced bread, sliced cucumber, sliced pepper, sliced tomato, sliced lettuce, sliced onion"
241 |
242 |
243 | ;;
244 | ;; Map and filter
245 |
246 | ;; A collection of accounts, each account being a map with id and balance values
247 | (def accounts
248 | [{:id "fred" :balance 10}
249 | {:id "betty" :balance 20}
250 | {:id "wilma" :balance 5}])
251 |
252 |
253 | ;; Get the balance for each account and add them together
254 | (apply + (map :balance accounts))
255 |
256 |
257 | ;; We could also use reduce insdead of apply
258 | (reduce + (map :balance accounts))
259 |
260 |
261 | ;;
262 | ;; Evaluating things you have defined
263 |
264 | ;; person
265 | ;; developer-events
266 | ;; (dev-event-details)
267 |
268 | (dev-event-details :devoxxuk)
269 |
270 | (first portfolio)
271 | (next portfolio)
272 |
273 |
274 | ;; First and next are termed as sequence functions in Clojure,
275 | ;; unlike other lisps, you can use first and next on other data structures too
276 |
277 | (first person)
278 | (rest person)
279 |
280 |
281 | ;; these functions return the strings as a set of characters,
282 | ;; as characters are the elements of a string
283 |
284 | ;; returning the first element as a string is easy, simply by rapping the
285 | ;; str function around the (first person) function
286 | (str (first person))
287 |
288 |
289 | ;; So how do we return the rest of the string as a string ?
290 | (str (rest person))
291 | (map str (rest person))
292 | (str (map str (rest person)))
293 | (apply str (rest person))
294 |
295 |
296 | ;; You can get the value of this map
297 |
298 | (def luke {:name "Luke Skywarker" :skill "Targeting Swamp Rats"})
299 | (def darth {:name "Darth Vader" :skill "Crank phone calls"})
300 | (def jarjar {:name "JarJar Binks" :skill "Upsetting a generation of fans"})
301 |
302 | (get luke :skill)
303 | (first luke)
304 |
305 |
306 | ;; Getting the keys or values in a map using keywords
307 |
308 | ;; When you use a keyword, eg. :name, as the key in a map, then that keyword can be used as a function call on the map to return its associated value.
309 | ;; Maps can also act as functions too.
310 |
311 | (:name luke)
312 | (luke :name)
313 |
314 |
315 | ;; There are also functions that will give you all the keys of a map and all the values of that map
316 | (keys luke)
317 | (vals luke)
318 |
319 |
320 | ;;
321 | ;; Set #{}
322 | ;; a unique set of elements
323 |
324 | (#{:a :b :c} :c)
325 | (#{:a :b :c} :z)
326 |
327 |
328 | ;; You can pull out data from a Vector
329 | ([1 2 3] 1)
330 |
331 |
332 | ;; ([1 2 3] 1 2) ;; wrong number of arguments, vectors behaving as a function expect one parameter
333 |
334 | ;; ((1 2 3) 1) ;; you cant treat lists in the same way, there is another approach - assoc
335 |
336 |
337 | ;; and there are lots of functions that work on data structures
338 |
339 | (def evil-empire #{"Palpatine" "Darth Vader" "Boba Fett" "Darth Tyranus"})
340 |
341 | (contains? evil-empire "Darth Vader")
342 |
343 |
344 | ;;
345 | ;; Scope
346 |
347 | ;; All def names are publicly available via their namespace.
348 | ;; As def values are immutable, then keeping things private is of less concern than languages built around Object Oriented design.
349 |
350 | ;; Private definitions syntax can be used to limit the access to def names to the namespace they are declared in.
351 |
352 | ;; To limit the scope of a def, add the :private true metadata key value pair.
353 |
354 | (def ^{:private true} some-var :value)
355 |
356 |
357 | ;; or
358 | (def ^:private some-var :value)
359 |
360 |
361 | ;; The second form is syntax sugar for the first one.
362 |
363 |
364 | ;; You can define a macro for def-
365 | (defmacro def-
366 | [item value]
367 | `(def ^{:private true} ~item ~value))
368 |
369 |
370 | ;; You can then use this macro as follows:
371 |
372 | (def- private-definition "This is only accessible in the namespace")
373 |
374 |
375 | ;;
376 | ;; Be Lazy and get more done
377 |
378 | ;; Seqs are an interface for logical lists, which can be lazy.
379 | ;; "Lazy" means that a seq can define an infinite series, like so:
380 | (range 4)
381 |
382 | (range) ; => (0 1 2 3 4 ...) (an infinite series)
383 |
384 | ;; So we dont blow up our memory, just get the values we want
385 | (take 4 (range)) ; (0 1 2 3)
386 |
387 | ;; Clojure (and Lisps in general) tend to evaluate at the last possible moment
388 |
389 | ;; Use cons to add an item to the beginning of a list or vector
390 | (cons 4 [1 2 3]) ; => (4 1 2 3)
391 | (cons 4 '(1 2 3)) ; => (4 1 2 3)
392 |
393 | ;; Use conj to add an item to the beginning of a list,
394 | ;; or the end of a vector
395 | (conj [1 2 3] 4) ; => [1 2 3 4]
396 | (conj '(1 2 3) 4) ; => (4 1 2 3)
397 |
398 |
399 | ;; Showing how changing data structures does not change the original data structure
400 | ;; Lets define a name for a data structure
401 | (def name1 [1 2 3 4])
402 |
403 |
404 | ;; when we evaluate that name we get the original data we set
405 | name1
406 |
407 |
408 | ;; Now we use a function called conj to adds (conjoin) another number to our data structure
409 | (conj name1 5)
410 |
411 |
412 | ;; This returns a new value without changing the original data structre
413 | name1
414 |
415 |
416 | ;; We cant change the original data structure, it is immutable. Once it is set it cant be changed.
417 | ;; However, if we give a name to the resultl of changing the original data structure, we can refer to that new data structure
418 | (def name2 (conj name1 5))
419 |
420 |
421 | ;; Now name2 is the new data structure, but name1 remains unchanged
422 | name2
423 | name1
424 |
425 |
426 | ;; So we cannot change the data structure, however we can achieve something that looks like we have changed it
427 | ;; We can re-assign the original name to the result of changing the original data structure
428 | (def name2 (conj name1 5))
429 |
430 |
431 | ;; Now name1 and name2 are the same result
432 | name2
433 | name1
434 |
435 |
436 | ;; Analogy (Chris Ford)
437 | ;; You have the number 2. If you add 1 to 2, what value is the number 2?
438 | ;; The number 2 is still 2 no mater that you add 1 to it, however, you get the value 3 in return
439 |
440 |
441 | ;; Use concat to add lists or vectors together
442 | (concat [1 2] '(3 4)) ; => (1 2 3 4)
443 |
444 | ;; Use filter, map to interact with collections
445 | (map inc [1 2 3]) ; => (2 3 4)
446 | (filter even? [1 2 3]) ; => (2)
447 |
448 | ;; Use reduce to reduce them
449 | (reduce + [1 2 3 4])
450 |
451 |
452 | ;; = (+ (+ (+ 1 2) 3) 4)
453 | ;; => 10
454 |
455 | ;; Reduce can take an initial-value argument too
456 | (reduce conj [] '(3 2 1))
457 |
458 |
459 | ;; = (conj (conj (conj [] 3) 2) 1)
460 | ;; => [3 2 1]
461 |
462 |
463 |
464 | ;; Playing with data structures
465 |
466 | ;; Destructuring
467 |
468 | (let [[a b c & d :as e] [1 2 3 4 5 6 7]]
469 | [a b c d e])
470 |
471 |
472 | (let [[[x1 y1] [x2 y2]] [[1 2] [3 4]]]
473 | [x1 y1 x2 y2])
474 |
475 |
476 | ;; with strings
477 | (let [[a b & c :as str] "asdjhhfdas"]
478 | [a b c str])
479 |
480 |
481 | ;; with maps
482 | (let [{a :a, b :b, c :c, :as m :or {a 2 b 3}} {:a 5 :c 6}]
483 | [a b c m])
484 |
485 |
486 | ;; printing out data structures
487 |
488 | (def data-structure-vector-of-vectors [[1 2 3] [4 5 6] [7 8 9]])
489 |
490 |
491 | (defn- map-over-vector-of-vectors
492 | []
493 | (map println data-structure-vector-of-vectors))
494 |
495 |
496 | (comp println map-over-vector-of-vectors)
497 |
498 |
499 | ;; It is often the case that you will want to bind same-named symbols to the map keys. The :keys directive allows you to avoid the redundancy:
500 |
501 | (def my-map {:fred "freddy" :ethel "ethanol" :lucy "goosey"})
502 |
503 |
504 | (let [{fred :fred ethel :ethel lucy :lucy} my-map]
505 | [fred ethel lucy])
506 |
507 |
508 | ;; can be written:
509 |
510 | (let [{:keys [fred ethel lucy]} my-map]
511 | [fred ethel lucy])
512 |
513 |
514 | ;; As of Clojure 1.6, you can also use prefixed map keys in the map destructuring form:
515 |
516 | (let [m {:x/a 1, :y/b 2}
517 | {:keys [x/a y/b]} m]
518 | (+ a b))
519 |
520 |
521 | ;; As shown above, in the case of using prefixed keys, the bound symbol name will be the same as the right-hand side of the prefixed key. You can also use auto-resolved keyword forms in the :keys directive:
522 |
523 | (let [m {::x 42}
524 | {:keys [::x]} m]
525 | x)
526 |
527 |
528 | ;; Pretty Printing Clojure data stuctures
529 | ;;
530 |
531 |
532 | ;; Pretty print hash-maps
533 |
534 | (require '[clojure.pprint])
535 |
536 |
537 | (clojure.pprint/pprint
538 | {:account-id 232443344 :account-name "Jenny Jetpack" :balance 9999 :last-update "2021-12-12" :credit-score :aa})
539 |
540 |
541 | {:account-id 232443344,
542 | :account-name "Jenny Jetpack",
543 | :balance 9999,
544 | :last-update "2021-12-12",
545 | :credit-score :aa}
546 |
547 |
548 | ;; Showing data structures as a table
549 |
550 | (clojure.pprint/print-table
551 | [{:location "Scotland" :total-cases 42826 :total-mortality 9202}
552 | {:location "Wales" :total-cases 50876 :total-mortality 1202}
553 | {:location "England" :total-cases 5440876 :total-mortality 200202}])
554 |
555 |
556 | ;; | :location | :total-cases | :total-mortality |
557 | ;; |-----------+--------------+------------------|
558 | ;; | Scotland | 42826 | 9202 |
559 | ;; | Wales | 50876 | 1202 |
560 | ;; | England | 5440876 | 200202 |
561 |
--------------------------------------------------------------------------------
/src/clojure_through_code/clojurebridge_exercises.clj:
--------------------------------------------------------------------------------
1 | ;; ClojureBridge London challenges - solved and explained
2 | ;;
3 | ;; All the exercises from https://clojurebridgelondon.github.io/workshop
4 | ;; Solutions and discussions on how the exercises were solved
5 | ;;
6 | ;; Author: @jr0cket
7 |
8 |
9 | (ns clojure-through-code.clojurebridge-exercises)
10 |
11 |
12 | ;; 01 Calculate the total number of years from all the following languages
13 | ;;
14 | ;; Clojure (10 years)
15 | ;; Haskell (27 years)
16 | ;; Python (26 years)
17 | ;; Javascript (21 years)
18 | ;; Java (22 years)
19 | ;; Ruby (22 years)
20 | ;; C (45 years)
21 | ;; C++ (34 years)
22 | ;; Lisp (59 years)
23 | ;; Fortran (60 years)
24 | ;; You can use the list of years here as a convenience 10 27 26 21 22 22 45 34 59 60
25 |
26 | ;; calculate the total by adding the age of each language together
27 |
28 | (+ 10 27 26 21 22 22 34 45 59 60)
29 |
30 |
31 | ;; => 326
32 |
33 | ;; There are two values the same, so we could calculate the total as follows
34 |
35 | (+ 10 27 26 21 (* 22 2) 34 45 59 60)
36 |
37 |
38 | ;; Find the average age of languages by adding the ages of all the language ages, then divide that total age with the number of languages
39 |
40 | (/ (+ 10 27 26 21 22 22 45 34 59 60)
41 | 10)
42 |
43 |
44 | ;; We can use the count function to find out how many language we have if we put them into a collection (eg.)
45 | (/ (+ 10 27 26 21 22 22 45 34 59 60)
46 | (count '(10 27 26 21 22 22 45 34 59 60)))
47 |
48 |
49 | ;; 02 Convert between time and numbers
50 | ;;
51 |
52 | ;; Assuming its been 2 hours and 20 minutes since you woke up, lets calculate the time in minutes:
53 |
54 | ;; We add the result of multiplying 2 hours by 60 to get the number of minutes with the 25 minutes
55 | (+ (* 2 60) 25)
56 |
57 |
58 | ;; To get the time in seconds, we can take the previous expression and wrap it with another expression that multiplies the result by 60.
59 | (* (+ (* 2 60) 25) 60)
60 |
61 |
62 | ;; Assuming it has been 428 seconds since the workshop started, the number of minutes could be calculated like this:
63 | (quot 428 60)
64 |
65 |
66 | ;; And the number of seconds calculated like this:
67 | (rem 428 60)
68 |
69 |
70 | ;; 7 hours is 420 minutes, so there are 8 minutes remaining that do not fit into a whole hour.
71 | ;; So 428 minutes is the same as 7 hours and 8 minutes
72 |
73 |
74 | ;; 03 Strings
75 | ;;
76 |
77 | ;; Using a function you can create a string and join values to create a string
78 | (str "My favorite colours are" " 23 " "green" " " "and" " " "purple")
79 |
80 |
81 | ;; Use a function to see if a string contains a colour.
82 | ;; Does the string "Rachel of York gave brown bread in vans" contain the colour brown?
83 |
84 | ;; The clojure.string library defines many functions that specifically work with String types.
85 | (clojure.string/includes? "Rachel of York gave brown bread in vans" "brown")
86 |
87 |
88 | ;; Fixing spelling mistakes
89 | ;; One aspect of fixing spelling mistakes is to find and replace incorrect words.
90 | ;; The clojure.string library provides the replace function.
91 |
92 | ;; Replace in this string the characters ovr with the characters over
93 |
94 | (clojure.string/replace "simplicity ovr complexity" "ovr" "over")
95 |
96 |
97 | (clojure.string/replace "coed as data as coed" "coed" "code")
98 |
99 |
100 | ;; NOTE: Briefly discuss libraries and namespaces
101 | ;; clojure.core is included by default in all projects
102 | ;; on the JVM host, so is java.lang
103 | ;; clojure.xxx libraries can be used by fully qualifying their namespace
104 | ;; Other Clojure libraries need to be added as a project dependency too.
105 |
106 |
107 |
108 | ;; Simple Palendrome Checker
109 | ;; A Palindrome is a word that is spelt the same whether it is written backwards or forwards. So when you reverse the word it should still look the same.
110 |
111 | ;; Using functions on strings that are not part of the clojure.string library can give 'interesting' results.
112 | ;; Some clojure.core functions treat a String as a sequence of characters and would return a sequence of characters rather than a string.
113 |
114 | (= "radar" (reverse "radar"))
115 |
116 |
117 | ;; The clojure.core/reverse function returns a list the individual characters in reverse order, rather than reversing the string as a single value.
118 |
119 | ;; Using the clojure.string/reverse function keeps the value as a string
120 | (= "radar" (clojure.string/reverse "radar"))
121 |
122 |
123 | ;; 04 Assignment (binding symbols to values)
124 | ;;
125 |
126 | ;; The = function is used to compare values
127 |
128 | ;; We can use def to give a name to something we already know, like a string
129 |
130 | (def jenny-loves "Jenny loves rubarb crumble and vanilla custard")
131 |
132 | jenny-loves
133 |
134 |
135 | ;; We can also use def to give a name to something we dont know yet, eg. a calculation.
136 |
137 | (def mangoes 4)
138 | (def oranges 12)
139 | (def total-fruit (+ mangoes oranges))
140 | (def average-fruit-amount (/ total-fruit 2))
141 | average-fruit-amount
142 |
143 |
144 | ;; NOTE: Its common to use a map to define multiple values in a namespace,
145 | ;; rather than multiple individual def functions
146 |
147 | (def fruit-stock
148 | {:mangos 4
149 | :oranges 12})
150 |
151 |
152 | (get fruit-stock :mangos)
153 |
154 |
155 | ;; 05 Collections - Vectors
156 | ;;
157 |
158 | ;; A vector of the temperatures for the next week
159 | [9 2 -3 4 5 9 4]
160 |
161 |
162 | ;; assuming the first temperature is for Monday, then to get Thursday we can write
163 | ;; The index of a vector starts at zero
164 |
165 | (nth [9 2 -3 4 5 9 4] 3)
166 |
167 |
168 | ;; Sequences - an abstraction over collections
169 |
170 | (first [9 2 -3 4 5 9 4])
171 | (second [9 2 -3 4 5 9 4])
172 | (last [9 2 -3 4 5 9 4])
173 | (rest [9 2 -3 4 5 9 4])
174 |
175 |
176 | ;; how to we get the third value from the vector ["Birthdays are" "full of" "presents" "that you" "always dreamd" "of having"]
177 |
178 | ;; We can call one function and use its return value as an argument to another function.
179 |
180 | ;; The `rest` function returns a new collection with all values but the first one.
181 | ;; Using the `second` function new collection, we ca second value can then be returned from the new collection
182 | (second
183 | (rest
184 | ["Birthdays are" "full of" "presents" "that you" "always dreamd" "of having"]))
185 |
186 |
187 | ;; Using Local Assignment
188 | ;; Use a local name to hold intermediary values in the processing of the collection.
189 |
190 | ;; the let function assignes a name to our collection of values
191 | ;; then we get the value in third place by using the name.
192 |
193 | (let [rest-of-strings
194 | (rest ["Birthdays are" "full of" "presents" "that you" "always dreamd" "of having"])]
195 | (second rest-of-strings))
196 |
197 |
198 | ;; Find the age of the youngest programming language
199 | ;; The ages are not in order, so you cant just get the first value.
200 |
201 | (first (sort [10 27 26 21 22 22 45 34 59 60]))
202 |
203 |
204 | ;; The min function will simplify our expression
205 | (min [10 27 26 21 22 22 45 34 59 60])
206 |
207 |
208 | ;; NOTE there are over 600 functions in clojure.core so there is often a function you are looking for to simplify you code
209 |
210 |
211 |
212 | ;; Clojure compares values rather than types (in general)
213 |
214 | (= [1 2 3] [1 2 3])
215 |
216 |
217 | ;; => true
218 | (= [1 2 3] [4 5 6])
219 | (= [1 2 3] [3 2 1])
220 |
221 |
222 | ;; => false
223 | (= ["Hello" "World"] ["Hello" "World" "Wide" "Web"])
224 | (= '(1 2 3) [1 2 3])
225 |
226 |
227 | ;; 06 Collections - Maps
228 | ;;
229 |
230 | ;; Maps are key-value pairs
231 | ;; Maps can be self describing if you use meaningful names for the keys
232 |
233 | (count {:firstname "Sally" :lastname "Brown"})
234 |
235 | (def sally {:firstname "Sally" :lastname "Brown"})
236 |
237 | (merge {:first "Sally"} {:last "Brown"})
238 |
239 | (assoc {:first "Sally"} :last "Brown")
240 |
241 | (dissoc {:first "Sally" :last "Brown"} :last)
242 |
243 | (get sally :lastname)
244 |
245 |
246 | ;; merge - join two maps together
247 | ;; assoc - add a key-value to a map
248 | ;; assoc-in - add a key-value to a nested map
249 | ;; update - change an existing value in a map using a function
250 | ;; update-in - change an existing value in a nested map using a function
251 |
252 | ;; keys - return the keys of a map
253 | ;; vals - return the values of a map
254 |
255 |
256 | ;; The assoc function can be used by assigning a new value to an existing key.
257 |
258 | (def hello {:count 1 :words "hello"})
259 |
260 | (assoc hello :count 0)
261 |
262 |
263 | (assoc hello :words "Hello Clojure World")
264 |
265 |
266 | ;; The update function applies a function to the existing value to create a new value for a specific key
267 |
268 | (def hello-update {:count 1 :words "hello"})
269 |
270 | (update hello-update :count inc)
271 |
272 |
273 | (update hello-update :words str ", world")
274 |
275 |
276 | ;; The update-in function works like update, but takes a vector of keys to update at a path to a nested map.
277 |
278 | (def startrek-cat
279 | {:pet
280 | {:age 5 :name "Khan"}})
281 |
282 |
283 | (update-in startrek-cat [:pet :age] + 1)
284 |
285 |
286 | ;; Map extraction with get
287 | ;; A map is a key-value pair, the key is used to get a value from a map.
288 |
289 | ;; If the key does not exist, then a nil value is returned.
290 |
291 | (get {:firstname "Sally" :lastname "Brown"} :firstname)
292 |
293 |
294 | (get {:firstname "Sally"} :lastname)
295 |
296 |
297 | ;; A default value can be included with the get function, so if a key is not found in the map, that default value is returned.
298 |
299 | (get {:firstname "Sally"} :lastname "Unknown")
300 |
301 |
302 | ;; Keyword keys behave like functions, as to maps
303 | ;; When a key is a keyword then that keyword can be used as a function to lookup values in a map.
304 |
305 | (:firstname {:firstname "Sally" :lastname "Brown"})
306 |
307 |
308 | ;; A map can also act like a function when given a keyword as an argument
309 |
310 | ({:firstname "Sally" :lastname "Brown"} :firstname)
311 |
312 | ({"firstname" "sally"} "firstname")
313 |
314 | (def sally {(count [1 2]) "aa"})
315 |
316 | sally
317 |
318 |
319 | ;; 07 - Filtering Collections and anonymous functions
320 | ;;
321 |
322 | (filter odd? [1 2 3 4])
323 |
324 |
325 | ;; Write a function to use with filter that will remove the word "we" from the collection: ["are" "we" "there" "yet"]
326 |
327 | (filter (fn [word] (= "we" word)) ["are" "we" "there" "yet"])
328 |
329 |
330 | (filter (fn [word] (not= "we" word)) ["are" "we" "there" "yet"])
331 |
332 |
333 | ;; Or the short-hand form of anonymous function. The % acts as a placeholder for the function argument
334 |
335 | (filter #(not= "we" %) ["are" "we" "there" "yet"])
336 |
337 |
338 | ;; Rather than use filter, we can use remove which is the inverse
339 |
340 | (remove #(= "we" %) ["are" "we" "there" "yet"])
341 |
342 |
343 | ;; 08 - First Class Functions (map and reduce)
344 | ;;
345 |
346 | ;; map is a function that takes another function as an argument, along with a collection.
347 | ;; map calls the function provided to it on each member of the collection, then returns a new collection with the results of those function calls.
348 |
349 | (map even? [0 1 2 3 4])
350 |
351 |
352 | ;; Count the number of characters in each word for a collection of strings eg. ["a" "abc" "abcdefg"]
353 |
354 | (map count ["a" "abc" "abcdefg"])
355 |
356 |
357 | ;; reduce is another function that takes a function and collection as arguments.
358 | ;; (reduce some-fn ["my" "collection" "of" "values"])
359 |
360 | ;; Use reduce with a function that adds numbers together, eg. [10 20 30 40 50]
361 |
362 | (reduce + [30 60 90])
363 |
364 |
365 | ;; Think of a function that joins strings together and use it with reduce to join the words in a collection eg ["h" "e" "l" "l" "o" " " "Clojure"]
366 |
367 | (reduce str ["h" "e" "l" "l" "o"])
368 |
369 | (reduce str ["h" "e" "l" "l" "o" " " "Clojure"])
370 |
371 |
372 | ;; Write map and reduce functions to create the map reduce sandwich
373 |
374 | ;; Create a collection of the raw ingredients for our sandwich
375 |
376 | (def raw-ingredients ["bread" "cucumber" "pepper" "tomato" "lettuce" "onion"])
377 |
378 |
379 | ;; Create a function to "slice" the raw ingredients so to prepare to be made into a sandwich.
380 |
381 | (defn prepare
382 | [all-ingredients]
383 | (map (fn [ingredient] (str "sliced " ingredient)) all-ingredients))
384 |
385 |
386 | (prepare raw-ingredients)
387 |
388 |
389 | ;; => ("sliced bread" "sliced cucumber" "sliced pepper" "sliced tomato" "sliced lettuce" "sliced onion")
390 |
391 |
392 | ;; Reduce our ingredients to a sandwich
393 |
394 | (defn make-sandwich
395 | [prepared-ingredience]
396 | (reduce str (interpose ", " prepared-ingredience)))
397 |
398 |
399 | (str "A tasty sandwich made with " (make-sandwich (prepare raw-ingredients)))
400 |
401 |
402 | ;; The same thing can be written sequentially using the threading macro
403 | (->> ["bread" "cucumber" "pepper" "tomato" "lettuce" "onion"]
404 | (map #(str "sliced " %) ,,,)
405 | (interpose ", " ,,,)
406 | (reduce str ,,,)
407 | (str "I have a tasty sandwich made with " ,,,))
408 |
409 |
410 | ;; Name Smash
411 | ;; Take two names and smash them together to create a new name
412 |
413 | (def students ["John Stevenson" "Mani Sarkar"])
414 |
415 |
416 | ;; Split each string using the regular expression for space character.
417 | ;; Map split as an anonymous function over the student collection
418 |
419 | (map #(clojure.string/split % #" ") students)
420 |
421 |
422 | ;; => (["John" "Stevenson"] ["Mani" "Sarkar"])
423 |
424 |
425 | ;; We can also flatten the result to make it look nicer
426 |
427 | (flatten (map #(clojure.string/split % #" ") students))
428 |
429 |
430 | ;; => ("John" "Stevenson" "Mani" "Sarkar")
431 |
432 |
433 |
434 | ;; Write a function called name-split that a full name as a string and return two seperate strings, one for the first name and one for the last name.
435 |
436 | (defn name-split
437 | "Splits a name into first & last names"
438 | [name]
439 | (clojure.string/split name #" "))
440 |
441 |
442 | ;; Jumble the names
443 | ;; For example, take the first name from the first person and join it with the last name from the second person
444 |
445 | (defn jumble-names
446 | [names]
447 | (let [first-person (first names)
448 | second-person (second names)
449 | first-person-first-name (first (name-split first-person))
450 | second-person-second-name (second (name-split second-person))]
451 | (str "Hello " first-person-first-name second-person-second-name)))
452 |
453 |
454 | ;; Call our function with an argument of the student collection.
455 | (jumble-names students)
456 |
457 |
458 | ;; 09 Conditionals
459 | ;;
460 |
461 | ;; In English we say someone came 1st, rather than someone came 1.
462 | ;; Write a function called position with an argument called number
463 | ;; if the number equals 1, then the function should return "1st" If number is not 1, then return an error message, such as: "Sorry, the number is not supported"
464 |
465 | ;; Using if function for a single condition
466 |
467 | #_(if (condition)
468 | true
469 | false)
470 |
471 |
472 | (defn position
473 | [number]
474 | (if (= number 1)
475 | (str number "st")
476 | (str "Sorry, the number " number " is not supported")))
477 |
478 |
479 | (position 2)
480 |
481 |
482 | ;; TODO fix in ClojureBridge London workshop content
483 |
484 |
485 |
486 | ;; cond function can evaluate multiple conditions
487 |
488 | ;; (cond
489 | ;; predicate1 expression-to-evaluate-when-predicate1-is-true
490 | ;; predicate2 expression-to-evaluate-when-predicate2-is-true
491 | ;; ...
492 | ;; :else expression-to-evaluate-when-all-above-are-false)
493 |
494 | ;; To create the position function with cond
495 |
496 |
497 | (defn positions
498 | [number]
499 | (cond
500 | (= number 1) "1st"
501 | (= number 2) "2nd"
502 | (= number 3) "3rd"
503 | :else (str number "th")))
504 |
505 |
506 | (positions 3)
507 |
508 |
509 | ;; => "3rd"
510 | (positions 4)
511 |
512 |
513 | ;; Temperature conversion with cond
514 | ;; Write a function which converts temperatures
515 |
516 | ;; (temperature-in-celcius 32.0 :fahrenheit) ;=> 0.0
517 | ;; (temperature-in-celcius 300 :kelvin) ;=> 26.85
518 | ;; (temperature-in-celcius 22.5 :celcius) ;=> 22.5
519 | ;; (temperature-in-celcius 22.5 :fake) ;=> "Unknown scale: :fake"
520 | ;; If an unknown temperature scale is used, an error message should be returned
521 |
522 |
523 | ;; Formulas to convert temperatures
524 | ;; Fahrenheit to Celcius: (* (- Fahrenheit 32) 5/9) = Celcius
525 | ;; Kelvin to Celcius: (+ Kelvin 273.15) = Celcius
526 |
527 | (defn temperature-in-celcius
528 | [temperature scale]
529 | (cond
530 | (= scale :celcius) temperature
531 | (= scale :fahrenheit) (* (- temperature 32) 5/9)
532 | (= scale :kelvin) (- temperature 273.15)
533 | :else (str "Unknown scale: " scale)))
534 |
535 |
536 | (temperature-in-celcius 32.0 :fahrenheit)
537 |
538 |
539 | ;; => 0.0
540 | (temperature-in-celcius 300 :kelvin)
541 | (temperature-in-celcius 22.5 :celcius)
542 | (temperature-in-celcius 22.5 :gibberish)
543 |
544 |
545 | ;; 10 iterate with for (list comprehension)
546 | ;;
547 |
548 | ;; Create a combination lock
549 |
550 | ;; Generate numbers with range
551 | (range 10)
552 |
553 |
554 | ;; Use for to create a range of numbers for each combination tumbler
555 | ;; then combine them together
556 |
557 | (for [tumbler-1 (range 10)
558 | tumbler-2 (range 10)
559 | tumbler-3 (range 10)]
560 | [tumbler-1 tumbler-2 tumbler-3])
561 |
562 |
563 | ;; Calculate the total number of combinations
564 |
565 | (count
566 | (for [tumbler-1 (range 10)
567 | tumbler-2 (range 10)
568 | tumbler-3 (range 10)]
569 | [tumbler-1 tumbler-2 tumbler-3]))
570 |
571 |
572 | ;; Make the combinations harder to guess
573 | ;; only allow the combinations where each tumbler wheel has a different number, so exclude combinations like 1-1-1, 1-2-2, 1-2-1, etc.
574 | ;; How many combinations does that give us?
575 |
576 | ;; for can set conditions that
577 |
578 | (for [tumbler-1 (range 10)
579 | tumbler-2 (range 10)
580 | tumbler-3 (range 10)
581 | :when (or (not= tumbler-1 tumbler-2)
582 | (not= tumbler-2 tumbler-3)
583 | (not= tumbler-3 tumbler-1))]
584 | [tumbler-1 tumbler-2 tumbler-3])
585 |
586 |
587 | ;; NOTE editor truncates to first 100 results
588 |
589 |
590 | (for [tumbler-1 (range 10)
591 | tumbler-2 (range 10)
592 | tumbler-3 (range 10)
593 | :when (or (= tumbler-1 tumbler-2)
594 | (= tumbler-2 tumbler-3)
595 | (= tumbler-3 tumbler-1))]
596 | [tumbler-1 tumbler-2 tumbler-3])
597 |
598 |
599 | ;; Polymorphic function via number of arguments
600 | (defn hello-poly
601 | ([] (hello-poly "Jane Doe"))
602 | ([name] (str "Hello my friend " name)))
603 |
604 |
605 | ;; Calling without arguments returns a default result
606 | (hello-poly)
607 |
608 |
609 | ;; => "Hello my friend Jane Doe"
610 |
611 | (hello-poly "Mani")
612 |
613 |
614 | ;; => "Hello my friend Mani"
615 |
--------------------------------------------------------------------------------
/src/clojure_through_code/working_with_maps.clj:
--------------------------------------------------------------------------------
1 | (ns clojure-through-code.working-with-maps)
2 |
3 |
4 | ;; Generating hash-maps
5 |
6 | (hash-map)
7 |
8 |
9 | ;; => {}
10 |
11 | ;; Arguments must be a key value pair (or pairs)
12 | #_(hash-map :key-without-value)
13 |
14 |
15 | ;; java.lang.IllegalArgumentException
16 | ;; No value supplied for key: :key-without-value
17 |
18 |
19 | (hash-map :movie "The Last Jedi")
20 |
21 |
22 | ;; => {:movie "The Last Jedi"}
23 |
24 |
25 | ;; Arguments that include the same key will use the value associated with the last key in the argument list
26 | (hash-map :movie "The Last Jedi" :movie "The Empire Strikes Back")
27 |
28 |
29 | ;; => {:movie "The Empire Strikes Back"}
30 |
31 |
32 | ;; example: defining pseudonyms that are author's nom de plume (writing names)
33 | ;; A vector of strings is used as the value as an author may have more than one writing name
34 | (hash-map "Samuel Langhorne Clemens" ["Mark Twain" "Sieur Louis de Conte"]
35 | "Mary Ann Evans" ["George Eliot"]
36 | "Charlotte Brontë" ["Currer Bell"]
37 | "Anne Brontë" ["Anne Bell"]
38 | "Ellis Brontë" ["Acton Bell"]
39 | "Alice B. Sheldon" ["James Tiptree Jr"]
40 | "Charles Dodgson" ["Lewis Carroll"]
41 | "Robert A. Heinlein" ["Anson MacDonald" "Caleb Strong"]
42 | "Stephen King" ["Richard Bachman"])
43 |
44 |
45 | ;; Hash-map designs can be as intricate as required to model the data of the system.
46 | ;; However, the more complex a data structure is, the more complex it may be to work with
47 |
48 |
49 | ;; Accessing hash-maps
50 |
51 | (def best-movie
52 | {:name "Empire Strikes Back"
53 | :actors {"Leia" "Carrie Fisher" "Luke" "Mark Hamill" "Han" "Harrison Ford"}})
54 |
55 |
56 | (get best-movie :name)
57 |
58 |
59 | ;; => "Empire Strikes Back"
60 |
61 | ;; keywords are also functions
62 | ;; who look themselves up in maps!
63 | (:name best-movie)
64 |
65 |
66 | ;; => "Empire Strikes Back"
67 |
68 |
69 | ;; access nested maps:
70 | (get-in best-movie [:actors "Leia"])
71 |
72 |
73 | ;; => "Carrie Fisher"
74 |
75 |
76 | ;; => "Empire Strikes Back"
77 |
78 | ;; keywords are also functions
79 | ;; who look themselves up in maps!
80 | (:name best-movie)
81 |
82 |
83 | ;; => "Empire Strikes Back"
84 |
85 | (best-movie :actors)
86 |
87 | (-> best-movie :actors)
88 |
89 |
90 | ;; access nested maps:
91 | (get-in best-movie [:actors "Leia"])
92 |
93 |
94 | ;; => "Carrie Fisher"
95 |
96 | (-> best-movie :actors "leia")
97 |
98 |
99 | (def planets
100 | {:mercury [3.303e+23, 2.4397e6]
101 | :venus [4.869e+24, 6.0518e6]
102 | ,,,})
103 |
104 |
105 | (defn mass
106 | [planet]
107 | (first (planet planets)))
108 |
109 |
110 | (defn radius
111 | [planet]
112 | (second (planet planets)))
113 |
114 |
115 | (name :mercury)
116 |
117 |
118 | ;; => "mercury"
119 |
120 | (keyword "mercury")
121 |
122 |
123 | ;; => :mercury
124 |
125 |
126 | (keys planets)
127 |
128 |
129 | ;; => (:mercury :venus)
130 |
131 |
132 |
133 | ;; Combining data structures into maps
134 | ;;
135 |
136 | (merge {:a 1 :b 2 :c 3} {:b 24 :d 4})
137 |
138 |
139 | ;; => {:a 1, :b 24, :c 3, :d 4}
140 |
141 | (merge {:recipie "tofu curry"})
142 |
143 |
144 | ;; Iterate over nested maps
145 | ;;
146 |
147 | ;; TODO: link to Iterate over vectors and mixed data structures
148 |
149 | ;; Collection processing exercise - hours of true
150 |
151 | ;; The project manager is responsible for multiple projects.
152 |
153 | ;; How many capex hours have been spent across all projects?
154 |
155 | (def project-management
156 | {"project A" {"hours" 7, "capex" true},
157 | "project B" {"hours" 3, "capex" false},
158 | "project C" {"hours" 6, "capex" true}})
159 |
160 |
161 | ;; Lets get the projects that have capex hours
162 |
163 | (for [project project-management
164 | :when (= true (get-in [project "capex"] project-management))]
165 | (get-in [project "hours"] project))
166 |
167 |
168 | ;; => ()
169 |
170 | (get-in ["project A" "capex"] ["project A" {"hours" 7, "capex" true}])
171 |
172 |
173 | ;; => nil
174 |
175 |
176 | (get ["project A" {"hours" 7, "capex" true}] "capex")
177 |
178 |
179 | ;; => nil
180 |
181 |
182 | (get ["project A" {"hours" 7, "capex" true}] "project A")
183 |
184 |
185 | ;; => nil
186 |
187 |
188 | (get (second ["project A" {"hours" 7, "capex" true}]) "capex")
189 |
190 |
191 | ;; => true
192 |
193 | (for [project project-management
194 | :when (= true (get-in [project "capex"] project-management))]
195 | (get (second project) "hours"))
196 |
197 |
198 | (for [project project-management
199 | :when (= true (get-in [project "capex"] project-management))]
200 | (get-in [project "hours"] project-management))
201 |
202 |
203 | ;; project is the full map and not just the project key name
204 | (for [project project-management
205 | :let [capex (get-in [project "capex"] project-management)]
206 | :when (= capex true)]
207 | (get-in [project "hours"] project-management))
208 |
209 |
210 | ;; create a second let to capture the project name
211 | (for [project project-management
212 | :let [project-name (keys project) ; intermediate data structure created by for is a vector, so project name not relevant
213 | capex (get-in [project "capex"] project-management)]
214 | :when (= capex true)]
215 | (get-in [project-name "hours"] project-management))
216 |
217 |
218 | (for [project project-management
219 | :let [capex (get (second project) "capex")]
220 | :when (= capex true)]
221 | (get (second project) "hours"))
222 |
223 |
224 | ;; => (7 6)
225 |
226 |
227 | ;; refactor this further by removing duplicate code
228 | ;; the expression (second project) is called twice,
229 | ;; so we add that as a local binding in the let function.
230 |
231 | (for [project project-management
232 | :let [project-details (second project)
233 | capex (get project-details "capex")]
234 | :when (= capex true)]
235 | (get project-details "hours"))
236 |
237 |
238 | #_(while project-management
239 | (into []
240 | (hash-map (first project-management))))
241 |
242 |
243 | #_(while project-management
244 | (into []
245 | (hash-map (keys project-management) (vals project-management))))
246 |
247 |
248 | ;; (filter #(get-in ))
249 |
250 |
251 | ;; mappy thinking
252 |
253 | ;; get the keys from the map
254 |
255 | (keys project-management)
256 |
257 |
258 | ;; => ("project A" "project B" "project C")
259 |
260 | ;; we have the values for the keys, so we can navigate to the hours
261 | (let [projects (keys project-management)]
262 | (for [project projects]
263 | (get-in project-management [project "hours"])))
264 |
265 |
266 | ;; => (7 3 6)
267 |
268 | ;; Now we want to add a condition to projects, so only those hours that are capex are returned
269 |
270 | (let [projects (keys project-management)]
271 | (for [project projects
272 | :when (get-in project-management [project "capex"])]
273 | (get-in project-management [project "hours"])))
274 |
275 |
276 | ;; => (7 6)
277 |
278 |
279 | ;; Now we just need to total the hours and we are finished.
280 |
281 | (let [projects (keys project-management)]
282 | (for [project projects
283 | :when (get-in project-management [project "capex"])]
284 | (reduce + (get-in project-management [project "hours"]))))
285 |
286 |
287 | ;; java.lang.IllegalArgumentException
288 | ;; Don't know how to create ISeq from: java.lang.Long
289 | ;; reduce is trying to work before the for has all the values?
290 |
291 | (reduce
292 | +
293 | (let [projects (keys project-management)]
294 | (for [project projects
295 | :when (get-in project-management [project "capex"])]
296 | (reduce + (get-in project-management [project "hours"])))))
297 |
298 |
299 | ;; java.lang.IllegalArgumentException
300 | ;; Don't know how to create ISeq from: java.lang.Long
301 | ;; reduce is trying to work before the for has all the values?
302 |
303 | ;; Using reduce on the final form is correct, so reduce must be trying to work before the values are put into a sequence.
304 | (reduce + '(7 6))
305 |
306 |
307 | ;; => 13
308 |
309 |
310 | ;; working solution
311 |
312 | (def project-capex-hours
313 | (let [projects (keys project-management)]
314 | (for [project projects
315 | :let [hours (get-in project-management [project "hours"])]
316 | :when (get-in project-management [project "capex"])]
317 | hours)))
318 |
319 |
320 | project-capex-hours; => (7 6)
321 |
322 | (reduce + project-capex-hours)
323 |
324 |
325 | ;; Generating hash-maps
326 | ;;
327 |
328 |
329 | ;; simple hash-maps
330 |
331 | (zipmap [:a :b :c :d :e] (range 0 5))
332 |
333 |
334 | ;; => {:a 0, :b 1, :c 2, :d 3, :e 4}
335 |
336 |
337 | ;; Generate nested hash-maps
338 |
339 | (into {}
340 | (for [character [:a :b :c :d :e]
341 | :let [map-key character
342 | map-value (zipmap [:a :b :c :d :e] (range 0 5))]]
343 | {map-key map-value}))
344 |
345 |
346 | ;; => {:a {:a 0, :b 1, :c 2, :d 3, :e 4}, :b {:a 0, :b 1, :c 2, :d 3, :e 4}, :c {:a 0, :b 1, :c 2, :d 3, :e 4}, :d {:a 0, :b 1, :c 2, :d 3, :e 4}, :e {:a 0, :b 1, :c 2, :d 3, :e 4}}
347 |
348 |
349 | ;; pretty print the output of a nested map
350 |
351 | (require 'clojure.pprint)
352 |
353 |
354 | (clojure.pprint/pprint
355 | (into {}
356 | (for [character [:a :b :c :d :e]
357 | :let [map-key character
358 | map-value (zipmap [:a :b :c :d :e] (range 0 5))]]
359 | {map-key map-value})))
360 |
361 |
362 | ;; {:a {:a 0, :b 1, :c 2, :d 3, :e 4},
363 | ;; :b {:a 0, :b 1, :c 2, :d 3, :e 4},
364 | ;; :c {:a 0, :b 1, :c 2, :d 3, :e 4},
365 | ;; :d {:a 0, :b 1, :c 2, :d 3, :e 4},
366 | ;; :e {:a 0, :b 1, :c 2, :d 3, :e 4}}
367 |
368 |
369 | ;; Transforming collections of maps
370 | ;;
371 |
372 | (def music-collection
373 | [{:album-name "Tubular Bells" :artist "MikeOldfield"}
374 | {:album-name "Smells like teen spirit" :artist "Nirvana"}
375 | {:album-name "Vision Thing" :artist "Sisters of Mercy"}
376 | {:album-name "Here comes the war" :artist "NewModelArmy"}
377 | {:album-name "Thunder & Consolation" :artist "NewModelArmy"}])
378 |
379 |
380 | (comment
381 | music-collection
382 | )
383 |
384 |
385 | ;; if we want albums by artist
386 |
387 | {"Mike Oldfield" ["Tubular Bells"]
388 | "Nirvana" ["Smells like teen spirit"]
389 | "Sisters of Mercy" ["Vision Thing"]
390 | "New Model Army" ["Here comes the war" "Thunder & Consolation"]}
391 |
392 |
393 | #_(reduce (fn [result [key value]]
394 | (assoc result value key))
395 | {} music-collection)
396 |
397 |
398 | #_(into {} (map (fn [album]
399 | )))
400 |
401 |
402 | (loop [albums music-collection
403 | by-artist {}]
404 | (let [album (first albums)]
405 | (if (empty? albums)
406 | by-artist
407 | (recur (rest albums)
408 | (update by-artist
409 | (keyword (:artist album))
410 | conj (:album-name album))))))
411 |
412 |
413 | ;; => {:Mike Oldfield ("Tubular Bells"), :Nirvana ("Smells like teen spirit"), :Sisters of Mercy ("Vision Thing"), :New Model Army ("Thunder & Consolation" "Here comes the war")}
414 |
415 |
416 | (group-by :artist music-collection)
417 |
418 |
419 | ;; => {"Mike Oldfield" [{:album-name "Tubular Bells", :artist "Mike Oldfield"}], "Nirvana" [{:album-name "Smells like teen spirit", :artist "Nirvana"}], "Sisters of Mercy" [{:album-name "Vision Thing", :artist "Sisters of Mercy"}], "New Model Army" [{:album-name "Here comes the war", :artist "New Model Army"} {:album-name "Thunder & Consolation", :artist "New Model Army"}]}
420 |
421 |
422 | (group-by (fn [album] {:artist [:album-name]})
423 | music-collection)
424 |
425 |
426 | (reduce-kv
427 | (fn [albums artist title]
428 | (if (empty? title)
429 | (assoc albums (:artist) [:title])
430 | (update albums artist conj title)))
431 | {}
432 | music-collection)
433 |
434 |
435 | (defn albums-by-artist
436 | [album f]
437 | (reduce-kv
438 | (fn [m k v]
439 | (assoc m (:artist album) (f (:album-name album)))) {} album))
440 |
441 |
442 | (map #(albums-by-artist % conj) music-collection)
443 |
444 |
445 | (defn albums-by-artist
446 | [album f]
447 | (reduce
448 | (fn [m k v]
449 | (assoc m (:artist album) (f (:album-name album)))) {} album))
450 |
451 |
452 | (group-by :artist music-collection)
453 |
454 |
455 | ;; => {"Mike Oldfield" [{:album-name "Tubular Bells", :artist "Mike Oldfield"}], "Nirvana" [{:album-name "Smells like teen spirit", :artist "Nirvana"}], "Sisters of Mercy" [{:album-name "Vision Thing", :artist "Sisters of Mercy"}], "New Model Army" [{:album-name "Here comes the war", :artist "New Model Army"} {:album-name "Thunder & Consolation", :artist "New Model Army"}]}
456 |
457 |
458 |
459 | #_(into {}
460 | (map (fn [album]
461 | (:artist album) )))
462 |
463 |
464 | ;; Renaming keys from a JSON rest call
465 | ;;
466 |
467 | ;; REST API represents an account and returns JSON:
468 |
469 | ;; { "userName": "foo", "password": "bar", "emailId": "baz" }
470 |
471 | ;; Existing Clojure function creates an account:
472 |
473 | ;; (create-account {:username "foo" :password "bar" :email "baz"})
474 |
475 | ;; Transform the Clojure keywords to the form of the REST API keywords
476 |
477 | ;; A low abstraction approach would be:
478 |
479 | (def args {:username "foo" :password "bar" :email "baz"})
480 |
481 |
482 | (def clj->rest
483 | {:username :userName
484 | :email :emailId})
485 |
486 |
487 | (apply hash-map
488 | (flatten (map
489 | (fn [[k v]] [(or (clj->rest k) k) v])
490 | args))) ; args is the arguments to create-account, as above
491 |
492 |
493 | ;; improved abstraction using into, more idiomatic
494 |
495 | (into {} (map
496 | (fn [[k v]] [(or (clj->rest k) k) v])
497 | args))
498 |
499 |
500 | ;; or
501 |
502 | (defn kmap
503 | [f m]
504 | (into {} (map #(update-in % [0] f) m)))
505 |
506 |
507 | (def clj->rest
508 | {:username :userName
509 | :password :password
510 | :email :emailId})
511 |
512 |
513 | (kmap clj->rest args)
514 |
515 |
516 | ;; using clojure.set/rename-keys is even nicer
517 |
518 | (clojure.set/rename-keys args clj->rest)
519 |
520 |
521 | ;; clojure.data.json/write-str is probably the most idiomatic approach.
522 |
523 | ;; Add clojure.data.json as a dependency
524 |
525 | #_(clojure.data.json/write-str args)
526 |
527 |
528 | (def operands {"+" + "/" /})
529 |
530 | ((operands "/") 4 3)
531 |
532 |
533 | ;; ## keys in maps
534 |
535 | (def recipe-map {:ingredients "tofu"})
536 |
537 | (contains? recipe-map :ingredients)
538 |
539 |
540 | ;; => true
541 |
542 | (some #{"tofu"} recipe-map)
543 |
544 |
545 | ;; => nil
546 |
547 | (vals recipe-map)
548 |
549 |
550 | ;; => ("tofu")
551 |
552 |
553 | (some #{"tofu"} (vals recipe-map))
554 |
555 |
556 | ;; => "tofu"
557 |
558 |
559 |
560 | ;; Testing for values in a map
561 | ;;
562 |
563 | ;; Does a collection of maps contain a value?
564 |
565 | (map #(= (:category %) "base") [{:category "base"}])
566 |
567 | (map #(= (:category %) "base") [{:category "base"} {:category "refinement"}])
568 |
569 | (map #(= (:category %) "base") [{:category "base"} {:category "refinement"} {:category "amendment"}])
570 |
571 | (some #(= (:category %) "base") [{:category "base"} {:category "refinement"} {:category "amendment"}])
572 |
573 | (some #(= (:category %) "base") [{:category "refinement"} {:category "amendment"}])
574 |
575 | (some (comp #{"base"} :category) [{:category "base"} {:category "refinement"} {:category "amendment"}])
576 |
577 | (some (comp #(= "base" %) :category) [{:category "base"} {:category "refinement"} {:category "amendment"}])
578 |
579 | (some (comp #{"base"} :category) [{:category "refinement"} {:category "amendment"}])
580 |
581 | (map #(= (:key %) "fish") [{:key "fish"} {:key "chips"} {:key "peas"}])
582 |
583 | (and true (seq [1 2 3]) true)
584 |
585 |
586 | ;; Merging maps
587 | ;;
588 |
589 | ;; Try avoid using deep merging of maps and get the level of maps you need to merge
590 |
591 |
592 | ;;
593 |
594 | (some->> [{:category "base"} {:category "refinement"} {:category "amendment"}]
595 | (filter #(= "base" (:category %))))
596 |
597 |
598 | ;; => ({:category "base"})
599 |
600 | (filter #(= "base" (:category %)) [{:category "base"} {:category "refinement"} {:category "amendment"}])
601 |
602 |
603 | ;; => ({:category "base"})
604 |
605 |
606 |
607 | [{:category "base" :payload {:team-id 1}}
608 | {:category "refinement" :payload {:body-part "right-foot"}}
609 | {:category "refinement" :payload {:height "ground"}}]
610 |
611 |
612 | (reduce merge [{:category "base" :payload {:team-id 1}}
613 | {:category "refinement" :payload {:body-part "right-foot"}}
614 | {:category "refinement" :payload {:height "ground"}}])
615 |
616 |
617 | (merge [{:category "base" :payload {:team-id 1}}
618 | {:payload {:body-part "right-foot"}}
619 | {:payload {:height "ground"}}])
620 |
621 |
622 | ;; => [{:category "base", :payload {:team-id 1}} {:payload {:body-part "right-foot"}} {:payload {:height "ground"}}]
623 |
624 | (reduce merge [{:category "base" :payload {:team-id 1}}
625 | {:payload {:body-part "right-foot"}}
626 | {:payload {:height "ground"}}])
627 |
628 |
629 | ;; => {:category "base", :payload {:height "ground"}}
630 |
631 |
632 | (apply merge [{:category "base" :payload {:team-id 1}}
633 | {:payload {:body-part "right-foot"}}
634 | {:payload {:height "ground"}}])
635 |
636 |
637 | ;; => {:category "base", :payload {:height "ground"}}
638 |
639 |
640 | (map merge [{:category "base" :payload {:team-id 1}}
641 | {:payload {:body-part "right-foot"}}
642 | {:payload {:height "ground"}}])
643 |
644 |
645 | ;; => ({:category "base", :payload {:team-id 1}} {:payload {:body-part "right-foot"}} {:payload {:height "ground"}})
646 |
647 |
648 | (merge-with merge [{:category "base" :payload {:team-id 1}}
649 | {:payload {:body-part "right-foot"}}
650 | {:payload {:height "ground"}}])
651 |
652 |
653 | (into {:category "base" :payload {:team-id 1}}
654 | (map :payload []))
655 |
656 |
657 | (update {:category "base" :payload {:team-id 1}} :payload merge {:body-part "right-foot"})
658 |
659 |
660 | ;; => {:category "base", :payload {:team-id 1, :body-part "right-foot"}}
661 |
662 |
663 | (update {:category "base" :payload {:team-id 1}} :payload merge {:body-part "right-foot"} {:height "ground"})
664 |
665 |
666 | ;; => {:category "base", :payload {:team-id 1, :body-part "right-foot", :height "ground"}}
667 |
668 |
669 |
670 | (update {:category "base" :payload {:team-id 1}} :payload merge [{:body-part "right-foot"} {:height "ground"}])
671 |
672 |
673 | ;; => {:category "base", :payload {:team-id 1, {:body-part "right-foot"} {:height "ground"}}}
674 |
675 |
676 | (update {:category "base" :payload {:team-id 1}} :payload #(apply merge % [{:body-part "right-foot"} {:height "ground"}]))
677 |
678 |
679 | ;; => {:category "base", :payload {:team-id 1, :body-part "right-foot", :height "ground"}}
680 |
681 |
682 |
683 | ;;
684 |
--------------------------------------------------------------------------------