Skip to content

Instantly share code, notes, and snippets.

@coldnew
Forked from danmidwood/ClojureIntro.md
Created October 18, 2015 12:28
Show Gist options
  • Save coldnew/8feb011c88f31ef6904c to your computer and use it in GitHub Desktop.
Save coldnew/8feb011c88f31ef6904c to your computer and use it in GitHub Desktop.
Evangelising Clojure at the Mind Candy Tech Forum

"Clojure Logo"

Let's REPL

In a terminal type: lein repl

; semi-colon marks the rest of the line as a comment
; This is the read prompt, type stuff at it and it will be evaluated
user=>
user=> (println "Hello from REPL")
Hello from REPL
nil

Data types

Numbers
  • Long, BigInt
  • Rational
  • Double, BigDecimal
; Prefer Long to integer
user=> (type 123456)
java.lang.Long

; And Ratio to decimals
user=> (/ 22 7)
22/7
user=> (type 22/7)
clojure.lang.Ratio
Boolean true / false
Text
  • "Strings"
  • \c \h \a \r \s
Some other things. More on these later
  • Symbols
  • :Keywords
Functions
user=> (fn [arg] arg)
#<user$eval678$fn__679 user$eval678$fn__679@1afcfd10> ; uh-oh wat?
; Wrap the function in parens to evaluate it. 
user=> ((fn [arg] arg) 10)
10
Collections
; Vectors
user=> [1 2 3 4 5 6]
[1 2 3 4 5 6]
; Maps
user=> {:one 1 :two 2 :three 3}
{:one 1, :three 3, :two 2}
; Sets
user=> #{1 2 3 4 5 6}
#{1 2 3 4 5 6}
; Lists
user=> (1 2 3 4 5 6)
ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn  user/eval702 (NO_SOURCE_FILE:1)
; Oops. That didn't work

Prefix notation and S-expression

(yay!)

Operator first, operands later Instead of 1 + 2 use + 1 2 Instead of 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 use + 1 2 3 4 5 6 7 8

In clojure the first item inside parens will be evaluated with the rest of the items as arguments.

user=> (+ 1 2)
3
; many arity example
;(operator operand1 operand2 .. operandN)
user=> (+ 1 2 3 4 5 6 7 8)
36

s-exps treeify your code

user=> (* 2 (+ 3 4))
14

"S-expression Tree"

Remember this

user=> (1 2 3 4 5 6)
ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn  user/eval702 (NO_SOURCE_FILE:1)

To prevent evaluate the list can be quoted

user=> (quote (1 2 3 4 5 6))
(1 2 3 4 5 6)
user=> `(1 2 3 4 5 6) ; quote shortcut
(1 2 3 4 5 6)

Namespaces and defining things

Namespaces allow you to bundle related things together

; The current namespace
user=> *ns*
#<Namespace user>
; Changing namespace
user=> (ns some.new.namespace)
nil
some.new.namespace=> 

Use def to create things inside the namespace

some.new.namespace=> (def one 1)
#'some.new.namespace/one
some.new.namespace=> one
1
; In other namespaces the symbol has to be qualified
some.new.namespace=> (ns user)
nil
user=> one ; won't work
CompilerException java.lang.RuntimeException: Unable to resolve symbol: one in this context, compiling:(NO_SOURCE_PATH:0) 
user=> some.new.namespace/one
1
; The value can be used in normal operations
user=> (+ 1 some.new.namespace/one)
2

Remember symbols from data types above? Symbols are used to name things and evaluate to the thing they name, in our code one is a symbol and it evaluates to 1.

Functions

user=> (def a-func (fn [arg] arg))
#'user/a-func
user=> a-func
#<user$a_func user$a_func@78c5cc63>
user=> (a-func 10)
10
user=> (a-func "ten")
"ten"

defn is a shortcut for (def ... (fn ...))

user=> (defn b-func [arg] arg)
#'user/b-func
user=> (b-func 10)
10

Lexical scoping

Scoping is lexical. Using let

user=> (let [one 1
  #_=>       two 2]
  #_=>      (+ one two))
3
user=> (+ one two) ; Outside scope where one and two don't exist
CompilerException java.lang.RuntimeException: Unable to resolve symbol: one in this context, compiling:(NO_SOURCE_PATH:1) 

Collections magic

Map over a collection

user=> (map inc [1 2 3 4 5])
(2 3 4 5 6)

Filter a collection

user=> (filter even? [1 2 3 4 5])
(2 4)

Invert filter with remove

user=> (remove even? [1 2 3 4 5])
(1 3 5)

Reduce a collection

user=> (reduce + [1 2 3 4 5])
15

Map is many arity

user=> (map + [1 2 3 4 5] [6 7 8 9 10] [10 20 30 40 50] [60 70 80 90 100])
(77 99 121 143 165)
; Additional values are ignored
user=> (map + [1 2 3 4 5] [6 7 8 9 10 11 12 13 14 15 16 17 18])
(7 9 11 13 15)

Parallelized mapping with pmap. (use this when the function is intensive or slow (i.e not for addition))

user=> (pmap + [1 2 3 4 5] [6 7 8 9 10] [10 20 30 40 50] [60 70 80 90 100])
(77 99 121 143 165)

Evaluate a many arity function with all list contents its arguments

user=> (apply + [1 2 3 4 5])
15
Side note: Many Clojure functions are higher order. Here's a shortcut to creating throwaway functions to pass as arguments
; Replace this function for squaring the arg
user=> (fn [arg] (* arg arg))
#<user$eval825$fn__826 user$eval825$fn__826@376433e4>
; With this
user=> #(* % %) ; #( represent the reader macro, % represents the first argument
#<user$eval947$fn__948 user$eval947$fn__948@40e4d37f>
user=> #(* %1 %1) ; The equivalent with numbered argument
#<user$eval837$fn__838 user$eval837$fn__838@635b9f9a>
user=> #(* %1 %2) ; Here this is a function of arity two that multiplies the operands
#<user$eval845$fn__846 user$eval845$fn__846@37b24706>

Clojure supports partial functions using partial

user=> (map (partial + 3) [1 2 3 4 5])
(4 5 6 7 8)

And function composition using comp

user=> (map (comp inc inc inc) [1 2 3 4 5])
(4 5 6 7 8)
And back to the collections

Sequential collections can be accessed by index with nth

user=> (nth [1 2 3 4 5] 1)
2
; Vectors are also functions that take an index as an argument
user=> ([1 2 3 4 5] 2)
3

The head and tail of a list can be taken with first and rest.

user=> (first [1 2 3 4 5])
1
user=> (rest [1 2 3 4 5])
(2 3 4 5)
user=> (take 2 [1 2 3 4 5])
(1 2)
user=> (drop 2 [1 2 3 4 5])
(3 4 5)

range builds ranges

user=> (range 10)
(0 1 2 3 4 5 6 7 8 9)
user=> (range 5 10)
(5 6 7 8 9)
user=> (range) ; Without an upper bound this creates an infinite sequance. Don't run this in the repl!

take is handy for sampling infinite sequences in the repl

user=> (take 100 (range))
(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99)

Some other infinite sequences

; repeat accepts a value creates an infinite seq full of the same value
user=> (take 10 (repeat "Clojure"))
("Clojure" "Clojure" "Clojure" "Clojure" "Clojure" "Clojure" "Clojure" "Clojure" "Clojure" "Clojure")
; cycle accepts a collection and creats and infinite seq looping the collection
user=> (take 10 (cycle [1 2 3]))
(1 2 3 1 2 3 1 2 3 1)

interleave merges sequences together

user=> (take 10 (interleave (repeat "Rich") (repeat "Hickey")))
("Rich" "Hickey" "Rich" "Hickey" "Rich" "Hickey" "Rich" "Hickey" "Rich" "Hickey")

group-by accepts a function and groups the items in the sequence by the result of function evaluation

user=> (group-by (partial > 5) [1 2 3 4 5 6 7 8 9 10])
{true [1 2 3 4], false [5 6 7 8 9 10]}

Similarly(ish), frequencies can tell you how many times an item appears in a sequence

user=> (frequencies [1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 5 5 5 5 6 6 6 7 7 8])
{1 8, 2 7, 3 6, 4 5, 5 4, 6 3, 7 2, 8 1}

Maps and Keywords

Maps are constructed in Clojure using curly braces. Commas are insignificant and treated as whitespace but it's idiomatic Clojure to separate each key-value pair by a comma.

user=> (def some-map {:one 1, :two 2, :three 3})
#'user/some-map
user=> some-map
{:one 1, :three 3, :two 2}

The colon prefixed symbols are keywords, and they are often used as map keys.

Maps are also functions that take a single key argument and return the value associated with that key

user=> (some-map :one)
1

Conversely, a keyword is also a function, one that will take a single map argument and lookup itself in that map

user=> (:one some-map)
1

Maps are often used as containers for named data fields

user=> (def important-data {:a-number 123, :some-name "Bob", :user-monster-adoption-monster-id 1234565432256765})
#'user/important-data

State, immutability and the software transactional memory (STM) system

"State: You're doing it wrong"

Rich Hickey doesn't like mutable state

In Clojure everything is immutable. Apart from when it isn't. And when it isn't, Rich Hickey is there to save us.

Clojure has four mutable types

  • Refs - use when multiple values need to be updated in the same transaction
  • Atoms - Synchronous and will be retried until complete
  • Agents - Asynchronous and will only be run once. Often used for IO.
  • Vars - Thread safe, used for dynamic binding. More later.

Ref example

user=> (def refxample (ref {})) ; Create a ref containing a map
#'user/refxample
user=> refxample
#<Ref@33e1ccbc: {}>
user=> (deref refxample) ; deref the ref to get the map
{}
user=> @refxample ; @ is a shortcut to deref
{}

Refs can be updated using alter inside a transaction

user=> (alter refxample assoc :key "value") ; this isn't in a transaction
IllegalStateException No transaction running  clojure.lang.LockingTransaction.getEx (LockingTransaction.java:208)

user=> (dosync (alter refxample assoc :key "value")) ; dosync evaluates its body inside a transaction
{:key "value"}
user=> @refxample
{:key "value"}

Atom example

user=> (def atomexample (atom 10)) ; Create an atom with value 10
#'user/atomexample
user=> @atomexample
10
user=> (swap! atomexample inc) ; Update the atom by calling inc on the current value
11
user=> @atomexample
11

Vars

Vars are interesting because they are dynamically bound and thread safe. Enclosing the var name in asterisks (often called earmuffs) is idiomatic

user=> (def ^:dynamic *some-var* 10)
#'user/*some-var*
user=> *some-var*
10
user=> (binding [*some-var* 20]
  #_=>     (println *some-var*))
20
nil

Because the binding is dynamic instead of lexical, it passes through function barriers

user=> (def ^:dynamic *some-var* 10)
#'user/*some-var*
#'user/print-some-var
user=> (defn print-some-var []
  #_=>       (println *some-var*)
  #_=>       *some-var*)
#'user/print-some-var
user=> (binding [*some-var* 20]
  #_=>     (print-some-var))
20
20
user=> (print-some-var)
10
10

Go forth and explore

This has only been a brief run through, there's more out there!

Some REPL tricks

Display documenation

(doc function-name)

Display source code

(source function-name)

Search for documentation

(find-doc "what are you looking for?")

Show javadoc

(javadoc class-name)

The End

"Rich Hickey"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment