Skip to content

Instantly share code, notes, and snippets.

@tangjeff0
Last active April 15, 2020 17:11
Show Gist options
  • Save tangjeff0/9c15c871953e1f2b2cf04a3ee14db207 to your computer and use it in GitHub Desktop.
Save tangjeff0/9c15c871953e1f2b2cf04a3ee14db207 to your computer and use it in GitHub Desktop.
(defn ignore
[expr] ;; expression evaluated
nil)
(ignore (pr "hello world"))
(defmacro ignore
[expr] ;; expression never evaluated
nil)
(ignore (pr "FREE MEEEEE"))
(defmacro rev [f & args]
(cons f (reverse args)))
(rev str "hello " (+ 40 2))
; lisp is just a list of verbs and nouns...
(macroexpand '(rev str "hello " (+ 40 2)))
; same as just running macro directly?
(eval (macroexpand '(rev str "hello " (+ 40 2))))
; => (clojure.core/inc user/y)
`(inc y)
; => (inc y)
'(inc y)
; => Unable to resolve symbol: y in this context
`(inc ~y)
; => (user/foo [1 2 3])
`(foo ~[1 2 3])
; => (user/foo 1 2 3)
`(foo ~@[1 2 3])
(pprint (macroexpand '(or a b c d)))
;; =>
;; (let*
;; [or__5516__auto__ a]
;; (if or__5516__auto__ or__5516__auto__ (clojure.core/or b c d)))
;; or__5516__auto__ generated to prevent naming conflicts/collisions with local namespace
;; diff output each time
(gensym "hi")
(gensym "hi")
(gensym "hi")
;; in a syntax quote, # at the end of a symbol expands to a gensym
`hi#
(def x 60)
(pprint
(macroexpand
'(condp <= x
70 :F5
58 :F4
49 :F3
42 :F2
:F1)))
;; => expands to
;; (let*
;; [pred__6063 <= expr__6064 x]
;; (if
;; (pred__6063 70 expr__6064)
;; :F5
;; (if
;; (pred__6063 58 expr__6064)
;; :F4
;; (if
;; (pred__6063 49 expr__6064)
;; :F3
;; (if (pred__6063 42 expr__6064) :F2 :F1)))))
;; recursion
(defn sum [numbers]
(if-let [n (first numbers)]
(+ n (sum (rest numbers)))
0))
(sum (range 10))
;; => 45
(sum (range 1000000))
;; => StackOverFlowError
;; did not know you could recur without loop lol
(defn sum
([numbers]
(sum 0 numbers))
([subtotal numbers]
(if-let [n (first numbers)]
(recur (+ subtotal n) (rest numbers))
subtotal)))
;; bruh the source for `for`, `fn`...
;; for
;; can use :when for filter, :while for exit condition, :let for binding
(for [x (range 5)
y (range 5)
:while (< x 3)
:when (and (even? x) (odd? y))]
[x y])
;; threading macro
;; thread last
(macroexpand
'(->> (range 10)
(filter odd?)
(reduce +)))
;; => (reduce + (filter odd? (range 10)))
;; thread first
(macroexpand
'(-> {:proton :fermion}
(assoc :photon :boson)
(assoc :neutrino :fermion)))
;; => (assoc (assoc {:proton :fermion} :photon :boson) :neutrino :fermion)
;; Problems
;; Using the control flow constructs we’ve learned, write a schedule function which, given an hour of the day, returns what you’ll be doing at that time. (schedule 18), for me, returns :dinner.
(defn schedule [hr]
(cond
(< hr 0) :invalid
(< hr 8) :sleeping
(< hr 9) :waking-up
(< hr 13) :coding
(< hr 22) :chillen
(< hr 24) :sleeping
:else :invalid))
(schedule 24)
(defn sched2 [hr]
(condp > hr
0 :invalid
8 :sleeping
9 :waking-up
13 :coding
22 :chillen
24 :sleeping
:invalid))
(sched2 24)
;; Using the threading macros, find how many numbers from 0 to 9999 are palindromes:
;; identical when written forwards and backwards.
;; 121 is a palindrome, as is 7447 and 5, but not 12 or 953.
(defn palindromes [start end]
(->> (range start end)
(map str)
(filter #(= (seq %) (reverse %)))
count))
(palindromes 0 9999)
;; Write a macro id which takes a function and a list of args: (id f a b c)
;; and returns an expression which calls that function with the given args: (f a b c).
(defmacro id [f a b c]
`(~f ~a ~b ~c))
(id str "hello" " " "world")
;; Write a macro log which uses a var, logging-enabled, to determine whether or not
;; to print an expression to the console at compile time. If logging-enabled is false,
;; (log :hi) should macroexpand to nil. If logging-enabled is true, (log :hi) should macroexpand to (prn :hi).
;; Why would you want to do this check during compilation, instead of when running the program? What might you lose?
;; not sure if this is right because i use the & macro, which requires exploding later on
;; but you might want do this during compilation because you get rid of the if statement and its contents
;; this logic never really needs to be run during production. nil leads to dead code, which can remove code size
(def logging-enabled true)
(defmacro log [& args]
`(if ~logging-enabled
(prn ~@args)
nil))
(log :hi)
;; (Advanced) Using the rationalize function, write a macro exact
;; which rewrites any use of +, -, *, or / to force the use of ratios instead of floating-point numbers.
;; (* 2452.45 100) returns 245244.99999999997, but (exact (* 2452.45 100)) should return 245245N
;; (defmacro exact [op]
;; `((first ~op) (second (rationalize ~op)) (last (rationalize ~op))))
;; (* (rationalize 2452.45) (rationalize 100))
(defmacro exact [[op & args]]
`(apply ~op (map rationalize (vector ~@args))))
(exact (* 2452.45 100 2.1))
(macroexpand '(exact (* 2452.45 100 2.1)))
(defn f-all [f xs]
(if (first xs)
(cons (f (first xs))
(f-all f (rest xs)))
[]))
(f-all #(inc (inc %)) [-2 -1 0 1])
(defn expand [f start count]
(if (= start count)
(list (f count))
(cons (f start) (expand f (inc start) count))))
(expand inc 1 5)
(partition-by identity [1 1 2 1 1 1 3 3])
(frequencies [:meow :mrrrow :meow :meow])
reductions
(into '() [:a :b :b :b :a :a])
(defn my-map [f coll]
(reduce (fn [acc x]
(conj acc (f x)))
[]
coll))
(my-map inc [1 2])
(defn my-filter [f coll]
(reduce (fn [acc x]
(if (f x)
(conj acc x)
acc))
[]
coll))
(my-filter odd? [ 1 2 3 4])
;; find the sum of the products of consecutive pairs of the first 1000 odd integers.
;; first n odd integers?
(let [n 3]
(->> (iterate inc 1)
(filter odd?)
(take n)
(partition 2 1)
(map #(reduce * %))
(reduce +)))
;; Write a function to find out if a string is a palindrome–that is, if it looks the same forwards and backwards.
;; Find the number of ‘c’s in “abracadabra”.
;; Write your own version of filter.
;; Find the first 100 prime numbers: 2, 3, 5, 7, 11, 13, 17, ….
(defn palindrome? [s]
(= (seq s) (reverse s)))
(palindrome? "racecar")
(palindrome? "banana")
(defn count-char [char s]
(count (filter #(= char %) s)))
(count-char \c "abracadabra")
(count-char \c "racecar")
(my-filter #(= \c %) "racecar")
(defn prime-sieve
"initialize with prime vector with [2]
iterate from 3, comparing modulo of n with each value in prime vector
if mod is equal to 0 for some val in vector, do not add, else add to vec
if vect is n length, terminate early (otherwise reduce never terminates from inf seq)"
[n]
(reduce (fn [acc x]
(cond
(= n (count acc)) (reduced acc)
(some #(= (mod x %) 0) acc) acc
:else (conj acc x)))
[2]
(iterate inc 3)))
(prime-sieve 100)
;; no delay
(do (prn "Adding") (+ 1 2))
;; let later equal an anon function
(def later
(fn []
(do (prn "Adding") (+ 1 2))))
(later) ;; fn body only evaluated at invocation
;; Delays ;;
;; there's a delay macro just for this
(def later
(delay (prn "Adding") (+ 1 2)))
(pr later) ;; => #delay[{:status :pending, :val nil} 0x7b173853]
(deref later) ;; or
@later ;; deref operator
(pr later) ;; => #delay[{:status :ready, :val 3} 0x7b173853]
;; "Adding" is only called the first time
;; the return value, 3 in this case, is re-returned on re-call
;; futures ;;
;; opportunistically defer... multitasking with multi-cores
;; a delay evaluated in parallel
(def futu (future (prn "hi") (+ 1 2)))
futu
@futu
(dotimes [i 5] (future (prn i))) ;; non-determinstic, overlapping output
;; promises ;;
(def box (promise))
(deref box) ;; if you call this, blocks and you can't escape :( but useful if you want to synchronize your program
box ;; pending
(deliver box :candy)
box ;; its here!
(deliver box :poop)
@box ;; doesn't matter, still candy! no going back on your promises ;)
(def card (promise))
card
;; => #promise[{:status :pending :val :nil} 0x123asd]
@card ;; if you deref before the promise has been `deliver`ed, gonna block!
(future
(Thread/sleep 5000)
(deliver card [(inc (rand-int 13))
(rand-nth [:spades :hearts :diamonds :clubs])]))
@card
;; => if you eval right after evalling dealer, wait 4.9 secs... => [13 :clubs] or something
;; Vars ;;
(def ^:dynamic *board* :maple)
(defn cut [] (prn "sawing through" *board*))
(cut)
;; => "sawing through" :maple
;; dynamic scope via binding and ^:dynamic
;; propagates through all fn calls within binding scope
;; if multi-threaded, binding only true for the calling thread (other threads unchanged)
(binding [*board* :cedar] (cut))
;; => "sawing through" :cedar
;; lexical scope via let or fn
;; constrained to just the let or fn's scope
(let [*board* :cedar] (cut))
;; => "sawing through" :maple
;; Atoms ;;
(def xs #{})
;; continually redefine xs
(dotimes [i 10] (def xs (conj xs i)))
xs
;; but mutating xs in a loop is not thread-safe
(def xs #{})
(dotimes [i 10] (future (def xs (conj xs i))))
xs
;; doesn't have all the items! there must've been race conditions
;; read-modify-update process assumes consecutive, not concurrent
;; introducing ~ atom ~
(def xs (atom #{}))
xs ;; => #atom[#{} 0x138ds]
(type xs)
@xs
(reset! xs :yeet) ;; instead of re-running `def` on an existing value, use `reset!`
(deref xs) ;; => :yeet
;; swap! takes in a strictly pure fn
(reset! xs 0)
(swap! xs inc) ;; => 1
@xs ;; => 1
(swap! xs + 5 6) ;; => 12
(def xs (atom #{}))
(dotimes [i 10] (future (swap! xs conj i)))
xs ;; has all the items :)
;; Refs (group txs) ;;
;; reset! : atom :: ref-set : ref
(def x (ref 0))
(def y (ref 0))
[@x @y] ;; => [0 0]
(dosync
(ref-set x 1)
(ref-set y 2))
[@x @y] ;; => [1 2]
;; swap! : atom :: alter : ref
;; alter always in a dosync
(def x (ref 1))
(def y (ref 2))
[(deref x) @y] ;; => [1 2]
(dosync
(alter x inc)
(alter y + 1 2))
[@x @y] ;; => [2 5]
;; alter executes operations atomically, in order, never interleaved
;; if operations can take place in any order, use commute for performance gains of multi-threading
;; commute etymology ~> commutative. x+2+3 = 3+x+2. weaker but faster safety
(dosync
(commute x inc)
(commute y dec))
[@x @y] ;; => [3 4]
;; use `ensure` for strongly consistent read: using one ref when affecting another
(dosync
(alter x + (ensure y)))
[@x @y] ;; => [7 4]
;; exercises ;;
;; Finding the sum of the first 10000000 numbers takes about 1 second on my machine:
(defn sum [start end] (reduce + (range start end)))
(time (sum 0 1e7))
;; "Elapsed time: 1101.295323 msecs"
;; => 49999995000000
;; 1. Use delay to compute this sum lazily; show that it takes no time to return the delay, but roughly 1 second to deref.
(defn delay-sum [start end] (delay (prn "feeling lazy today") (reduce + (range start end))))
;; creating the delay ~0 sec
(def dlaid
(time (delay-sum 0 1e7)))
;; "Elapsed time: 0.18736 msecs"
;; derefing delay first time ~1 sec
(time @dlaid) ;; => 49999995000000
;; "feeling lazy today"
;; "Elapsed time: 1168.885123 msecs
;; subsequent delays are now ~0 sec
(time (deref dlaid))
;; Elapsed time: 0.0257 msecs
;; 2. We can do the computation in a new thread directly, using (.start (Thread. (fn [] (sum 0 1e7)))–but this simply runs the (sum) function and discards the results. Use a promise to hand the result back out of the thread. Use this technique to write your own version of the future macro.
;; not entirely sure if this is right but
;; i think you have to pass a promise into `my-future`, otherwise you can't deliver it out
;; if you generate a promise local to `my-future`, it never leaves the lexical scope
(defmacro my-future [p & expr]
`(.start (Thread. (fn [] (deliver ~p ~@expr)))))
(def p (promise))
(macroexpand ' (my-future p (sum 0 1e7)))
(my-future p (sum 0 1e7))
@p
;; 3. If your computer has two cores, you can do this expensive computation twice as fast by splitting it into two parts: (sum 0 (/ 1e7 2)), and (sum (/ 1e7 2) 1e7), then adding those parts together. Use future to do both parts at once, and show that this strategy gets the same answer as the single-threaded version, but takes roughly half the time.
(defn sum [start end] (reduce + (range start end)))
(defn s1 [] (time (sum 0 1e7)))
(defn s2 [] (let [f1 (future (sum (/ 1e7 2) 1e7))
f2 (future (sum 0 (/ 1e7 2)))]
(time (+ @f1 @f2))))
;; type independent equality (s1 is Long, s2 is Double)
(== (s1) (s2))
;; 4. Instead of using reduce, store the sum in an atom and use two futures to add each number from the lower and upper range to that atom. Wait for both futures to complete using deref, then check that the atom contains the right number. Is this technique faster or slower than reduce? Why do you think that might be?
;; lol i didnt use reduce but w/e
(def sum-atom (atom 0))
sum-atom
(time (let [f1 (future (swap! sum-atom + (sum 0 (/ 1e7 2))))
f2 (future (swap! sum-atom + (sum (/ 1e7 2) 1e7)))]
@f1
@f2
@sum-atom))
;; my intuition would be that solution with swap! is slower because it has retry logic in the case where
;; race condition happened
;; but they're approximately the same speed
;; there probably aren't enough race conditions when only 2 possible swaps ever happening at once
;; 5. Instead of using a lazy list, imagine two threads are removing tasks from a pile of work. Our work pile will be the list of all integers from 0 to 10000:
(def work (ref (apply list (range 1e5))))
(take 10 @work)
;; (0 1 2 3 4 5 6 7 8 9)
;; And the sum will be a ref as well:
(def sum (ref 0))
;; Write a function which, in a dosync transaction, removes the first number in work and adds it to sum.
;; Then, in two futures, call that function over and over again until there’s no work left. Verify that @sum is 4999950000. Experiment with different combinations of alter and commute–if both are correct, is one faster? Does using deref instead of ensure change the result?
(def work (ref (apply list (range 1e5))))
(def sum (ref 0))
(defn work-alter [work sum]
(dosync
(alter sum + (first @work))
(alter work rest))
sum)
(time (do
(while (> (count @work) 0)
@(future (do-work work sum))
@(future (do-work work sum)))
@sum))
(defn work-commute [work sum]
(dosync
(commute sum + (first @work))
(commute work rest)))
;; commute shouldnt work because we need addition to occur before mutating work
(time (do
(while (> (count @work) 0)
@(future (work-commute work sum))
@(future (work-commute work sum)))
@sum))
(defn work-ensure [work sum]
(dosync
(alter sum + (first (ensure work)))
(alter work rest)))
;; ensure shuold be slower
(time (do
(while (> (count @work) 0)
@(future (work-ensure work sum))
@(future (work-ensure work sum)))
@sum))
;; doesnt appear as if there's any significant diff in speed. and commute with simple references doesn't seem to be problematic
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment