1. Resources for Learning
  2. Require Namespaces
  3. Import Java Classes
  4. Vars
    1. Dynamic Vars
  5. Working with Strings
    1. Inflections
  6. Regular Expressions
  7. Keywords and Symbols
  8. Random Numbers
  9. Hexidecimal
  10. Dates and Times
    1. Time Zones
  11. Expression Problem, Protocols and Multimethods
  12. Sequences vs Collections
  13. Lists
  14. Vectors
  15. Sets
  16. De-structuring
  17. Command Line Arguments
  18. try catch
  19. Zippers
  20. Parsing xml
  21. Java Iterable
  22. Java Arrays
  23. Implement Java Interface
  24. Boot
    1. Load Dependency from REPL
  25. Property Based Testing
  26. Clojure Spec

Resources for Learning

Require Namespaces

Prefer require over use

From repl

(require '[some.namespace :as sn :refer [specific symbols]))

From namespace files
(:require [some.namespace :as sn :refer [specific symbols]))

Import Java Classes

Import java classes from the repl

(import '(java.io File BufferedReader))

Import java classes from namespace files

(ns my-ns
    (:import (java.io File BufferedReader)))

Vars

Dynamic Vars

(def ^:dynamic x 1)
(+ x x)
2
(binding [x 2]
    (+ x x))
4
(+ x x)
2

Working with Strings

(require '[clojure.string :as str])
;; Strings are sequences of characters
(apply str (seq "abcdefg"))
;; Get rid of whitespace before and after string
(str/trim "\t whitespace needs to go\n\r")

;; Collapse whitespace inside string
(str/replace "\twhitespaces   is\t not cool\r\n" #"\s+" " ")
;; Join a bunch of list items into comma separated value (csv)
;; string
(apply str
       (interpose "," ["one potato" "two potato" "three potato" "four"]))
;; Instead of apply and interpose, just use join
(str/join "," ["one potato" "two potato" "three potato" "four"])

Clojure uses UTF-16 for all Strings.

;; Each character is represented as unicode integer code point
(int \d) ;;=> 100
(char 100) ;;=> \d

format is sometimes more concise and more powerful than str for formatting strings. format uses the same format as C's printf

;; Format a list of lists into a table
(defn table-format [row]
      (apply format "%-20s | %-20s | %-20s" row))
(def header ["First" "Last" "Date"])
(def data [["Andy" "Murray" "2013"]
           ["Rodger" "Federer" "2012"]
           ["Novak" "Djokovic" "2011"]])
(->> (concat [header] data)
     (map table-format)
     (mapv println))
    
First                | Last                 | Date
Andy                 | Murray               | 2013
Rodger               | Federer              | 2012
Novak                | Djokovic             | 2011

split accepts regexes

(clojure.string/split "check    this \t out" #"\s+")
;;=> ["check" "this" "out"]

Inflections

(Note: checkout the string format function from common lisp)

r0man/inflections-clj

(require '[inflections.core :as inf])
(inf/pluralize 12 "cow")
;;=> "12 kine"

Customize the ways things are pluralized

(inf/pluralize 12 "cow" "cows")
;;=> "12 cows"
(inf/plural! #"(cow)(?i)$" "$1s")
(inf/plural "cow")
;;=> "cows"

Camelize

(inf/camelize "my_object")
;;=> "MyObject"
(inf/parameterize "My most favorite URL!")

Regular Expressions

Use literal reader syntax:

#"(?i)<regex>"

Or build dynamically:

(re-pattern (str "(?i)" <regex>))

Use re-find to search for pattern in strings.
(re-find #"\b([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4})\b."
         "Find some emails like test@domain.com, for example")
#=> ["TEST@domain.com," "TEST@domain.com"]

Watch out for case senstivity
(re-find #"\b([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4})\b."
         "Find some emails like TEST@domain.com, for example")
;; -> nil
(re-find #"(?i)\b([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4})\b."
         "Find some emails like TEST@domain.com, for example")
;; -> ["TEST@domain.com," "TEST@domain.com"]

re-matches is for matching entire string to a given pattern

(re-matches #"\w+" "Doesn't Match")
(re-matches #"\w+" "Match")

Remember, the difference between re-find and re-matches is that re-matches attempts to match the entire string

re-seq is good for matching a pattern multiple times. And it's safe to use on large strings because it's lazy.

;; For example, extract simple 7-digit phone numbers
(re-seq #"\({0,1}\d{0,3}\){0,1}\s*-{0,1}\d{3}-\d{4}"
        "My cell is 421-555-1234. My home is 444-3333. Work is (222) 111-2222")
;; -> ("421-555-1234" " 444-3333" "(222) 111-2222")

Keywords and Symbols

Keyword to string

(name :foo) ;; -> "foo"
(str :foo) ;; -> ":foo"

String to keyword

(keyword "foo") ;; -> :foo

Symbol to string

(str 'defn) ;; -> "defn"

String to symbol

(symbol "defn") ;; -> defn

Symbol to keyword

(keyword 'defn) ;; -> :defn

Keywords and Symbols with namespaces

(keyword "my-ns" "my-var") ;; -> :my-ns/my-var

Random Numbers

Random integer between 0 and 9

(rand-int 10)

Random decimal between 0 and 1

(rand)

Use shuffle to randomly shuffle a collection

(shuffle [0 1 2 3 4 5 6 7 8 9])

Lots of good stuff in data.generators

Hexidecimal

(Integer/toString 1234572 16)
;;=> "12d68c"
16r12d68c
;;=> 1234572

Dates and Times

Date Literals

#inst "2014-05-06T16:00:00.000-00:00"
(require '[clj-time.core :as ti])
(ti/date-time 2014 05 06 11 00 00 00)

Unix Time

(java.util.Date. (.getTime (java.util.Date.)))
(require '[clj-time.coerce :as tc])
(tc/from-long (tc/to-long (tc/now)))

Current Date and Time

(java.util.Date.)
(ti/now)
(require '[clj-time.local :as tl])
(tl/local-now)

Both now and local now are the same except local now has the timezone set to match

(compare (ti/now) (tl/local-now))
;;=> 0
    
Parse Strings to dates
(require '[clj-time.format :as tf])

(tf/parse (tf/formatter "yy-MM-dd, HH:mm") "2014-05-06, 11:00")
;;=> #<DateTime 2014-05-06T11:00:00.000Z>

UnParse dates to strings

(tf/unparse (tf/formatter "yy-MM-dd, HH:mm") (ti/now))

Lots of built-in formatters

(tf/show-formatters)
(tf/parse (:basic-date-time-no-ms tf/formatters) "20140611T175505Z")

Formatters can even accept literal strings
(tf/parse (tf/formatter "EEE' at 'HH:mm") "Mon at 1:00")

Switching between Java Dates and Joda DateTimes

(tc/from-date #inst "2014-05-06T16:00:00.000-00:00")
(tc/to-date (tl/local-now))

Use compare to compare dates

(compare (ti/now)
    (tc/from-date #inst "2014-05-06T16:00:00.000-00:00"))
;;=> 1

Calculate time intervals

(ti/in-days (ti/interval (ti/date-time 2014 01 01) (ti/now)))
;;=> 161
(str (ti/in-years (ti/interval (ti/now)
    (ti/date-time 2100 01 01))) " years from now")
;;=> "85 years from now"
;; 1.day.from_now 
(-> 1 ti/days ti/from-now)
;; 1.day.ago 
(-> 1 ti/days ti/ago)

Time Zones

Current Timezone

(ti/default-time-zone) 
Convert a date time into local time zone
(ti/from-time-zone (ti/date-time 1979 07 04)
    (ti/default-time-zone)) 

Expression Problem, Protocols and Multimethods

Great description of the Expression Problem and how clojure addresses it

Great write up on Protocols vs Multimethods

Sequences vs Collections

Collection Types (such as vector, list, map, and set) can be Sequential, Associative, and/or Counted. Great overview here of abstractions

Lists, Vectors, Maps, Strings, and Streams are all sequences. The ISeq interface includes 3 functions: first, rest, and cons

If you need to add elements at beginning, consider lists, otherwise use vectors because vectors have near constant time lookup.

"Add" to a sequence

(conj [1 2 3] 4 5)
;;=> [1 2 3 4 5]
(conj '(1 2 3) 4 5)
;;=> (5 4 1 2 3)

Note that conj is polymorphic depending on type of seq it operates on.

(cons 4 '(1 2 3))
;;=> (4 1 2 3)
(cons 4 [1 2 3])
;;=> (4 1 2 3)

cons operates on ISeq. And note that cons returns a Cons type (not a list)

(type (cons 4 [1 2 3]))
clojure.lang.Cons

"Remove" from sequence

(pop '(1 2 3 4 5))
;;=> (2 3 4 5)
(pop [1 2 3 4 5])
;;=> [1 2 3 4]

pop is polymorphic like cons

(rest '(1 2 3 4 5))
;;=> (2 3 4 5)
(rest [1 2 3 4 5])
;;=> (2 3 4 5)

rest operates on ISeq like cons

Get value in sequence

(nth '(1 2 3 4 5) 2)
;; => 3
(nth [1 2 3 4 5] 2)
;; => 3

Note that if you know the datastructure is a vector, there are alternative ways to getting a value.

Lists

Lists are sequences that implement "counted" and "sequential"

Creating lists - Difference between into and list

(time (def t1 (into '() (range 1 10000))))
;;=> "Elapsed time: 1.087 msecs"
(first t1)
;;=> 9999

Note that into reversed the list

(time (def t1 (apply list (range 1 10000))))
;;=> "Elapsed time: 2.367 msecs"

apply creates a correctly ordered list but is a lot slower than into

Vectors

Vectors are sequences that implement "sequential" and "associative" and "counted" (the sweet spot!)

There are a couple ways to create vectors

[ 1 2 3 4 5 ]
(vector 1 2 3 4 5)
(vec '(1 2 3 4 5))
(into [] '(1 2 3 4 5))

Use into over vec for more performance

"Add" item to vector

(conj [1 2 3] 4)
;;=> [1 2 3 4]

"Add" item to beginning of vector

(into ["boo" "baz"] ["foo" "bar"])
;;=> ["boo" "baz" "foo" "bar"]

Vectors are associative (each value can be accessed by index)
(get [1 2 3] 1)
;; => 2

So you can also "add" item to a vector using indexes

(assoc [1 2 3] 3 4)
;;=> [1 2 3 4]

Use assoc to "update" any item in vector

(assoc [1 2 3 4] 0 2)
;;=> [2 2 3 4]

assoc accepts variable argument list so you can set multiple indices at same time

(assoc [1 2 3 4] 0 2 1 2 2 2 3 2)
;; => [2 2 2 2]

A few different ways to get values in vectors besides nth

(get [1 2 3 4] 1)
;; => 2
([1 2 3 4] 1)
;; => 2

Vectors are functions of themselves!

Here's a few ways to determine the index of an element in a vector

(.indexOf ["one" "two"] "two")
;; => 1
(def v (map-indexed vector ["one" "two"])
;; => [[1 "one"] [2 "two"]]

(map first

     (filter #(= (second %) "two")
             (map-indexed vector v)))
;; => 1 

Sets

Sets aren't seqs (they don't implement sequential). They are collections. They implement "counted".

(seq? #{1 2 3})
;; => false
(type #{ 1 2 3})
;; ->clojure.lang.PersistentHashSet

Create Sets

(hash-set :a :b :c)
;;=> #{:a :c :b}
(apply hash-set :a [:b :c])
;;=> #{:a :c :b}
(set '(:a :b :c))
;;=> #{:c :b :a}
(into #{:a :b} [:c :d])
;;=> #{:c :b :d :a}

(into #{:a :b} [:a :b :c :a])
;;=> #{:c :b :a}

Sorted Sets are based on balanced red-black binary tree

(into (sorted-set) #{:c :b :d :a})
;;=> #{:a :b :c :d}
(into (sorted-set-by (fn [a b] (- (compare a b)))) #{:c :b :d :a})
;;=> #{:d :c :b :a}

Add to sets

(conj #{:a :b :c} :d :e)
;; -> #{:e :c :b :d :a}

Remove from sets

(disj #{:a :b :c} :b)
;; -> #{:a :c}

Finding values inside Sets

(contains? #{:red :white :green} :blue)
;; -> false
(contains? #{:red :white :green} :green)
;; -> true
(get #{:red :white :green} :blue)
;; -> nil
(get #{:red :white :green} :green)
;; -> :green

If set values are keywords, use keywords as functions instead of get

(:blue #{:red :white :green})
;; -> nil
(:green #{:red :white :green})
;; -> :green

Sets are functions of themselves?!

(#{:red :white :green} :green)
;; -> :green
(#{:red :white :green} :blue)
;; -> nil

De-structuring

De-structure Maps

let [{:keys [x y] :as thing :or {x 0 y 0}] ...

Another way to destructure Maps

let [{var1 :key1 var2 :key2 {nested1 :key3}}] ...

# Maps

Maps aren't seqs. They do implement "counted" and "associative".

Iterate over keys and vals in map

(def tmap {:foo {:error true}
           :bar {:error false}
           :baz {:error true}})
(seq tmap) ;; => ([:baz {:error true}] [:bar {:error false}] [:foo {:error true}])
(for [[k v] (seq tmap)] (:error v))

Command Line Arguments

tools.cli on github

try catch

(try
    (do ;; stuff)
    (catch Exception e (do ;; stuff when error)))

Zippers

Parsing xml

Java Iterable

iterator-seq

Java Arrays

Create a Java Array

(def arr1 (into-array ["foo" "bar"]))
(aget arr1 0) ;;=> "foo";

Implement Java Interface

https://github.com/cemerick/clojure-type-selection-flowchart

Use reify for most cases. Use proxy for some cases.

clojure
(.listFiles (java.io.File. ".")
  (reify
    java.io.FileFilter
    (accept [this f]
      (.isDirectory f))))

Boot

Load Dependency from REPL

(merge-env! :dependencies '[[com.google.gdata/core     "1.47.1"]])

Property Based Testing

I've only scratched the surface of the power of the test.check library and property based testing. If you're looking to get your feet wet, I'd recommend starting by watching John Hughes Talk on Quick Check, and Reid Draper's Talk on Test.Check.

Clojure Spec

(require '[clojure.spec :as s])

Any clojure function that takes a single argument and returns a truthy value can be used as a predicte.

conform can be used to test value against a spec

(s/conform even? 1000) ;;=? 1000
(s/conform even? 1001) ;;=> :clojure.spec/invalid

valid? is like conform but returns true or false

Use sets to define, well, sets of valid matches

(s/valid? #{:monohull :catamaran :trimaran} :monohull) ;;=> true
(s/valid? #{:monohull :catamaran :trimaran} :multihull) ;;=> false

Use s/def to add specs to registry for reuse

(s/def ::date #(instance? java.util.Date %))
(s/valid? ::date (java.util.Date.))

Use s/and to compose specs

(require '[clj-time.coerce :as tc])
(require '[clj-time.core :as ti])

(s/def ::date    #(inst? %))
(s/def ::past    #(< (tc/to-long %) (tc/to-long (ti/now))))
(s/def ::future  #(> (tc/to-long %) (tc/to-long (ti/now))))
(s/def ::present #(= (tc/to-long %) (tc/to-long (ti/now))))

(s/def ::past-date    (s/and ::date ::past))
(s/def ::future-date  (s/and ::date ::future))
(s/def ::present-date (s/and ::date ::present))

(def date1 #inst "2014-05-06T16:00:00.000-00:00")

(s/valid? ::past-date date1)    ;;=> true
(s/valid? ::past-date (ti/now)) ;;=> false

Can also use s/or, note that s/or needs a key for documentation. Then, use s/explain or s/explain-data to see why values failed specs

(s/def ::valid-date (s/or :present ::present
                          :future ::future))

(s/explain ::valid-date #inst "2016-07-20T14:16:39.994Z")
;; =>val: #inst "2016-07-20T14:16:39.994-00:00" fails spec: :boot.user/present at: [:present] predicate: (= (to-long %) (to-long (now)))
;; => val: #inst "2016-07-20T14:16:39.994-00:00" fails spec: :boot.user/future at: [:future] predicate: (> (to-long %) (to-long (now)))
def photo1 {
:orig-display-width 1080
:width 1080
:orig-width 1080
:src "blob:http%3A//localhost%3A3449/0ae35ca4-125f-4f70-abb1-23a9bf27ddd9"
:orig-height 1920
:display-width 1080
:orig-display-height 1920
:display-height 1920
:height 1920
}