Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
782 lines (592 sloc) 15.8 KB

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

http://openweathermap.org/API

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?