Clojure basics

85
CLOJURE BASICS Kyle Oba [email protected] @mudphone Tuesday, March 19, 13

Transcript of Clojure basics

Page 1: Clojure basics

CLOJURE BASICS

Kyle [email protected]@mudphone

Tuesday, March 19, 13

Page 2: Clojure basics

Clojure 1.5 was released on March 1st, 2013.

Read this to see changes:https://github.com/clojure/clojure/blob/master/changes.md

My examples use 1.4.0.

Tuesday, March 19, 13

Page 3: Clojure basics

“Startups tend to be an all or nothing proposition. You either get rich, or you get nothing. In a startup, if you bet on the wrong technology, your competitors will crush you.”

Paul Graham

Tuesday, March 19, 13

Page 4: Clojure basics

“When you choose technology, you have to ignore what other people are doing, and consider only what will work the best.”

Paul Graham

Tuesday, March 19, 13

Page 5: Clojure basics

“In a big company, you can do what all the other big companies are doing. But a startup can't do what all the other startups do. I don't think a lot of people realize this, even in startups.”

Paul Graham

Tuesday, March 19, 13

Page 6: Clojure basics

“So if you're running a startup, you had better be doing something odd. If not, you're in trouble.”

Paul Graham

Tuesday, March 19, 13

Page 7: Clojure basics

“Lisp is so great not because of some magic quality visible only to devotees, but because it is simply the most powerful language available. And the reason everyone doesn't use it is that programming languages are not merely technologies, but habits of mind as well, and nothing changes slower.”

Paul Graham

Tuesday, March 19, 13

Page 8: Clojure basics

“I'll begin with a shockingly controversial statement: programming languages vary in power.”

Paul Graham

Tuesday, March 19, 13

Page 10: Clojure basics

Tuesday, March 19, 13

Page 11: Clojure basics

Rich Hickey is a genius. And, I’m just here to piss you off.

Clojure Concurrencyhttp://blip.tv/clojure/clojure-concurrency-819147

22:00 - 23:18

Simple Made Easyhttp://www.infoq.com/presentations/Simple-Made-Easy

15:30 - 17:15

Tuesday, March 19, 13

Page 12: Clojure basics

Why you should take a look...

functional langs can more easily use multiple cores

purely functional is awkward when dealing with state

Clojure has refs, agents, atoms, dynamic binding

dynamic typing

Java invocation (not functional)

refs (STM)

JVM, calling Java is idiomatic

Tuesday, March 19, 13

Page 13: Clojure basics

CLOJURE BASICS

Why is Clojure so cool necessary?

Basic Setup x2 or 3

Language Basics

Working with State (or without it)

Working with government forms Java

Macros

Webby Stuff

Testing

Clojure + Processing + Kinect/OpenNI

Tuesday, March 19, 13

Page 14: Clojure basics

Why is Clojure necessary?

Tuesday, March 19, 13

Page 15: Clojure basics

Multi-Core Future

Tuesday, March 19, 13

Page 16: Clojure basics

Why use the JVM?

The JVM is advanced technology.

Java libraries... lots of them.

Widely deployed

Tuesday, March 19, 13

Page 17: Clojure basics

Using the JVMFor example: https://github.com/mmcgrana/clj-redis

(require '[clj-redis.client :as redis])

(def db (redis/init))

(redis/ping db)=> "PONG"

(redis/set db "foo" "BAR")=> "OK"

(redis/get db "foo")=> "BAR"

Tuesday, March 19, 13

Page 18: Clojure basics

JVMFor example: https://github.com/mmcgrana/clj-redis

(ns clj-redis.client (:import java.net.URI) (:import (redis.clients.jedis Jedis JedisPool JedisPoolConfig JedisPubSub)) (:require [clojure.string :as str]) (:refer-clojure :exclude [get set keys type]))

(def ^{:private true} local-url "redis://127.0.0.1:6379")

(defn init ([] (init {})) ([{:keys [url timeout test-on-borrow] :as opts}] (let [uri (URI. (or url local-url)) tout (or timeout 2000) host (.getHost uri) port (if (pos? (.getPort uri)) (.getPort uri) 6379) uinfo (.getUserInfo uri) pass (and uinfo (last (str/split uinfo #":"))) config (JedisPoolConfig.)] (when test-on-borrow (.setTestOnBorrow config test-on-borrow)) (JedisPool. config host port tout pass))) ([k1 v1 & {:as opts}] (init (assoc opts k1 v1))))

Tuesday, March 19, 13

Page 19: Clojure basics

JVMFor example: https://github.com/mmcgrana/clj-redis

(defn lease [^JedisPool p f] (let [j (.getResource p)] (try (f j) (finally (.returnResource p j)))))

(defn ping [p] (lease p (fn [^Jedis j] (.ping j))))

(defn get [p ^String k] (lease p (fn [^Jedis j] (.get j k))))

(defn set [p ^String k ^String v] (lease p (fn [^Jedis j] (.set j k v))))

Tuesday, March 19, 13

Page 20: Clojure basics

Why is Clojure a secret weapon?

http://www.paulgraham.com/avg.html

Tuesday, March 19, 13

Page 21: Clojure basics

Why is Clojure so cool?

Homoiconic

Modern LISP

Functional (but not purely functional)

Tuesday, March 19, 13

Page 22: Clojure basics

Homoiconic

Lisp code is just lisp data

Revenge of the Nerds - Paul Graham

Macros == black magic

Reprogramming the language with the language

Tuesday, March 19, 13

Page 23: Clojure basics

Modern

Fewer parentheses

Sequences

Reader macros for data structures

regex, map, set, vector, metadata

First-class data structures: [], {}, ‘()

Less Lipsy than other Lisps (ex: fns use [ ])

Commas are whitespace

Tuesday, March 19, 13

Page 24: Clojure basics

List Comprehension

user> (take 40 (for [x (range) :when (and (> (* 2 x) 99) (= 0 (mod x 3)))] (* 2 x)))(102 108 114 120 126 132 138 144 150 156162 168 174 180 186 192 198 204 210 216222 228 234 240 246 252 258 264 270 276282 288 294 300 306 312 318 324 330 336)

Tuesday, March 19, 13

Page 25: Clojure basics

Functional

Functions are first-class

Data is immutable

Functions are NOT pure

Tuesday, March 19, 13

Page 26: Clojure basics

Why Functional?

Simple

Thread-safe

Parallelizable

Generic

Tuesday, March 19, 13

Page 27: Clojure basics

Clojure’s approach to Functional Programming

Not purely functional

Has system for working with refs, agents, atoms, and dynamic binding

Dynamic typing

Java invocation is NOT functional

Tuesday, March 19, 13

Page 28: Clojure basics

“Mutable objects are the new spaghetti code.”- Rich Hickey

Tuesday, March 19, 13

Page 29: Clojure basics

Concurrency is difficult

Tuesday, March 19, 13

Page 30: Clojure basics

You can’t stop the world

http://clojure.org/concurrent_programming

http://blip.tv/clojure/clojure-concurrency-819147

http://www.infoq.com/presentations/Value-Values

Tuesday, March 19, 13

Page 31: Clojure basics

Concurrency is difficult

Persistent data structures

Programming with values

Immutable by default

Syntax for state:

Refs (STM), Agents, Atoms, Dynamic Vars

We’ll a bit about state later.

Tuesday, March 19, 13

Page 32: Clojure basics

Enough chit chat.

Tuesday, March 19, 13

Page 33: Clojure basics

Let’s get started

there are a few options

Emacs, Leiningen, nrepl

Emacs Live

LightTable

Tuesday, March 19, 13

Page 34: Clojure basics

Leiningen

Clojure automation...https://github.com/technomancy/leiningen

1) Download the script:

https://raw.github.com/technomancy/leiningen/stable/bin/lein

2) Place it on your $PATH. (I like to use ~/bin)

3) Set it to be executable. (chmod 755 ~/bin/lein)

Start a new project:$ lein new <project name>$ lein repl

Tuesday, March 19, 13

Page 35: Clojure basics

Emacs

Get version >= 24.X

Pick one (or more):

1) http://emacsformacosx.com

2) $ brew install emacs

3) https://github.com/overtone/emacs-live

Tuesday, March 19, 13

Page 36: Clojure basics

Emacs https://gist.github.com/mudphone/4698169= install lein 2- Do this: https://github.com/technomancy/leiningen- download lein script, it will boot strap itself- install on path, rename to lein, chmod 755- run lein --version Leiningen 2.0.0 on Java 1.6.0_37 Java HotSpot(TM) 64-Bit Server VM = Emacs >= 24- Download latest version: http://emacsformacosx.com, if you want the GUI version- Or, for CLI, use homebrew- Or, do both = Emacs Starter Kit- look at my init.el- install latest: https://github.com/technomancy/emacs-starter-kit- install clojure-mode package-install <ret> clojure-mode <ret>- install nrepl" package-install <ret> nrepl <ret> = nrepl (already installed)- docs here: https://github.com/kingtim/nrepl.el- at a minimum, check out the keybindings = nrepl auto-complete- https://github.com/purcell/ac-nrepl (popup docs keybinding didn't work for me, so I'm not using it right now) = REFS:- this isn't required reading, but it works: http://www.kedrovsky.com/blog/clojure-emacs-nrepl-and-leiningen

Tuesday, March 19, 13

Page 37: Clojure basics

Emacs

nREPL

M-x nrepl-jack-in

C-c M-n (to switch repl to this ns)

C-x C-c (eval buffer in repl)

M-C-x (eval form under point in repl)

C-c C-z (switch to repl buffer)

Tuesday, March 19, 13

Page 38: Clojure basics

Clojure Basics

Tuesday, March 19, 13

Page 39: Clojure basics

Getting Started

Symbols

Vars

Namespaces

use == require + refer

Help w/ doc, find-doc, source

Tuesday, March 19, 13

Page 40: Clojure basics

Forms

Prefix notation

false and nil evaluate to false

maps are functions of keys, and keys are functions of maps

defrecord

Tuesday, March 19, 13

Page 41: Clojure basics

defrecord

user> (defrecord Person [fname lname age favorites])user.Personuser> (defrecord Favorites [movie band])user.Favoritesuser> (def me (Person. "Kyle" "Oba" 37 (Favorites. "Lost in Translation" "Pantera")))#'user/meuser> (:fname me)"Kyle"

user> (-> me :favorites :movie)"Lost in Translation"

user> (assoc me :fname "That guy")#user.Person{:fname "That guy", :lname "Oba", :age 37, :favorites #user.Favorites{:movie "Lost in Translation", :band "Pantera"}}

user> (update-in me [:favorites :band] #(str "ZZ" % "ZZ"))#user.Person{:fname "Kyle", :lname "Oba", :age 37, :favorites #user.Favorites{:movie "Lost in Translation", :band "ZZPanteraZZ"}}

Tuesday, March 19, 13

Page 43: Clojure basics

Imperative programming

single-threaded premise

world is stopped

requires mutexes and locks

difficult to get right / extremely complicated

Tuesday, March 19, 13

Page 44: Clojure basics

Identity and Value

identity

value

name

Kyle

fav foods

{cheese donutsbacon}

today

March18th 2013

Tuesday, March 19, 13

Page 45: Clojure basics

Typical OO

has imperative programming baked into it

identities are conflated with values

not necessarily, but usually, due to the language

Tuesday, March 19, 13

Page 46: Clojure basics

Clojure’s approach

separates state from identity

move away from state as “the content of this memory block”

toward “the value currently associated with this identity”

identity is in different states at different times

but, the state at a point in time never changes value

Tuesday, March 19, 13

Page 47: Clojure basics

Clojure’s approach

vars - root binding, refers to mutable storage location

vars w/ dynamic binding - per-thread binding

atoms

agents, asynchronous change via a function and values

refs, coordinate change with STM: software transactional memory

Tuesday, March 19, 13

Page 48: Clojure basics

Actor Model (not Clojure)

distributed

more complex

see Erlang

Tuesday, March 19, 13

Page 49: Clojure basics

Working with State

atoms

refs

dynamic binding

acting at a distance / aspect oriented programming

loss of purity

memoize example

Tuesday, March 19, 13

Page 50: Clojure basics

Working with State

Tuesday, March 19, 13

Page 51: Clojure basics

vars

user> (def x 1)#'user/x

user> x1

user> (def y 1)#'user/y

user> (+ x y)2

allow reference to mutable storage locations

Tuesday, March 19, 13

Page 52: Clojure basics

dynamic binding

user> (def ^:dynamic x 1)#'user/x

user> (def ^:dynamic y 1)#'user/y

user> (+ x y)2

user> (binding [x 2 y 3] (+ x y))5

user> (+ x y)2

Tuesday, March 19, 13

Page 53: Clojure basics

Changing State async synchronous

coordinated

independent atom

ref

agent

Tuesday, March 19, 13

Page 54: Clojure basics

atoms

user> (def z (atom 5))#'user/z

user> z#<Atom@f2882ad: 5>

user> @z5

user> (swap! z inc)6

user> @z6

user> (swap! z (constantly 10))10

user> @z10

shared, synchronous, independent state

Tuesday, March 19, 13

Page 55: Clojure basics

agents independent, asynchronous change

user> (def a (agent 1))#'user/a

user> a#<Agent@474d75ae: 1>

user> (send a inc)#<Agent@474d75ae: 2>

user> a#<Agent@474d75ae: 2>

user> @a2

user> (send a #(do (Thread/sleep 5000) (+ % 1)))#<Agent@474d75ae: 2>

user> @a2

user> @a3

Tuesday, March 19, 13

Page 56: Clojure basics

refs (transactional references)shared use of mutable storage via STM

user> (def r (ref [1 2 3]))#'user/r

user> r#<Ref@369ca84f: [1 2 3]>

user> @r[1 2 3]

(dosync (alter r #(conj % 4)))[1 2 3 4]

user> @r[1 2 3 4]

user> (dosync (alter r (partial cons 0)))(0 1 2 3 4)

user> @r(0 1 2 3 4)

Tuesday, March 19, 13

Page 57: Clojure basics

Java Interop

Tuesday, March 19, 13

Page 58: Clojure basics

Working with Java

Calling Java

Java interop

Java collections

Tuesday, March 19, 13

Page 59: Clojure basics

Java: Examples

user> (System/getProperty "java.vm.version")"20.14-b01-447"

user> (.toUpperCase "fred")"FRED"

user> (.getName String)"java.lang.String"

user> Math/PI3.141592653589793

Tuesday, March 19, 13

Page 60: Clojure basics

Java: import and .

user> (import '[java.util Random])java.util.Random

user> (def r (Random.))#'user/r

user> (.nextInt r)458809484

user> (.nextInt r 100)42

Tuesday, March 19, 13

Page 61: Clojure basics

Java: doto

user> (doto (new java.util.HashMap) (.put "a" 1) (.put "b" 2))#<HashMap {b=2, a=1}>

Tuesday, March 19, 13

Page 62: Clojure basics

Java: set!

user> (def p (java.awt.Point.))#'user/p

user> (.x p)0

user> (set! (. p x) 5)5

user> p#<Point java.awt.Point[x=5,y=0]>

Tuesday, March 19, 13

Page 63: Clojure basics

Macros

Tuesday, March 19, 13

Page 64: Clojure basics

What’s a Macro?

#!/usr/bin/env ruby -wsay_hello = "puts \"hello\""

ruby_cmd = (0..9).map do |n| say_helloend

puts "ruby -e '#{ruby_cmd.join(";")}'"

$ ruby ruby_macro.rb | shhellohellohellohellohellohellohellohellohellohello

What if we could do this, while staying in the language?

Tuesday, March 19, 13

Page 65: Clojure basics

Ruby-like “unless” implementation as a function...

This won’t work, because arguments are evaluated.

(defn unless [expr form] (if expr nil form))

user> (unless (= 1 1) (println "hi"))hinil

Tuesday, March 19, 13

Page 66: Clojure basics

Arguments are evaluated first

(defn unless [expr form] (println "Doing unless...") (if expr nil form))

user> (unless (= 1 1) (println "hi"))hiDoing unless...nil

Tuesday, March 19, 13

Page 67: Clojure basics

What we want

(unless expr form) -> (if expr nil form)

Tuesday, March 19, 13

Page 68: Clojure basics

What we want

(defmacro unless [expr form] (list 'if expr nil form))

user> (unless true (println "yep"))nil

user> (unless false (println "yep"))yepnil

Tuesday, March 19, 13

Page 69: Clojure basics

macroexpand-1

user> (macroexpand-1 '(when-not true (println "hi")))(if true nil (do (println "hi")))

user> (source when-not)(defmacro when-not "Evaluates test. If logical false, evaluates body in an implicit do." {:added "1.0"} [test & body] (list 'if test nil (cons 'do body)))nil

Tuesday, March 19, 13

Page 70: Clojure basics

WAT

reader

compile

macros

text

data structures

bytecode

macrosevaluation

Tuesday, March 19, 13

Page 71: Clojure basics

macroexpand

user> (.. System (getProperties) (get "os.name"))"Mac OS X"

user> (macroexpand-1 '(.. System (getProperties) (get "os.name")))(.. (. System (getProperties)) (get "os.name"))

user> (macroexpand '(.. System (getProperties) (get "os.name")))(. (. System (getProperties)) (get "os.name"))

Tuesday, March 19, 13

Page 72: Clojure basics

Java Interop Bonus

user> (source ..)(defmacro .. "form => fieldName-symbol or (instanceMethodName-symbol args*)

Expands into a member access (.) of the first member on the first argument, followed by the next member on the result, etc. For instance:

(.. System (getProperties) (get \"os.name\"))

expands to:

(. (. System (getProperties)) (get \"os.name\"))

but is easier to write, read, and understand." {:added "1.0"} ([x form] `(. ~x ~form)) ([x form & more] `(.. (. ~x ~form) ~@more)))nil

Tuesday, March 19, 13

Page 73: Clojure basics

One more macro...

user> (source and)(defmacro and "Evaluates exprs one at a time, from left to right. If a form returns logical false (nil or false), and returns that value and doesn't evaluate any of the other expressions, otherwise it returns the value of the last expr. (and) returns true." {:added "1.0"} ([] true) ([x] x) ([x & next] `(let [and# ~x] (if and# (and ~@next) and#))))nil

Tuesday, March 19, 13

Page 74: Clojure basics

“Webby Stuff”

Tuesday, March 19, 13

Page 75: Clojure basics

Areas of Interest:

ClojureScript to any old websitehttp://blip.tv/clojure/rich-hickey-unveils-clojurescript-5399498

ClojureScript + Three.js, to get at the WebGL bits

ClojureScript embedded in game systems

ClojureScript CLI

Pedestal: http://pedestal.io/documentation/hello-world-service/

It runs on Node.js.

Tuesday, March 19, 13

Page 76: Clojure basics

ClojureScript$ lein cljsbuild onceCompiling ClojureScript.Compiling "resources/public/hello.js" from "src/cljs"...Successfully compiled "resources/public/hello.js" in 8.542318 seconds.

$ tree.├── README.md├── doc│   └── intro.md├── docroot│   └── index.html├── project.clj├── resources│   └── public│   ├── hello.js│   └── index.html├── src│   ├── clj│   │   └── hello_world│   │   └── core.clj│   └── cljs│   └── hello.cljs└── test └── hello_world └── core_test.clj

10 directories, 9 files

Tuesday, March 19, 13

Page 77: Clojure basics

Pedestal$ tree.├── README.md├── config│   └── logback.xml├── dev│   ├── dev.clj│   └── user.clj├── logs│   └── helloworld-2013-03-18.0.log├── project.clj├── src│   └── helloworld│   ├── server.clj│   └── service.clj├── target│   ├── classes│   └── stale│   └── extract-native.dependencies└── test └── helloworld └── service_test.clj

10 directories, 10 files

$ lein replnREPL server started on port 63132...helloworld.server=> (use 'dev)nilhelloworld.server=> (start)nilhelloworld.server=> (stop)nilhelloworld.server=> Bye for now!

http://pedestal.io/documentation/hello-world-service/

Tuesday, March 19, 13

Page 78: Clojure basics

Pedestalhttp://pedestal.io/documentation/application-introduction/

(defn count-model [old-state message] (condp = (msg/type message) msg/init (:value message) :inc (inc old-state)))

(defmulti render (fn [& args] (first args)))

(defmethod render :default [_] nil)

(defmethod render :value [_ _ old-value new-value] (dom/destroy-children! (dom/by-id "content")) (dom/append! (dom/by-id "content") (str "<h1>" new-value " Hello Worlds</h1>")))

(defn render-fn [deltas input-queue] (doseq [d deltas] (apply render d)))

(def count-app {:models {:count {:init 0 :fn count-model}}})

(defn receive-input [input-queue] (p/put-message input-queue {msg/topic :count msg/type :inc}) (.setTimeout js/window #(receive-input input-queue) 3000))

(defn ^:export main [] (let [app (app/build count-app)] (render/consume-app-model app render-fn) (receive-input (:input app)) (app/begin app)))

(ns hello-world(:require [io.pedestal.app.protocols :as p] [io.pedestal.app :as app] [io.pedestal.app.messages :as msg] [io.pedestal.app.render :as render] [domina :as dom]))

Tuesday, March 19, 13

Page 79: Clojure basics

Pedestalhttp://pedestal.io/documentation/application-introduction/

(ns hello-world (:require [io.pedestal.app.protocols :as p] [io.pedestal.app :as app] [io.pedestal.app.messages :as msg] [io.pedestal.app.render :as render] [io.pedestal.app.render.push :as push] [domina :as dom]))

(defn count-model [old-state message] (condp = (msg/type message) msg/init (:value message) :inc (inc old-state)))

(defn render-value [renderer [_ _ old-value new-value] input-queue] (dom/destroy-children! (dom/by-id "content")) (dom/append! (dom/by-id "content") (str "<h1>" new-value " Hello Worlds</h1>")))

(def count-app {:models {:count {:init 0 :fn count-model}}})

(defn receive-input [input-queue] (p/put-message input-queue {msg/topic :count msg/type :inc}) (.setTimeout js/window #(receive-input input-queue) 3000))

(defn ^:export main [] (let [app (app/build count-app) render-fn (push/renderer "content" [[:value [:*] render-value]])] (render/consume-app-model app render-fn) (receive-input (:input app)) (app/begin app)))

Tuesday, March 19, 13

Page 80: Clojure basics

Testing

Tuesday, March 19, 13

Page 81: Clojure basics

(ns oudl.core-test (:use clojure.test oudl.core))

(deftest a-test (testing "FIXME, I fail." (is (= 0 1))))

user> (require '[clojure.test :as test])nil

user> (test/run-all-tests #"oudl.*-test")

Testing oudl.core-test

FAIL in (a-test) (core_test.clj:7)FIXME, I fail.expected: (= 0 1) actual: (not (= 0 1))

Ran 1 tests containing 1 assertions.1 failures, 0 errors.{:type :summary, :pass 0, :test 1, :error 0, :fail 1}

Free!

Tuesday, March 19, 13

Page 82: Clojure basics

(require ‘[clojure.test :as test])

user> (doc test/run-all-tests)-------------------------clojure.test/run-all-tests([] [re]) Runs all tests in all namespaces; prints results. Optional argument is a regular expression; only namespaces with names matching the regular expression (with re-matches) will be tested.nil

user> (doc test/run-tests)-------------------------clojure.test/run-tests([] [& namespaces]) Runs all tests in the given namespaces; prints results. Defaults to current namespace if none given. Returns a map summarizing test results.nil

Tuesday, March 19, 13

Page 83: Clojure basics

Things I Like

Tuesday, March 19, 13

Page 84: Clojure basics

Some things

Clojure + Processing = Quil

Clojure + Kinect/OpenNI = Bifocals

LightTable

Tuesday, March 19, 13

Page 85: Clojure basics

Tuesday, March 19, 13