Skip to content

Instantly share code, notes, and snippets.

@bhb
Last active August 29, 2015 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bhb/0d70a8cada57bb54b91b to your computer and use it in GitHub Desktop.
Save bhb/0d70a8cada57bb54b91b to your computer and use it in GitHub Desktop.
;; 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
;;; &quot;Returns a lazy sequence of the items in coll for which
;;; (pred item) returns true. pred must be free of side-effects.&quot;
;;; {:added &quot;1.0&quot;
;;; :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'>&quot;matched&quot;</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'>&quot;no match&quot;</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'>&quot;matched&quot;</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'>&quot;Mr. Bob Smith&quot;</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'>&quot;Bob&quot;</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'>&quot;Smith&quot;</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