class: center, middle # Hacking In Clojure ### Farmlogs Backend Team --- # Brief Intro to Clojure/Lisp * Normally, you call functions this way ```python foo(1, 3, 4) ``` * Now, move that leftmost parenthesis a little _more_ to the left: ```clojure (foo 1 2 3) ; that wasn't so hard! ``` * Anything of that form is called an "s-expression": ```clojure (whatever-you-are-doing arg1 arg2 ...) ; those dots are not valid clojure ``` In lisps, *everything* is an s-expression... (continued on next slide) --- # S-Expression Examples * Defining things ```clojure (def message "Hello World") (defn add-two-things "Obligatory useless addition example" [x y] (+ x y)) ``` * Control statements ```clojure (if (predicate? item) (do-something item) (do-something-else item)) (while true (do-something-endlessly)) ``` That's it! --- # How to Server in Clojure: Ring * From the [Github Page](https://github.com/ring-clojure/ring) > Ring is a Clojure web applications library inspired by Python's WSGI and Ruby's Rack * A Ring application is just a clojure function: ```clojure (defn app [request] {:status 200 :headers {"Content-Type" "text/html"} :body "Hello World"}) ``` * To run the app, you plug the function into some magic java server adapter which handles the actual requesting and responsing * Very simple, and (as we'll soon see), *very* powerful --- # First, "Higher Order Functions" * Any function that takes another function as an argument and/or returns a function as its result * The most well-known examples are the classic collection processing functions: `map`, `filter`, `reduce` etc. ```clojure (map inc [1 2 3]) ; => (2 3 4) (filter pos? [-8 -2 1 9 3]) ; => (1 9 3) (reduce + [1 2 3]) ; => 6 ``` * function composition and partial application: ```clojure (def filter-then-count (comp count filter)) (filter-then-count neg? [-3 -2 -1 1 2 3]) ; => 3 (def add-two (partial + 2)) (add-two 5) ; => 7 ``` --- # Back to Ring: Middleware Remember our simple app? ```clojure (defn app [request] {:status 200 :headers {"Content-Type" "text/html"} :body "Hello World"}) ``` "Hello World" is pretty sensitive info, so we should authenticate every request: ```clojure (defn get-cookie [name request] ...) ; not a valid clojure function (defn wrap-authentication [handler] (fn [request] (let [session (get-cookie "session" request)] (if (= "super-secret-session-id" session) (handler request) {:status 403 :body "You're not allowed!"})))) (def secure-app (wrap-authentication app)) ``` We can create an authenticated web app from our old web app using higher-order functions! --- # Obligatory Lisp Macro Slide(s) Lisps have a proud tradition of utilizing the power of _macros_, which you can think of as "functions" (not technically functions) that manipulate code at compile time instead of data at runtime For example, Clojure has a `time` macro takes some code, and returns some new code that prints the amount of time that code took to execute: ```clojure (time (reduce + [1 2 3 4])) ; prints something like "Elapsed time: 0.038252 msecs", returns 10 ``` The approximate source code of `time` (if you dare!) ```clojure (defmacro time [expr] `(let [start# (. System (nanoTime)) ret# ~expr] (prn (str "Elapsed time: " (/ (double (- (. System (nanoTime)) start#)) 1000000.0) " msecs")) ret#)) ``` --- # Fancier Macros Macros are especially useful for extending or modifying language syntax. Would you rather write this? ```clojure (defn arbitrary-arithmetic [x y z] (/ (* 2 y) (+ z (- x y)))) ``` Or this? ```clojure (defn arbitrary-arithmetic [x y z] ($= (2 * y) / (z + x - y))) ``` Thanks to the `$=` macro from [Incanter](http://incanter.org/) (a clojure math and stats library), you can write math the way your (or most people's) brains think. The possiblities are endless! --- # Testing Clojure Apps Testing is important. By default, all Clojure applications have access to a nice set of macros in `clojure.test` ```clojure (defn set-cookie [name value request] ...) ; another fake cookie function (def request { }) ; empty map, doesn't really matter for this toy app (def authed-request (set-cookie "session" "super-secret-session-id" request)) (deftest security-policy (let [good-response (secure-app authed-request) ; remember that ring apps are just functions! bad-response (secure-app request)] (is (= 200 (:status good-response))) (is (= "Hello World" (:body good-response))) (is (= 403 (:status bad-response))) (is (= "You're not allowed!" (:body bad-response))))) ``` Because `is` is a macro, when the test fails, it can tell you exactly what the code was supposed to look like. --- # Real World Example! Macros + Testing = Win If you've ever tested anything in real life, you've probably written code like this: ```python initialize_lots_of_fake_data() # this function is usually super annoying to write run_tests() delete_fake_data() # same with this one ``` At Farmlogs, we wrote a macro to automate the annoying parts ```clojure (deftest test-database-stuff (with-data [data [data-model-1] more-data [data-model-2]] (do-test-stuff data more-data)))) ``` `with-data` initializes data with the given types in your local database, then cleans it up for you automatically when whatever code inside it is done. It's saved us a *ton* of time in test writing. --- # Extra Credit We've only just scratched the surface of the wonderful world of Clojure, there are so many other things to learn about, such as: # (Everyone should add stuff here) * Persistent data structures, lazy sequences, and other functional programming goodies * Compojure, a set of macros for defining routes in real apps ```clojure (defroutes app (GET "/" [] "Hello World") (POST "/data" request (post-some-data request)) (route/not-found "Page not found")) ``` * [Every talk](http://thechangelog.com/rich-hickeys-greatest-hits/) by Rich Hickey, creator of Clojure * [ClojureScript](https://github.com/clojure/clojurescript), Clojure that runs on top of JavaScript instead of the JVM --- class: center, middle # Thank You!