'Getting' Clojure – '(Parentheses are just hugs for your code)



'Getting' Clojure – '(Parentheses are just hugs for your code)

0 0


bohconf.clojure


On Github canweriotnow / bohconf.clojure

'Getting' Clojure

'(Parentheses are just hugs for your code)

Created by Jason Lewis / Gary Trakhman

Functions

Javascript
                        function(){
                        return 5;
                        }
                    
Put some parens around it, kill the braces
                        (function()
                        return 5;
                        )
                    
Change 'function' to 'fn', makes args into a vector
                        (fn []
                        return 5;
                        )
                    
Kill the 'return', last thing's always returned.
                        (fn [] 5)
                    
Welcome to Clojure.

Calling Stuff

                        someFunction(arg1, arg2, arg3);
                    
Move the left parenthesis over a bit more...
                        (someFunction arg1 arg2 arg3)
                    
Done.

This isn't an accident

Put another way...

  • Q: Why do you think we've gotten so much mileage out of javascript?
  • A: Lisp is very powerful, and it will never die

Data

Should look familiar
                        {:key1 5,
                        :key2 nil}

                        [1 2 3 4 "five"]
                    
Don't freak out
                        [1 [2] #{3} {4 4} (constantly 5)]
                    
DON'T FREAK OUT
                        => (range 10)
                        (0 1 2 3 4 5 6 7 8 9)

                        => (take 11 (range))
                        (0 1 2 3 4 5 6 7 8 9 10)

                        => (last (range)) ;;Hope you don't mind waiting a long time.
                    

Everything is Data

                        ;; semicolons are comments, commas are ignored,
                        ;; check out this weird hash-map
                        {:a-keyword 5,

                        "a string key" "a string value",

                        ["a" :vector "acting" :as [:a :compound] "key"]

                        (fn [] "a no-arg function
                        that returns this multi-line string,
                        the function itself is the value"),

                        + '(functions can be keys too, and when
                        you quote symbols, you just
                        have symbols, not what they represent)}
                    
Evals to...
                        {:a-keyword 5, "a string key" "a string value",
                        ["a" :vector "acting" :as [:a :compound] "key"]
                        #<user$eval331$fn__332 user$eval331$fn__332@a585ef>,
                        #<core$_PLUS_ clojure.core$_PLUS_@20a12d8f>
                        (functions can be keys too and when you quote symbols
                        you just have symbols not what they represent)}
                    

Anything can be a key, because

Every object is also a 'value' Values have true equality Values Never Change (Immutability) Without immutability, objects are just buckets in memory ...have you ever trusted a bucket with no values?
  • Q: Why is this big news?
  • A: I can write code and rest assured that other parts of my program can't change the data that I'm working on.
  • Q: But I thought every program is simply a short-lived http request handler that talks to a database? We just throw the program state out after every request!
  • A: Well, that's one way to do it.
http://www.ibm.com/developerworks/library/wa-aj-multitier2/

Node.js...

http://www.andrerodrigues.me/isel-workshop/intro.html#/24

Node.js... is nothing new

  • We can write our own loops
  • Node.js assumes threaded programming is hard, and throws out the baby with the bath-water
  • Threaded programming is hard without real 'Data' or 'Values'
  • Composition of any sort is simpler with data

Approximating Node.js

  • 'Agents' are asynchronous queues, sharing threadpools to do work, storing the last value returned.
                            (defn inc-last [val]
                            (conj val (inc (last val))))

                            ;; We make a sequence of 10 inc-last tasks,
                            ;; then follow-up with a 'println' task
                            (def tasks
                            (concat (repeat 10 inc-last)
                            [(fn [val]
                            (println val)
                            val)]))
                        
                        ;; starts off with a value of [0]
                        (let [a (agent [0])]
                        (doseq [t tasks]
                        (send a t)))

                        ;; prints: [0 1 2 3 4 5 6 7 8 9 10]
                    
  • Agents are not values, they are mutable references with asynchronous semantics
  • Clojure has other mutable references types, acting as 'containers' for values, for various use cases.
  • Nothing prevents you from making your own.

MORE!

                        (let [f (future (do-a-bunch-of-stuff))] ;; in another thread
                        (do-stuff-in-this-thread)
                        ;; return the value in f, blocking if it's not finished
                        (deref f))
                    
  • Basically,
  • Clojure promotes your ability to do whatever you want, by simplifying things to their bare essence.

What We Really Want

Tools that let us Compose Systems Change our minds Re-use components in different contexts, processes, servers, etc.. Data/Values give us the ability to decouple things easily

Brainsplode

'(code is data)

R-E-P-L

Read-Eval-Print-Loop Read: (read-string "(+ 1 2)") => '(+ 1 2) Eval: (eval '(+ 1 2)) => 3 What if there's something in the middle?
                        (class (read-string "(+ 1 2)"))
                        ;; clojure.lang.PersistentList

                        (map class (read-string "(+ 1 2)"))
                        ;; (clojure.lang.Symbol java.lang.Long java.lang.Long)
                    
                        (defn only-even!
                        [val]
                        (if (and (integer? val) (odd? val))
                        (inc val)
                        val))

                        (map only-even! (read-string "(+ 1 2)"))
                        ;; '(+ 2 2)

                        (eval (map only-even! (read-string "(+ 1 2)")))
                        ;; 4
                    
This is only the beginning
Everybody likes chaining, right?
                        $("#p1").css("color","red").slideUp(2000).slideDown(2000);
                    
How is this implemented? Is this reusable?
What if, as a library author, you could just not write that fluent interface code at all?
                        (use 'clojure.string)

                        ;; These are equivalent

                        (map trim (split (upper-case "hola, world") #","))
                        ;; ("HOLA" "WORLD")

                        (-> "hola, world"
                        upper-case
                        (split #",")
                        (->> (map trim)))
                        ;; ("HOLA" "WORLD")
                    
Really useful when you're doing a lot of collection operations, filtering, etc.
                        (->> (range)
                        (filter even?)
                        (map (partial * 2))
                        (take 10)
                        (into []))
                        ;; [0 4 8 12 16 20 24 28 32 36]

                        ;; versus
                        (into []
                        (take 10 (map (partial * 2)
                        (filter even? (range)))))
                    
I find the flat one easier to think about. Semantically equivalent. No burden on implementing code. Functions don't care about how they're used.

Giving the user choices is more effective with more powerful languages. Leads to simple, composable libraries.

Macros

Let's look at a real one.
                        (defmacro lazy-seq
                        "Takes a body of expressions that returns an ISeq or nil, and yields
                        a Seqable object that will invoke the body only the first time seq
                        is called, and will cache the result and return it on all subsequent
                        seq calls. See also - realized?"
                        {:added "1.0"}
                        [& body]
                        (list 'new 'clojure.lang.LazySeq (list* '^{:once true} fn* [] body)))

                        ;; simply returns a list, allocates a Java object (LazySeq) and wraps
                        ;; your expressions in a function

                        (macroexpand-1 '(lazy-seq ANYTHING1 ANYTHING2))
                        ;; '(new clojure.lang.LazySeq (fn* [] ANYTHING1 ANYTHING2))

                    
Let's create an infinite sequence representing a square-wave --__--__--__--__
                        (defn square-wave
                        "t is the period for a half-cycle"
                        [t]
                        (letfn
                        [(osc [cur-value so-far]
                        (let [so-far (mod so-far t)
                        next-val (if (zero? so-far)
                        (- cur-value)
                        cur-value)]
                        (cons next-val
                        (lazy-seq (osc next-val
                        (inc so-far))))))]
                        (osc 1 0)))
                    
                        (take 10 (square-wave 3))
                        ;; (-1 -1 -1 1 1 1 -1 -1 -1 1)
                    
No mutable variables

Call to Action

Learn Clojure Build cool things Screencasts!
(You ruby guys really know how to make good screencasts)

Demo Time

Clojure on the Web

Resources

Fun Exercises: http://www.4clojure.com
Community docs: http://clojuredocs.org

MORE Demo Time

Thanks for coming!

We are:

Gary Trakhman

@gtrakGT

Software Engineer at Revelytix, Inc.

Jason Lewis

@canweriotnow

CTO at An Estuary, LLC