with 4 step
- sudo docker run -it --name brave -v pathOnHost:pathOnContainer -d clojure tail -f /dev/null
- sudo docker exec -it brave /bin/bash
- lein repl (in the container terminal)
enjoy!
Start learning from a awesome and free online book.
http://www.braveclojure.com/getting-started
Control Flow
if
(if boolean-form
then-form
optional-else-form)
If you omit the else branch and the Boolean expression is false, Clojure returns nil.
do
The do operator lets you wrap up multiple forms in parentheses and run each of them.
(if true
(do (println "Success!")
"By Zeus's hammer!")
(do (println "Failure!")
"By Aquaman's trident!"))
when
is like a combination of if and do, but without else branch.
(when boolean-form
then-form1
then-form2)
If condition is true, the then-form1 and then-form2 will run.
If condition is false, nothing will run and return nil.
nil, true false
Both nil and false are used to represent logical falsiness, whereas all other values are logically truthy.
equality
(= 1 1)
(= nil nil)
Boolean operators and and or
or returns either the first truthy value or the last value.
and returns either the first falsy value or the last value.
def
Best practice: you should treat def as if it's defining constants.
Hash map
create hash maps
{:key1 "x" :key2 "y"}
(hash-map :key1 "x" :key2 "y")
Get value from map
(get {:key1 "x" :key2 "y"} :key1)
(get {:key1 "x" :key2 "y"} :key3)
(get {:key1 "x" :key2 "y"} :key3 "default") ;with default value
({:key1 "x" :key2 "y"} :key3 (throw (Exception. "my exception message")))
(get-in {:a 0 :b {:c "ho hum"}} [:b :c]) ;get nested value
({:key1 "x" :key2 "y"} :key1) ; treat the map like a function
(:key1 {:key1 "x" :key2 "y"}) ; treat keyword like a function
Vector
is similar to an Array.
[ 1 2 3 ]
(vector 1 2 3)
(get [3 2 1] 1) ; get the second element
(get [3 2 1] 4) ; return nil
(nth [3 2 1] 4) ; throw IndexOutOfBoundsException
(conj [1 2 3] 4) ; 4 is added to the end of a vector
List
'(1 2 3 4)
(list 1 "two" {3 4})
(nth '(1 2 3 4) 3)
(get '(1 2 3 4) 3) ; this will return nil, never use get with list!!
(conj '(1 2 3 4) 5) ; 5 is added to the beginning of a list
Set
# {1 2 3 3 3}
(hash-set 1 1 2 2)
(set [1 3 3]) ; create sets from vectors and lists
(set '(1 2 3))
(contains? #{:a :b} :b) ; return true or false
(:a #{:a :b}) ; get itself or nil
(get #{:a :b} nil)
(get #{:a nil} nil)
Do not use contains? to lists and vectors. see the doc
function call
clojure evaluates all function arguments recursively before passing them to the function.
special forms(definitions and if expressions)
they don't always evaluate all of their operands.
define function(arity overloading)
(defn x-chop
"Describe the kind of chop you're inflicting on someone"
([name chop-type]
(str "I " chop-type " chop " name "! Take that!"))
([name]
(x-chop name "karate")))
variable-arity functions
(defn favorite-things
[name & things]
(str "Hi, " name ", here are my favorite things: "
(clojure.string/join ", " things)))
(favorite-things "Doreen" "gum" "shoes" "kara-te")
destructing(vector,list)
(defn my-first
[[first]]
first)
(my-first ["x" "2"])
(defn my-rest
[[first & rest]]
rest)
(my-first ["x" "2"])
destructing(map)
(defn xxxx
[{lat :lat lng :lng}]
(println lat)
(println lng))
(defn xxxx
[{:keys[lat lng] :as location}]
(println lat)
(println lng))
(xxxx {:lat 32 :lng 45})
Anonymous Functions
(fn [x y]
(+ x y))
# (+ %1 %2) ; %1 or % is the first argument
; %2 is the second argument, and so on.
; %& is the rest arguments
let (let it be)
let will introduce a new scope.
(def x 0)
(let [x (inc x)] x) ; return 1
x ; return 0, not changed
main uses: 1.name things
2.evaluate an expression only once and reuse the result.
(like a network API call, the expression has side effects)
(let [[part & remaining] remaining-asym-parts]
(recur remaining
(into final-body-parts
(set [part (matching-part part)]))))
loop
(loop [iteration 0]
(println (str "Iteration " iteration))
(if (> iteration 3)
(println "Goodbye!")
(recur (inc iteration))))
Regular expressions
# "regular-expression"
(re-find #"^left-" "left-eye")
(clojure.string/replace "left-eye" #"^left-" "right-")
sequence abstraction
As long as a data structure(like list, vector, set, map) responds to the core sequence operations (the functions first, rest, and cons, which we’ll look at more closely in a moment), it will work with map, reduce, and oodles of other sequence functions for free.
convert to seq
(seq '(1 2 3))
; => (1 2 3)
(seq [1 2 3])
; => (1 2 3)
(seq #{1 2 3})
; => (1 2 3)
(seq {:name "Bill Compton" :occupation "Dead mopey guy"})
; => ([:name "Bill Compton"] [:occupation "Dead mopey guy"])
convert the seq back into a map
(into {} (seq {:a 1 :b 2 :c 3}));
map function
(map inc [1 2 3])
(map str ["a" "b" "c"] ["A" "B" "C"]) ; => ("aA" "bB" "cC")
(map #(% 12) [inc #(+ % 2) #(+ % 3)]) ; functions is data!
reduce function
(reduce (fn [new-map [key val]]
(assoc new-map key (inc val)))
{}
{:max 30 :min 10})
; => {:max 31, :min 11}
take drop take-while drop-while filter
take: take the first n elements of the sequence
drop: return the sequence with the first n elements removed
take-while: take until the predicate function is false
drop-while: drop until the predicate function is false, and return remained.
filter: return all elements that test true for a predicate function.
some: return the first truthy value returned by a predicate function.
(take 3 [1 2 3 4 5 6 7 8 9 10])
; => (1 2 3)
(drop 3 [1 2 3 4 5 6 7 8 9 10])
; => (4 5 6 7 8 9 10)
(take-while #(< (:month %) 3) food-journal)
(drop-while #(< (:month %) 3) food-journal)
(some #(> (:critter %) 3) food-journal) ; => true
(some #(and (> (:critter %) 3) %) food-journal); => {:month 3 :day 1 :human 4.2 :critter 3.3}
sort sort-by
(sort [3 1 2])
(sort-by count ["aaa" "c" "bb"])
concat
concat simply appends the members of one sequence to the end of another
(concat [1 2] [3 4])
lazy Seqs
- A lazy seq is a seq whose members aren’t computed until you try to access them. Computing a seq’s members is called realizing the seq.
- A lazy seq as consisting of two parts: a recipe for how to realize the elements of a sequence and the elements that have been realized so far.
(defn heavy-compute
[x]
(Thread/sleep 1000)
(inc x))
(def result (map heavy-compute (range 1 1000)))
(time (first result)) ; "Elapsed time: 32004.460656 msecs"
infinite Sequences
(take 8 (repeat "na"))
(take 3 (repeatedly (fn [] (rand-int 10))))
(defn even-numbers
([] (even-numbers 0))
([n] (cons n (lazy-seq (even-numbers (+ n 2))))))
(defn even-numbers
([] (even-numbers 0))
([n] (lazy-seq (cons n (even-numbers (+ n 2))))))
Collection Abstraction
The sequence abstraction is about operating on members individually, whereas the collection abstraction is about the data structure as a whole. For example, the collection functions count, empty?, and every? aren’t about any individual element; they’re about the whole:
into
- Adding all the elements from the second to the first.
- As you now know, many seq functions return a seq rather than the original data structure. You’ll probably want to convert the return value back into the original value, and into lets you do that:
(map identity {:sunlight-reaction "Glitter!"})
; => ([:sunlight-reaction "Glitter!"])
(into {} (map identity {:sunlight-reaction "Glitter!"}))
; => {:sunlight-reaction "Glitter!"}
(map identity [:garlic :sesame-oil :fried-eggs])
; => (:garlic :sesame-oil :fried-eggs)
(into [] (map identity [:garlic :sesame-oil :fried-eggs]))
; => [:garlic :sesame-oil :fried-eggs]
conj
(conj [0] [1]) ; => [0 [1]]
(into [0] [1]) ; => [0 1]
(conj [0] 1) ; => [0 1]
(conj [0] 1 2 3 4) ; => [0 1 2 3 4]
(conj {:time "midnight"} [:place "ye olde cemetarium"]) ; => {:place "ye olde cemetarium" :time "midnight"}
(defn my-conj
[target & additions]
(into target additions)) ; define conj in terms of into
apply
apply explodes a seqable data structure so it can be passed to a function that expects a rest parameter.
(max 0 1 2)
(apply max [0 1 2])
partial
partial takes a function and any number of arguments. It then returns a new function.
(def add10 (partial + 10))
(add10 3)
(defn my-partial
[partialized-fn & args]
(fn [& more-args]
(apply partialized-fn (into args more-args))))
complement
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.
(def not-empty? (complement empty?))
Pure Functions
A function is pure if it meets two qualifications:
- It always returns the same result if given the same arguments. This is called referential transparency.
- It can’t cause any side effects. That is, the function can’t make any changes that are observable outside the function itself—for example, by changing an externally accessible mutable object or writing to a file.
comp
Composing functions is so common that Clojure provides a function, comp, for creating a new function from the composition of any number of functions.
using comp on the functions f1, f2, ... fn, creates a new function g such that g(x1, x2, ... xn) equals f1( f2( fn(x1, x2, ... xn))). One detail to note here is that the first function applied—* in the code shown here—can take any number of arguments, whereas the remaining functions must be able to take only one argument.
((comp inc *) 2 3)
(def character
{:name "Smooches McCutes"
:attributes {:intelligence 10
:strength 4
:dexterity 5}})
(def c-int (comp :intelligence :attributes))
memoize
Another cool thing you can do with pure functions is memoize them so that Clojure remembers the result of a particular function call.
exclude var in cljs.core
(ns hello.core
(:refer-clojure :exclude [map]))