Last active
August 29, 2015 14:21
-
-
Save bhb/0d70a8cada57bb54b91b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; gorilla-repl.fileformat = 1 | |
;; ** | |
;;; # "SECRETS"* of Clojure development | |
;;; | |
;;; \* one or more tips may not be a secret to you! | |
;;; | |
;;; For months after getting started with Clojure, I understood how to write functions, but not how to **develop programs**. I did have a good grasp of the processes, idioms, and ways to structure larger code bases. As a result, I largely developed programs the same way I did with Ruby: using TDD with lots of implicit state strewn throughout my programs. | |
;;; | |
;;; I'm still learning, but I want to share a few of the key ideas I wish I had known when I first started out. | |
;;; | |
;; ** | |
;; ** | |
;;; ## REPL driven development | |
;;; | |
;;; Always try to build and explore code in a REPL! | |
;;; | |
;;; * single biggest boost to productivity | |
;;; * encourages building pure functions | |
;;; * make sure your editor can send eval any expression in REPL! | |
;;; * try to build REPL helpers to allow you to reload code & restart entire system from scratch (see `tools.namespace` and [`component`](https://github.com/stuartsierra/component)) | |
;;; * warning: watch out for functions that used to be defined but no longer are! | |
;;; | |
;;; | |
;;; ### DEMO | |
;;; | |
;;; http://blog.jayfields.com/2014/01/repl-driven-development.html | |
;;; | |
;; ** | |
;; @@ | |
(ns unsightly-foliage | |
(:require [gorilla-plot.core :as plot] | |
[clojure.repl :refer :all])) | |
;; @@ | |
;; => | |
;;; {"type":"html","content":"<span class='clj-nil'>nil</span>","value":"nil"} | |
;; <= | |
;; @@ | |
;; Handy REPL functions (in `clojure.repl`) | |
(doc filter) | |
;; @@ | |
;; -> | |
;;; ------------------------- | |
;;; clojure.core/filter | |
;;; ([pred coll]) | |
;;; Returns a lazy sequence of the items in coll for which | |
;;; (pred item) returns true. pred must be free of side-effects. | |
;;; | |
;; <- | |
;; => | |
;;; {"type":"html","content":"<span class='clj-nil'>nil</span>","value":"nil"} | |
;; <= | |
;; @@ | |
(source filter) | |
;; @@ | |
;; -> | |
;;; (defn filter | |
;;; "Returns a lazy sequence of the items in coll for which | |
;;; (pred item) returns true. pred must be free of side-effects." | |
;;; {:added "1.0" | |
;;; :static true} | |
;;; ([pred coll] | |
;;; (lazy-seq | |
;;; (when-let [s (seq coll)] | |
;;; (if (chunked-seq? s) | |
;;; (let [c (chunk-first s) | |
;;; size (count c) | |
;;; b (chunk-buffer size)] | |
;;; (dotimes [i size] | |
;;; (when (pred (.nth c i)) | |
;;; (chunk-append b (.nth c i)))) | |
;;; (chunk-cons (chunk b) (filter pred (chunk-rest s)))) | |
;;; (let [f (first s) r (rest s)] | |
;;; (if (pred f) | |
;;; (cons f (filter pred r)) | |
;;; (filter pred r)))))))) | |
;;; | |
;; <- | |
;; => | |
;;; {"type":"html","content":"<span class='clj-nil'>nil</span>","value":"nil"} | |
;; <= | |
;; @@ | |
;; RUNNING SPECIFIC TESTS IN REPL | |
(require '[clojure.test :refer [deftest is]]) | |
(deftest test-foo | |
(is (= 1 1))) | |
(deftest test-bar | |
(is (= 2 1))) | |
(clojure.test/test-vars [#'test-foo #'test-bar]) | |
;; @@ | |
;; -> | |
;;; | |
;;; FAIL in (test-bar) (form-init4989848549826252691.clj:9) | |
;;; expected: 2 | |
;;; actual: 1 | |
;;; diff: - 2 | |
;;; + 1 | |
;;; | |
;; <- | |
;; => | |
;;; {"type":"html","content":"<span class='clj-nil'>nil</span>","value":"nil"} | |
;; <= | |
;; @@ | |
;; COMMENTS | |
;; This is a comment | |
(+ 1 1 ) ; So is this, but it may format weird, so only use it on end of line | |
;; You can also comment out a sexp, although the value inside must be a valid sexp | |
(str "foo" (comment "this code is commented, but the result will be nil") "bar") | |
(+ 3 4 #_(+ "this code will be skipped") #_5 6) | |
;; @@ | |
;; => | |
;;; {"type":"html","content":"<span class='clj-long'>18</span>","value":"18"} | |
;; <= | |
;; @@ | |
;; THREADING OPERATORS (-> ->> and as->) for readability | |
;; -> is "thread-first" | |
(require '[clojure.string :as str]) | |
(first (str/split (str/upper-case "a b c") #" ")) | |
;; same as | |
(-> "a b c" | |
(str/upper-case) | |
(str/split #" ") | |
(first)) | |
;; always inserts last return in first position like this: | |
(-> "a b c" | |
(str/upper-case #_LAST-RESULT) | |
(str/split #_LAST-RESULT #" ") | |
(first #_LAST-RESULT)) | |
;; ->> puts things in the LAST position e.g. | |
(reverse (sort (filter even? (map inc [1 2 3 4 5])))) | |
;; is the same as | |
(->> [1 2 3 4 5] | |
(map inc) | |
(filter even?) | |
(sort) | |
(reverse)) | |
;; you can think of it like | |
(->> [1 2 3 4 5] | |
(map inc #_LAST-RESULT) | |
(filter even? #_LAST-RESULT) | |
(sort #_LAST-RESULT) | |
(reverse #_LAST-RESULT)) | |
;; BONUS - if you need first/last position, you can use as->, e.g. | |
(as-> "a b c" $ ;; $ is any symbol you want | |
(str/upper-case $) | |
(str/split $ #" ") | |
(map #(str % %) $)) | |
;; (.. (UIScreen/getMainScreen) (getBounds) (getSize) (getWidth)) | |
(comment | |
(doto | |
(FakeObj. "x") | |
(.setFoo "x") | |
(.setBar "x") | |
)) | |
;; @@ | |
;; => | |
;;; {"type":"html","content":"<span class='clj-nil'>nil</span>","value":"nil"} | |
;; <= | |
;; @@ | |
;; GOTCHAS | |
;; `contains` means does this collection contain this KEY! | |
(contains? {:a 1 :b 2} :a) | |
;; @@ | |
;; => | |
;;; {"type":"html","content":"<span class='clj-unkown'>true</span>","value":"true"} | |
;; <= | |
;; @@ | |
(contains? #{:a :b} :a) | |
;; @@ | |
;; => | |
;;; {"type":"html","content":"<span class='clj-unkown'>true</span>","value":"true"} | |
;; <= | |
;; @@ | |
(contains? [:a :b] :a) ;; false, because vectors are keyed by integers!!! | |
(contains? [:a :b] 1) | |
;; @@ | |
;; => | |
;;; {"type":"html","content":"<span class='clj-unkown'>true</span>","value":"true"} | |
;; <= | |
;; @@ | |
;; `case` clauses must be CONSTANTS | |
(let [x 4] | |
(case x | |
3 "picked 3" | |
4 "matched" | |
;; default | |
"no match")) | |
;; @@ | |
;; => | |
;;; {"type":"html","content":"<span class='clj-string'>"matched"</span>","value":"\"matched\""} | |
;; <= | |
;; @@ | |
(let [x 4 | |
y 4] | |
(case x | |
3 "picked 3" | |
y "matched" | |
;; default | |
"no match")) | |
;; @@ | |
;; => | |
;;; {"type":"html","content":"<span class='clj-string'>"no match"</span>","value":"\"no match\""} | |
;; <= | |
;; @@ | |
;; If you want to match on values, use `cond` or `condp` | |
;; @@ | |
;; @@ | |
(let [x 4 | |
y 4] | |
(condp = x | |
3 "picked 3" | |
y "matched" | |
;; default | |
"no match")) | |
;; @@ | |
;; => | |
;;; {"type":"html","content":"<span class='clj-string'>"matched"</span>","value":"\"matched\""} | |
;; <= | |
;; @@ | |
;; LINTING CODE | |
;; `lein eastwood` | |
;; `lein bikeshed` | |
;; `lein kibit` | |
;; All give different warnings that are useful to understand, even if you ignore | |
;; @@ | |
;; @@ | |
;; CLEANING UP NAMESPACES | |
;; `lein slamhound src/foo/bar.clj` | |
;; @@ | |
;; @@ | |
;; DESTRUCTURING args | |
;; * It's a handy way to remove boilerplate | |
;; * Warning: often gives weird errors that are hard to understand if you get syntax wrong | |
;; * Mostly used in `let` blocks, but can be used in `defn` (and other places) | |
;; Vector destructuring | |
;; Instead of: | |
(defn sum1 [v] | |
(let [x (first v) | |
y (second v)] | |
(+ x y))) | |
(sum1 [3 4]) | |
;; Do | |
(defn sum2 [v] | |
(let [[x y] v] | |
(+ x y))) | |
(sum2 [3 4]) | |
;; Map destructuring | |
;; Instead of doing this | |
(let [bob {:first "Bob" :last "Smith"} | |
fname (:first bob) | |
lname (:last bob)] | |
(str fname " " lname)) | |
;; declaring new vars | |
(let [{fname :first lname :last} {:first "Bob" :last "Smith"}] | |
(str fname " " lname)) | |
;; or even | |
(let [{:keys [first last]} {:first "Bob" :last "Smith"}] | |
(str first " " last)) | |
;; you can even do default values and assign the entire var | |
(let [{:keys [first last title] :or {title "Mr." first "" last ""} :as name} {:first "Bob" :last "Smith"}] | |
[(str title " " first " " last) | |
name]) | |
;; @@ | |
;; => | |
;;; {"type":"list-like","open":"<span class='clj-vector'>[</span>","close":"<span class='clj-vector'>]</span>","separator":" ","items":[{"type":"html","content":"<span class='clj-string'>"Mr. Bob Smith"</span>","value":"\"Mr. Bob Smith\""},{"type":"list-like","open":"<span class='clj-map'>{</span>","close":"<span class='clj-map'>}</span>","separator":", ","items":[{"type":"list-like","open":"","close":"","separator":" ","items":[{"type":"html","content":"<span class='clj-keyword'>:first</span>","value":":first"},{"type":"html","content":"<span class='clj-string'>"Bob"</span>","value":"\"Bob\""}],"value":"[:first \"Bob\"]"},{"type":"list-like","open":"","close":"","separator":" ","items":[{"type":"html","content":"<span class='clj-keyword'>:last</span>","value":":last"},{"type":"html","content":"<span class='clj-string'>"Smith"</span>","value":"\"Smith\""}],"value":"[:last \"Smith\"]"}],"value":"{:first \"Bob\", :last \"Smith\"}"}],"value":"[\"Mr. Bob Smith\" {:first \"Bob\", :last \"Smith\"}]"} | |
;; <= | |
;; ** | |
;;; ### ASSORTED GOODIES FOR FUTURE RESEARCH | |
;;; | |
;;; * Gorilla REPL - used for this presentation! | |
;;; * `test.check` for generated testing | |
;;; * [`spyscope`](https://github.com/dgrnbrg/spyscope) for advanced inspecting of values in your code as it runs | |
;;; * [`schema`](https://github.com/Prismatic/schema) and `core.typed` for incremental strutural type-checking | |
;;; | |
;;; ### TALKS | |
;;; | |
;;; * [Simple Made Easy](http://www.infoq.com/presentations/Simple-Made-Easy) | |
;;; * [Thinking in Data](http://www.infoq.com/presentations/Thinking-in-Data) | |
;;; * [Clojure in the Large](http://www.infoq.com/presentations/Clojure-Large-scale-patterns-techniques) | |
;;; * [Always Be Composing](https://www.youtube.com/watch?v=3oQTSP4FngY) | |
;; ** | |
;; @@ | |
;; @@ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment