Clojure Notes
- Resources for Learning
- Require Namespaces
- Import Java Classes
- Vars
- Working with Strings
- Regular Expressions
- Keywords and Symbols
- Random Numbers
- Hexidecimal
- Dates and Times
- Expression Problem, Protocols and Multimethods
- Sequences vs Collections
- Lists
- Vectors
- Sets
- De-structuring
- Command Line Arguments
- try catch
- Zippers
- Parsing xml
- Java Iterable
- Java Arrays
- Implement Java Interface
- Boot
- Property Based Testing
- Clojure Spec
- Ring and Compojure
- Stack
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)
(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
Beware of contains?
!!! contains?
is only meant for associative collections. It also works on sets even though they are not associative.
https://lambdaisland.com/blog/2017-10-02-clojure-gotchas-associative-contains
(contains? #{:red :white :green} :blue)
;; -> false
(contains? #{:red :white :green} :green)
;; -> true
(get #{:red :white :green} :blue)
;; -> nil
(get #{:red :white :green} :green)
;; -> :green
Instead of using contains?
, use some
like so:
(some #{1} [0 1 2 3]) ;; -> 1
(some #{5} [0 1 2 3]) ;; -> nil
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}}] ...
# MapsMaps 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
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
conform
takes single arg and returns truthy value if valid. The return value will be "conformed" to meet the spec if possible. :clojure.spec.alpha/invalid
is returned if it doesn't conform
valid?
is just like conform
but returns true or false
explain
prints to out explain-str
and explain-data
Use keys
to define required and optional keys. Use either :req
, :opt
for namespace keys or :req-un
, :opt-un
for unqualified keys.
(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
}
Ring and Compojure
How to serve resources?
Stack
A good technique for representing a stack datastructure in clojure, is to use cons
as push and drop
as pop.
If you try to add and remove latest item from the right (instead of the left/front of a list), it'll be a lot slower than cons
and drop
.