Welcome
Introductions
- Henry Garner (CTO, Likely)
- James Henderson (Senior Developer, Likely)
Professionally using Clojure for 18 months
Who are you?
… and what are you hoping to get out of these sessions?
What we’ll cover today
- Getting up and running
- Language primitives
- Working with sequences
- Working with higher order functions
- Working with real data
- Moving beyond the REPL
Where will we get to in 4 weeks?
- The ability to create your own non-trivial Clojure projects
Housekeeping
- Experience of LISP?
- Other functional languages?
- What text editor?
- Who has Java?
- Who has leiningen?
Getting Set Up
First, you’ll need a Java Virtual Machine, or JVM, and its associated development tools, called the JDK. This is the software which runs a Clojure program. If you’re on Windows, install Oracle JDK 1.7. If you’re on OS X or Linux, you may already have a JDK installed. In a terminal, try:
which javac
If you see something like
/usr/bin/javac
Then you’re good to go
Windows users: http://leiningen.org/#install
Leiningen
mkdir -p ~/bin
cd ~/bin
curl -O https://raw.github.com/technomancy/leiningen/stable/bin/lein
chmod a+x lein
cd
lein new scratch
export PATH="$PATH":~/bin
REPL
lein repl
This is an interactive Clojure environment called a REPL, for “Read, Evaluate, Print Loop”. It’s going to read a program we enter, run that program, and print the results. REPLs give you quick feedback, so they’re a great way to explore a program interactively, run tests, and prototype new ideas.
REPL
With most languages, you write the system from the outside.
With LISPs, you bring the system up and develop it from the inside.
Jumping inside
user=> nil
nil
nil is the most basic value in Clojure.
Boolean values
user=> true
true
user=> false
false
Basic Types
0
-42
1.2e-5
1/3
"Hi there!"
\space
\E
:keywords
#"\d+"
Collection Types
Maps
{:a 1 :b 2}
Sets
#{1 2 3}
Vectors
[1 2 3]
… that’s it!
“It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures.” —Alan Perlis
Def
user=> (def x 3)
#'user/x
We’ve defined a var in the ‘user’ namespace and can refer to it:
user=> x
3
Lists
user=> (1 2 3)
ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn
user/eval146 (NO_SOURCE_FILE:1)
Wha happen?
The REPL sees a list and treats it as a function invocation.
The first element in the list is always the function to be invoked, with any remaining elements passed as arguments.
Function Invocation
user=> (inc 0)
1
user=> (inc x)
4
Nesting
Increment increment the number
user=> (inc (inc 0))
2
Evaluation
Every list starts with a verb. Parts of a list are evaluated from left to right. Innermost lists are evaluated before outer lists.
(+ 1 (- 5 2) (+ 3 4))
(+ 1 3 (+ 3 4))
(+ 1 3 7)
11
Control structures:
user=> (if (> 3 2) "Higher" "Lower")
"Higher"
user=> (when (< 3 2) "Lower")
nil
user=> (when (> 3 2)
(println "3 is greater than 2")
"Higher")
3 is greater than 2
"Higher"
See also: `if-not` and `when-not`
More conditionals
user=> (case (inc 3)
3 "Uh oh"
4 "Yep!"
"Not so sure...")
"Yep!"
user=> (cond
(= 4 (inc 2)) "(inc 2) is 4"
(= 4 (/ 8 2)) "Cond picks the first correct case"
(zero? (- (* 4 2) 8) "This is true, but we won't get here"
:otherwise "None of the above."
"Cond picks the first correct case"
See also: condp
Having fn yet?
user=> (fn [x] (+ x 1))
#
We’ve created a function!
user=> (fn [x]
(if (even? x)
(inc x)
(dec x)))
#
Usage
user=> ((fn [x] (+ x 1)) 10)
11
You probably won’t see this in production code…
Defn
user=> (def half
(fn [number]
(/ number 2)))
#'user/half
user=> (half 6)
3
Creating a function and binding it to a var is so common that it has its own form: defn, short for def fn.
user=> (defn half [number]
(/ number 2))
#'user/half
Function Arity
Functions don’t have to take an argument. We’ve seen functions which take zero arguments, like (+).
user=> (defn half [] 1/2)
#'user/half
user=> (half)
1/2
But if we try to use our earlier form with one argument, Clojure complains that the arity–the number of arguments to the function–is incorrect.
user=> (half 10)
ArityException Wrong number of args (1) passed to:
user$half clojure.lang.AFn.throwArity (AFn.java:437)
Multiple Arities
To handle multiple arities, functions have an alternate form. Instead of an argument vector and a body, one provides a series of lists, each of which starts with an argument vector, followed by the body.
user=> (defn half
([] 1/2)
([x] (/ x 2)))
#'user/half
user=> (half)
1/2
user=> (half 10)
5
Variable Arities
Some functions can take any number of arguments. For that, Clojure provides &, which slurps up all remaining arguments as a list:
user=> (defn vargs
[x y & more-args]
{:x x
:y y
:more more-args})
#'user/vargs
user=> (vargs 1)
ArityException Wrong number of args (1) passed to: user$vargs
clojure.lang.AFn.throwArity (AFn.java:437)
user=> (vargs 1 2)
{:x 1, :y 2, :more nil}
user=> (vargs 1 2 3 4 5)
{:x 1, :y 2, :more (3 4 5)}
Bindings
We know that symbols are names for things, and that when evaluated, Clojure replaces those symbols with their corresponding values.
user=> +
#
We can create names which are locally scoped.
user=> (let [cats 5]
(str "I have " cats " cats."))
"I have 5 cats."
Bindings are local
Let bindings apply only within the let expression itself. They also override any existing definitions for symbols at that point in the program. For instance, we can redefine addition to mean subtraction, for the duration of a let:
user=> (let [+ -]
(+ 2 3))
-1
But that definition doesn’t apply outside the let:
user=> (+ 2 3)
5
Bindings can be composed
We can also provide multiple bindings. Since Clojure doesn’t care about spacing, alignment, or newlines, I’ll write this on multiple lines for clarity.
user=> (let [person "joseph"
num-cats 186]
(str person " has " num-cats " cats!"))
"joseph has 186 cats!"
When multiple bindings are given, they are evaluated in order. Later bindings can use previous bindings.
user=> (let [cats 3
legs (* 4 cats)]
(str legs " legs all together"))
"12 legs all together"
Keywords as functions
user=> (def my-map {:a 1 :b 2})
#'user/my-map
user=> (:a my-map)
1
Destructuring
user=> (def my-map {:a 1 :b 2 :c [3 4 5]})
#'user/my-map
user=> (let [a (:a my-map)
b (:b my-map)]
(+ a b))
3
user=> (let [{a :a b :b} my-map]
(+ a b))
3
user=> (let [{:keys [a b]} my-map]
(+ a b))
3
Nested Destructuring
user=> (let [{:keys [c]} my-map
[c1 c2 c3] c]
(+ c1 c2 c3))
12
user=> (let [{[c1 c2 c3] :c} my-map]
(+ c1 c2 c3))
12
A brief tour of clojure.core
Working with maps:
user=> (def my-map {:a 1 :b 2})
#'user/my-map
user=> (assoc my-map :c 3)
{:a 1, :c 3, :b 2}
user=> (dissoc my-map :a)
{:b 2}
user=> my-map
{:a 1, :b 2}
A brief tour of clojure.core
Working with maps:
user=> (def my-map {:a 1
:b 2
:c {:d 4
:e 5}})
#'user/my-map
user=> (keys my-map)
(:a :c :b)
user=> (vals my-map)
(1 {:d 4, :e 5} 2)
user=> (assoc-in my-map [:c :f] 6)
{:a 1, :c {:f 6, :d 4, :e 5}, :b 2}
Vector functions
user=> (def my-coll [2 3 1 5 6 4 0])
#'user/my-coll
user=> (first my-coll)
2
user=> (second my-coll)
3
user=> (nth my-coll 4)
6
user=> (conj my-coll 7)
[2 3 1 5 6 4 0 7]
user=> my-coll
[2 3 1 5 6 4 0]
Vector functions
user=> (def my-coll [2 3 1 5 6 4 0])
#'user/my-coll
user=> (sort my-coll)
(0 1 2 3 4 5 6)
user=> (interpose -1 my-coll)
(2 -1 3 -1 1 -1 5 -1 6 -1 4 -1 0)
user=> (zipmap [:a :b :c :d :e :f] my-coll)
{:f 4, :e 6, :d 5, :c 1, :b 3, :a 2}
user=> (frequencies "Hello world!")
{\space 1, \! 1, \d 1, \e 1, \H 1, \l 3, \o 2, \r 1, \w 1}
See also: concat, interleave, cons, last, butlast
Sheer laziness
While Clojure is technically eager by default, most of the functions on collections operate lazily:
user=> (def my-coll [0 1 2 3 4 5 6])
#'user/my-coll
user=> (take 3 my-coll)
(0 1 2)
user=> (drop 2 my-coll)
(2 3 4 5 6)
user=> (partition 3 my-coll)
((0 1 2) (3 4 5))
user=> (partition-all 3 my-coll)
((0 1 2) (3 4 5) (6))
user=> (split-at 3 my-coll)
[(0 1 2) (3 4 5 6)]
user=> (range)
;; good luck with this one! (cancel with Ctrl-c)
user=> (range 5)
(0 1 2 3 4)
user=> (take 5 (range))
(0 1 2 3 4)
Higher order functions
Functions that accept or return functions
user=> (def names [{:forename "Henry" :surname "Garner"}
{:forename "James" :surname "Henderson"}])
#'user/names
user=> (defn full-name [{:keys [forename surname]}]
(str forename " " surname))
#'user/full-name
user=> (full-name (first names))
"Henry Garner"
user=> (map full-name names)
["Henry Garner" "James Henderson"]
Anonymous Functions
Used where you have a case for a single-use function that doesn’t warrant a name.
user=> (def names [{:forename "Henry" :surname "Garner"}
{:forename "James" :surname "Henderson"}])
#'user/names
user=> (defn full-name [forename surname]
(str forename " " surname))
#'user/full-name
user=> (map (fn [x] (full-name (:forename x) (:surname x))) names)
;; Equivalent to
user=> (map #(full-name (:forename %) (:surname %)) names)
Anonymous function arities
You can refer to multiple args by %1, %2, …
(fn [x y] (+ x y))
;; Equivalent to
#(+ %1 %2)
Other higher-order functions
Higher order functions can make use of functions.
user=> (update-in {:name "Henry" :age 30} [:age] inc)
{:name "Henry" :age 31}
Sequence-Sequence higher order functions
user=> (map inc [1 2 3 4])
(2 3 4 5)
user=> (filter even? [1 2 3 4 5 6])
(2 4 6)
user=> (sort-by count ["bb" "aaa" "c"]
("c" "bb" "aaa")
user=> (sort-by first > [[1 2] [2 2] [3 3]])
([3 3] [2 2] [1 2])
See also: mapcat, remove, partition-by
Sequence in > Something else out
user=> (reduce + [1 2 3])
6
user=> (group-by even? [1 2 3 4])
{false [1 3], true [2 4]}
Namespaces
In the REPL we get a ‘user’ namespace. In larger projects we like to split our code out into more namespaces.
We can refer to symbols in other namespaces.
(ns some.namespace
(:require [other.namespace :as blah]))
Leiningen’s project.clj
(defproject weather "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.5.1"]
[clj-http "0.7.7"]])
Your code goes here
src/weather/core.clj
Open up that file and remove the template function.
Add dependencies
(ns weather.core
(:require [clj-http.client :as http]))
Let’s use some real data
Free, JSON api that provides current weather data and forecasts.
Sample questions
- How many cities called London are there? (hint: find?q=London)
- What are the lat/long positions of all the Londons?
- What is the forecasted average temperature for London, UK for the last 5 days? (hint: forecast?q=London)
- What is the forecasted averages temperature of London, UK for the last 10 days?
- How many of the next 10 days is it forecasted to be cloudy?
- How many of the next 10 days is it forecasted not to be cloudy?