├── 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 | [![Built with Spacemacs](https://cdn.rawgit.com/syl20bnr/spacemacs/442d025779da2f62fc86c2082703697714db6514/assets/spacemacs-badge.svg)](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 | "<title>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 | ;; => "\nOliver100Revilo50" 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 | ;; => "\nOliver100Revilo50" 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 | --------------------------------------------------------------------------------