Clojure basics

Post on 06-May-2015

971 views 5 download

Tags:

transcript

CLOJURE BASICS

Kyle Obakoba@pasdechocolat.com@mudphone

Tuesday, March 19, 13

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

“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

“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

“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

“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

“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

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

Paul Graham

Tuesday, March 19, 13

Tuesday, March 19, 13

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

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

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

Why is Clojure necessary?

Tuesday, March 19, 13

Multi-Core Future

Tuesday, March 19, 13

Why use the JVM?

The JVM is advanced technology.

Java libraries... lots of them.

Widely deployed

Tuesday, March 19, 13

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

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

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

Why is Clojure a secret weapon?

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

Tuesday, March 19, 13

Why is Clojure so cool?

Homoiconic

Modern LISP

Functional (but not purely functional)

Tuesday, March 19, 13

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

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

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

Functional

Functions are first-class

Data is immutable

Functions are NOT pure

Tuesday, March 19, 13

Why Functional?

Simple

Thread-safe

Parallelizable

Generic

Tuesday, March 19, 13

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

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

Tuesday, March 19, 13

Concurrency is difficult

Tuesday, March 19, 13

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

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

Enough chit chat.

Tuesday, March 19, 13

Let’s get started

there are a few options

Emacs, Leiningen, nrepl

Emacs Live

LightTable

Tuesday, March 19, 13

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

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

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

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

Clojure Basics

Tuesday, March 19, 13

Getting Started

Symbols

Vars

Namespaces

use == require + refer

Help w/ doc, find-doc, source

Tuesday, March 19, 13

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

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

Imperative programming

single-threaded premise

world is stopped

requires mutexes and locks

difficult to get right / extremely complicated

Tuesday, March 19, 13

Identity and Value

identity

value

name

Kyle

fav foods

{cheese donutsbacon}

today

March18th 2013

Tuesday, March 19, 13

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

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

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

Actor Model (not Clojure)

distributed

more complex

see Erlang

Tuesday, March 19, 13

Working with State

atoms

refs

dynamic binding

acting at a distance / aspect oriented programming

loss of purity

memoize example

Tuesday, March 19, 13

Working with State

Tuesday, March 19, 13

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

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

Changing State async synchronous

coordinated

independent atom

ref

agent

Tuesday, March 19, 13

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

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

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

Java Interop

Tuesday, March 19, 13

Working with Java

Calling Java

Java interop

Java collections

Tuesday, March 19, 13

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

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

Java: doto

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

Tuesday, March 19, 13

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

Macros

Tuesday, March 19, 13

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

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

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

What we want

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

Tuesday, March 19, 13

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

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

WAT

reader

compile

macros

text

data structures

bytecode

macrosevaluation

Tuesday, March 19, 13

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

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

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

“Webby Stuff”

Tuesday, March 19, 13

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

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

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

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

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

Testing

Tuesday, March 19, 13

(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

(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

Things I Like

Tuesday, March 19, 13

Some things

Clojure + Processing = Quil

Clojure + Kinect/OpenNI = Bifocals

LightTable

Tuesday, March 19, 13

Tuesday, March 19, 13