Fork me on GitHub

I am trying to write a macro that prints out maximum useful information. Right now it uses pr-str, which is what I want for most cljs structures. But for javascript objects, sometimes I get an error like “Uncaught TypeError: Cannot convert a Symbol value to a string”. If I use str instead, I just get [object Object]. So I think I need to use JSON.stringify to get something useful. Is there a predicate to figure out if something is safe to pass to pr-str? I feel like I’ve seen some kind of protocol that you can test for but I can’t find it.


You can also extend the PrintWriter to any types that cause trouble. eg

;; Don't crash when printing JS symbols:
;; see 
(extend-protocol IPrintWithWriter
  (-pr-writer [sym writer _]
    (-write writer (str "\"" (.toString sym) "\""))))


oh cool that’s handy


@lee.justin.m Why not (try (pr-str x) (catch ? _ (JSON.stringify x))) (sorry, I don't know what JS-speak for Throwable is)


yea that would work. thanks


Sometimes, it's more work to figure out if something is valid than it is to just try it and catch the failure...


true dat. especially in a debugging macro


30 minutes of fuzting with ` and ~ later, it works 🙂

👍 4

Is there a better way of updating an inner sequence of a sequence, when using transducers. I'm trying this - which gives the results I need, but I'm not sure if I'm missing a trick

(let [xform (map (fn [m] (update m :a (fn [coll] (into [] (map #(update % :b inc)) coll)))))]
  (sequence xform
            [{:a [{:b 1 :c 2}]}
             {:a [{:b 1 :c 2} {:b 2 :c 3}]}
             {:a [{:b 1 :c 2} {:b 3 :c 4}]}
             {:a [{:b 1 :c 2} {:b 4 :c 5}]}]))
({:a [{:b 2, :c 2}]}
 {:a [{:b 2, :c 2} {:b 3, :c 3}]}
 {:a [{:b 2, :c 2} {:b 4, :c 4}]}
 {:a [{:b 2, :c 2} {:b 5, :c 5}]})


@danieleneal did you consider specter?

Samuel Nystedt13:05:26

Hey, I’m new to ClojureScript and I’m researching the ecosystem a bit. I’m looking for a nice SPA development setup with hot reloading etc, but I’m a bit overwhelmed by the options… Can anyone give me some hints? Which tools do you use (leiningen, shadow-cljs, boot, cljsbuild etc.) and which libs do you use for rendering and state management (re-frame, om, reagent, rum, citrus etc.)?


fulcro is also worth giving a try: it has a template, guidebook, examples, and videos.


@samuel.nystedt if you're new to avoid getting overwhelmed, you can start with a template and then look at swapping things out when you need to. Try perhaps the re-frame-template - there's a lot of info out there about how to build SPAs with reframe so it's a good place to start


lein new re-frame <project-name>


then lein figwheel dev to kick off the figwheel server

Samuel Nystedt13:05:48

Thanks @danieleneal, seems re-frame is popular right now. I’ll play around with the template project 🙂


🙂 I've heard good things about reframe-10x (a debugging tool) so if it's just a trial project, you could include that in the template options


lein new reframe <project-name> +10x


being able to inspect what is going on is one of the big plus points of re-frame's data driven approach

👍 4

How do I incorporate clojure.spec.test.alpha/check into my test file?


@grierson Take a look at clojure.spec.test.alpha/summarize-results which takes the output of st/check and produces a more amenable data structure for clojure.test stuff...


@samuel.nystedt if you think you will use more than one or two libraries (e.g. react libraries) from npm, I’d give strong advice to use shadow-cljs for your build. It will save you a tremendous amount of headache.

👍 4

I'm working on a challenge problem where I'm giving a big glob of deeply nested JSON, and need to sum all of the numbers in it (none of which are represented as string). I was able to parse the JSON no problem using cheshire, and I can easily print all of the numbers using clojure.walk.postwalk #(if (number? %) (prn %)) data). I know that I can use atoms to sum all of the numbers by introducing side-effects, but I was wondering if there was a more idiomatic (pure) way to do this?


Cool, I'll look into tree-seq! My current solution is:

(def *sum (atom 0))

  #(if (number? %) (swap! *sum + %))


with tree-seq you don't need mutation, just filter and apply +


I was always wondering about this — what are the implications between (apply + args) vs (reduce + args)? Is the vararg version of + slower ?

Alex Miller (Clojure team)12:05:44

It actually depends some on what args is but for me conceptually it makes more sense to walk down a series of numbers, summing as we go rather than invoking + once on a giant coll of numbers - Clojure’s function invocation machinery falls into a special case when for >20 args that is less efficient.

Alex Miller (Clojure team)21:05:24

(->> data tree-seq (filter number?) (reduce +))


with transduce the filter can be part of the reduce


(reduce + (tree-seq data)) gives an arity error


I'm trying to figure out how to actually call tree-seq

Alex Miller (Clojure team)21:05:47

what’s the json structure?

Alex Miller (Clojure team)21:05:03

or rather, what does data look like?


Deeply nested, irregular


top-level is a key

Alex Miller (Clojure team)21:05:28

oh, I forgot tree-seq has a bunch of options

Alex Miller (Clojure team)21:05:53

does (tree-seq coll? seq data) work to give you data?


It does though it's still nested

Alex Miller (Clojure team)21:05:58

(->> data (tree-seq coll? seq data) (filter number?) (reduce +))


Calling flatten on that looks better, but still not quite there


(->> data (tree-seq coll? seq) (filter number?) (reduce +)) works!

Alex Miller (Clojure team)21:05:40

drop the last reduce if you want to verify all the numbers are there


it's the correct answer


So how does seq work here?


I'm a bit confused about that argument position in the tree-seq func

Alex Miller (Clojure team)21:05:23

just gives you the sequence of children for a node

Alex Miller (Clojure team)21:05:33

like a map, a vector, a map entry


Okay; I thought that root had to be a value, not a function


the doc is a bit confusing to me

Alex Miller (Clojure team)21:05:27

the first arg to tree-seq is a fn for “is this a branching node?“. the second arg is a fn for “give me the children for this branching node”

Alex Miller (Clojure team)21:05:35

the third arg is the root


oooh, wait, I get it, misread the arg order


thanks! that helps

Alex Miller (Clojure team)21:05:21

if you ever look at, zippers are defined in a similar way

Alex Miller (Clojure team)21:05:42

but with an additional function to tell the zipper how to construct a new branching node


Interestingly, my original solution using mutation is about twice as fast, at least with this data size.


(def *sum (atom 0))

(time (do
          #(if (number? %) (swap! *sum + %))
        (prn @*sum)))

(time (->> data (tree-seq coll? seq) (filter number?) (reduce +) prn))


Not to say that it's that important, but my general assumption with clojure is that mutation is slower outside of transients; I don't have much experience to back that on though


building the whole tree, doing a filter and then summing everything must be slower than just doing + on a number every time you encounter a number

Alex Miller (Clojure team)21:05:32

yeah, you’re building a couple layers of lazy seq


@montanonic specter’s got you covered: (reduce + 0 (traverse (walker number?) your-thing))

👍 4

That looks real nice


even better: (transduce (traverse-all (walker number?)) + your-thing)


less overhead due to transducers, but unless you have a giant dataset it probably won’t make that big of a difference


after JIT compilation @schmee's solutions are both 2-3x faster than the mutation one


at least in this scenario


Specter is pretty neat! 😄


What's the best way to learn it? its docs?


docs + wiki + #specter


note that walker is one of the slower navigators, if you implement a custom recursive-path tuned to your data structure you could probably at least double the speed


Thanks for your feedback everyone. This was really helpful and edifying!

Alex Miller (Clojure team)22:05:36

so there’s no reason you can’t make a tree-reducible like tree-seq…

Alex Miller (Clojure team)22:05:10

(defn tree-red
 [branch? children root]
 (reify clojure.lang.IReduceInit
   (reduce [_ f val]
     (loop [acc val
            [node & nodes] [root]]
       (if node
         (recur (f acc node) (if (branch? node) (concat(children node) nodes) nodes))

Alex Miller (Clojure team)22:05:18

and then you can just reduce over the tree

Alex Miller (Clojure team)22:05:36

(transduce (filter number?) + 0 (tree-red coll? seq data))


but what if I want children to return a reducible, not a seq?

Alex Miller (Clojure team)22:05:28

if you want depth-first like tree-seq, I’m not sure you can do that

Alex Miller (Clojure team)22:05:35

I’ll leave that to you for homework… :)


yeah, actually, come to think of it, I was just talking about depth first searches and tail recursion in the irc channel the other day


you have to do cps to turn the calls in to tail calls, which the contract for reduce doesn't let you do


When I execute clj -Sdeps '{:deps {tools.deps.alpha {:mvn/version "0.5.435"}}}' It report error:

Error building classpath. Could not find artifact tools.deps.alpha:tools.deps.alpha:jar:0.5.435 in central ()
org.eclipse.aether.resolution.ArtifactResolutionException: Could not find artifact tools.deps.alpha:tools.deps.alpha:jar:0.5.435 in central ()
But I confirm this version artifact exist


Is there anyone knows how to solve this problem?

Alex Miller (Clojure team)23:05:27

It’s org.clojure/tools.deps.alpha


Aha, that's it. Thanks. @alexmiller


After clj -Sdeps '{:deps {org.clojure/tools.deps.alpha {:mvn/version "0.5.435"}}}'. Then execute user=> (use ' namespace does not exist, but can't found function (add-lib ...). I followed the steps on

Alex Miller (Clojure team)23:05:36

That’s an experimental feature - it’s only on a branch

Alex Miller (Clojure team)23:05:59

The command line to try it is in the article

Alex Miller (Clojure team)23:05:41

It uses tools.deps as a git dep to pull from an explicit commit (on the branch)

Alex Miller (Clojure team)23:05:02

There will likely be changes before it makes its way into a release