Consider this pseudo code:
(defrc name
"string"
[a :A]
[:div a])
Where defrc would be a macro, that would expand to the following
(let [a (rum/react (atom :A))]
(rum/defc name < rum/reactive []
[:div a]))
Where rum/defc is itself a macro. I came up with the code below:
(defmacro defrc
[name subj bindings & body]
(let [map-bindings# (apply array-map bindings)
keys# (keys map-bindings#)
vals# (vals map-bindings#)
atomised-vals# (atom-map vals#)]
`(let ~(vec (interleave keys# (map (fn [v] (list 'rum/react v)) (vals atomised-vals#))))
(rum/defc ~name < rum/reactive [] ~#body))))
Which almost works:
(macroexpand-all '(defrc aname
#_=> "string"
#_=> [a :A]
#_=> [:div a]))
(let* [a (rum/react #object[clojure.lang.Atom 0x727ed2e6 {:status :ready, :val nil}])] (rum/defc aname clojure.core/< rum/reactive [] [:div a]))
However when used it results in a syntax error:
ERROR: Syntax error at (clojure.core/< rum.core/reactive [] [:div a])
Is this because the inner macro is not being expanded?
Turns out the macro was working correctly but the problem occurred because < was inside the syntax quote it got expanded to clojure.core/<, and Rum simply looks for a quoted <, relevant snippet from Rum's source:
...(cond
(and (empty? res) (symbol? x))
(recur {:name x} next nil)
(fn-body? xs) (assoc res :bodies (list xs))
(every? fn-body? xs) (assoc res :bodies xs)
(string? x) (recur (assoc res :doc x) next nil)
(= '< x) (recur res next :mixins)
(= mode :mixins)
(recur (update-in res [:mixins] (fnil conj []) x) next :mixins)
:else
(throw (IllegalArgumentException. (str "Syntax error at " xs))))...
I wanted to learn Clojure and started with a Mandelbrot generator using quil, I got it to work - however it takes some time to generate images and is a massive resource hog. Any advice for how to make it faster or if you spot any un-clojure-esque parts of my code.
Core.clj
(ns frac.core
(:require [quil.core :as q])
(:require [frac.complex :refer :all]))
(def scale (atom 0.01))
(def xoff (atom -200))
(def yoff (atom -200))
(def its 50)
(defn mandelbrot [r i]
(count (take its (take-while #(<= (modu %) 2) (iterate #( add (mul % %) [r i]) [r i])))))
(defn gen []
(let [p (q/pixels)
w (q/width)
h (q/height)]
(doseq [x (range w) y (range h)]
(let [m (mandelbrot (* #scale (+ x #xoff)) (* #scale (+ y #yoff)))
c (if (= m its) 0 m)]
(aset p (+ x (* y w)) (q/color (* 1.5 c) (* 4 c) (* 5.2 c))))))
(q/update-pixels))
(defn setup []
(gen))
(defn draw [])
(defn click []
(swap! xoff #(+ (q/mouse-x) (- (/ (q/width) 2)) %))
(swap! yoff #(+ (q/mouse-y) (- (/ (q/height) 2)) %))
(gen))
(defn wheel [z]
(swap! scale #(if (pos? z) (* 1.1 %) (* 0.9 %)))
(prn #scale)
(gen))
(q/defsketch example
:title "Mandel"
:setup setup
:draw draw
:size [400 400]
:mouse-clicked click
:mouse-wheel wheel)
(defn -main [& args])
Complex.clj
(ns frac.complex)
(defn mul [z1 z2]
(let [r1 (first z1)
i1 (second z1)
r2 (first z2)
i2 (second z2)]
[(- (* r1 r2) (* i1 i2)) (+ (* r1 i2) (* r2 i1))]))
(defn add [z1 z2]
(let [r1 (first z1)
i1 (second z1)
r2 (first z2)
i2 (second z2)]
[(+ r1 r2) (+ i1 i2)]))
(defn modu [z]
(let [r (first z)
i (second z)]
(Math/sqrt (+ (* r r) (* i i)))))
Try set this:
(set! *unchecked-math* :warn-on-boxed)
and remove all warnings. Use type hints as needed.
Why does this keep asking Jack if he wants a cup of tea and not the other fathers.
(defn tea-anyone
"Ask 'fathers' if they would like some tea"
[fathers]
(loop [asks 0 to-ask fathers]
(let [[father & others] to-ask]
(println (str "Cup of tea " father "? "))
(if (> asks 6)
(println (str father ": All right then!"))
(recur (inc asks) (conj others father))))))
(tea-anyone ["Jack" "Ted" "Dougle"])
Because others isn't a vector. See for yourself:
(let [[f & o :as a] [1 2 3 4]]
(println f)
(println o)
(println a)
(println (type f))
(println (type o))
(println (type a))
(println (type (conj o 5)))
(println (type (conj a 5))))
To achieve the effect you want, you could use cycle.
Try this:
(recur (inc asks) (conj (vec others) father))
I have a following function:
(defn map-pairs [f coll]
(map f (partition 2 1 coll)))
Is it possible to avoid creating of the intermediate 2-element collections using clojure.core.reducers?
Not out of the box. But you can:
=> (into [] (map2 + (range 10)))
[1 3 5 7 9 11 13 15 17]
See:
(deftype Fn2 [f1 f ^:volatile-mutable a ^:volatile-mutable b]
clojure.lang.IFn
(invoke [this] (f1))
(invoke [this _] (set! b this)) ; hack to init the sentinel
(invoke [this ret v]
(set! a b)
(set! b v)
(if (identical? a this)
ret
(f1 ret (f a b))))
#_(invoke [this ret k v] to do))
(defn fn2 [f1 f] 1
(let [f2 (Fn2. f1 f nil nil)]
(f2 nil)
f2))
(defn map2 [f coll]
(r/reducer coll
(fn [f1]
(fn2 f1 f))))
I have a sequence s and a list of indexes into this sequence indexes. How do I retain only the items given via the indexes?
Simple example:
(filter-by-index '(a b c d e f g) '(0 2 3 4)) ; => (a c d e)
My usecase:
(filter-by-index '(c c# d d# e f f# g g# a a# b) '(0 2 4 5 7 9 11)) ; => (c d e f g a b)
You can use keep-indexed:
(defn filter-by-index [coll idxs]
(keep-indexed #(when ((set idxs) %1) %2)
coll))
Another version using explicit recur and lazy-seq:
(defn filter-by-index [coll idxs]
(lazy-seq
(when-let [idx (first idxs)]
(if (zero? idx)
(cons (first coll)
(filter-by-index (rest coll) (rest (map dec idxs))))
(filter-by-index (drop idx coll)
(map #(- % idx) idxs))))))
make a list of vectors containing the items combined with the indexes,
(def with-indexes (map #(vector %1 %2 ) ['a 'b 'c 'd 'e 'f] (range)))
#'clojure.core/with-indexes
with-indexes
([a 0] [b 1] [c 2] [d 3] [e 4] [f 5])
filter this list
lojure.core=> (def filtered (filter #(#{1 3 5 7} (second % )) with-indexes))
#'clojure.core/filtered
clojure.core=> filtered
([b 1] [d 3] [f 5])
then remove the indexes.
clojure.core=> (map first filtered)
(b d f)
then we thread it together with the "thread last" macro
(defn filter-by-index [coll idxs]
(->> coll
(map #(vector %1 %2)(range))
(filter #(idxs (first %)))
(map second)))
clojure.core=> (filter-by-index ['a 'b 'c 'd 'e 'f 'g] #{2 3 1 6})
(b c d g)
The moral of the story is, break it into small independent parts, test them, then compose them into a working function.
The easiest solution is to use map:
(defn filter-by-index [coll idx]
(map (partial nth coll) idx))
I like Jonas's answer, but neither version will work well for an infinite sequence of indices: the first tries to create an infinite set, and the latter runs into a stack overflow by layering too many unrealized lazy sequences on top of each other. To avoid both problems you have to do slightly more manual work:
(defn filter-by-index [coll idxs]
((fn helper [coll idxs offset]
(lazy-seq
(when-let [idx (first idxs)]
(if (= idx offset)
(cons (first coll)
(helper (rest coll) (rest idxs) (inc offset)))
(helper (rest coll) idxs (inc offset))))))
coll idxs 0))
With this version, both coll and idxs can be infinite and you will still have no problems:
user> (nth (filter-by-index (range) (iterate #(+ 2 %) 0)) 1e6)
2000000
Edit: not trying to single out Jonas's answer: none of the other solutions work for infinite index sequences, which is why I felt a solution that does is needed.
I had a similar use case and came up with another easy solution. This one expects vectors.
I've changed the function name to match other similar clojure functions.
(defn select-indices [coll indices]
(reverse (vals (select-keys coll indices))))
(defn filter-by-index [seq idxs]
(let [idxs (into #{} idxs)]
(reduce (fn [h [char idx]]
(if (contains? idxs idx)
(conj h char) h))
[] (partition 2 (interleave seq (iterate inc 0))))))
(filter-by-index [\a \b \c \d \e \f \g] [0 2 3 4])
=>[\a \c \d \e]
=> (defn filter-by-index [src indexes]
(reduce (fn [a i] (conj a (nth src i))) [] indexes))
=> (filter-by-index '(a b c d e f g) '(0 2 3 4))
[a c d e]
I know this is not what was asked, but after reading these answers, I realized in my own personal use case, what I actually wanted was basically filtering by a mask.
So here was my take. Hopefully this will help someone else.
(defn filter-by-mask [coll mask]
(filter some? (map #(if %1 %2) mask coll)))
(defn make-errors-mask [coll]
(map #(nil? (:error %)) coll))
Usage
(let [v [{} {:error 3} {:ok 2} {:error 4 :yea 7}]
data ["one" "two" "three" "four"]
mask (make-errors-mask v)]
(filter-by-mask data mask))
; ==> ("one" "three")