Fork me on GitHub
#beginners
<
2018-05-15
>
lee.justin.m03:05:32

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.

eggsyntax21:05:27

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

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

lee.justin.m22:05:01

oh cool that’s handy

seancorfield03:05:26

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

lee.justin.m03:05:51

yea that would work. thanks

seancorfield03:05:16

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

lee.justin.m03:05:46

true dat. especially in a debugging macro

lee.justin.m03:05:26

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

danieleneal10:05:23

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}]})

soulflyer11:05:58

@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.)?

sundarj15:05:59

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

danieleneal13:05:31

@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 https://github.com/Day8/re-frame-template

danieleneal13:05:38

lein new re-frame <project-name>

danieleneal13:05:03

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 🙂

danieleneal13:05:13

🙂 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

danieleneal13:05:20

lein new reframe <project-name> +10x

danieleneal13:05:51

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

grierson14:05:18

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

seancorfield16:05:54

@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...

lee.justin.m16:05:45

@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.

montanonic21:05:08

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?

montanonic21:05:31

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

(def *sum (atom 0))

(walk/postwalk
  #(if (number? %) (swap! *sum + %))
  data)

noisesmith21:05:06

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

alexmiller21:05:00

reduce is better imo

alexmiller21:05:00

reduce is better imo

orestis10:05:44

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

alexmiller12: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.

alexmiller21:05:24

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

hiredman21:05:09

with transduce the filter can be part of the reduce

montanonic21:05:50

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

montanonic21:05:00

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

alexmiller21:05:47

what’s the json structure?

alexmiller21:05:03

or rather, what does data look like?

montanonic21:05:29

Deeply nested, irregular

montanonic21:05:50

top-level is a key

alexmiller21:05:28

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

alexmiller21:05:53

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

montanonic21:05:46

It does though it's still nested

alexmiller21:05:58

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

montanonic21:05:59

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

montanonic21:05:22

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

alexmiller21:05:40

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

montanonic21:05:01

it's the correct answer

montanonic21:05:08

So how does seq work here?

montanonic21:05:21

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

alexmiller21:05:23

just gives you the sequence of children for a node

alexmiller21:05:33

like a map, a vector, a map entry

montanonic21:05:10

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

montanonic21:05:17

the doc is a bit confusing to me

alexmiller21: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”

alexmiller21:05:35

the third arg is the root

montanonic21:05:55

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

montanonic21:05:59

thanks! that helps

alexmiller21:05:21

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

alexmiller21:05:42

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

montanonic21:05:14

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

montanonic21:05:32

(def *sum (atom 0))

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

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

montanonic21:05:48

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

joelsanchez21:05:13

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

alexmiller21:05:32

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

schmee21:05:37

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

montanonic21:05:18

That looks real nice

schmee21:05:41

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

schmee21:05:54

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

montanonic21:05:31

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

montanonic21:05:37

at least in this scenario

schmee22:05:18

Specter is pretty neat! 😄

montanonic22:05:36

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

schmee22:05:41

docs + wiki + #specter

schmee22:05:43

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

montanonic22:05:49

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

alexmiller22:05:36

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

alexmiller22: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))
         acc)))))

alexmiller22:05:18

and then you can just reduce over the tree

alexmiller22:05:36

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

hiredman22:05:12

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

alexmiller22:05:28

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

alexmiller22:05:35

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

hiredman22:05:39

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

hiredman22:05:45

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

stardiviner23:05:18

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 http://search.maven.org/#search%7Cgav%7C1%7Cg:%22org.clojure%22AND%20a:%22tools.deps.alpha%22

stardiviner23:05:35

Is there anyone knows how to solve this problem?

alexmiller23:05:27

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

stardiviner23:05:40

Aha, that's it. Thanks. @alexmiller

stardiviner23:05:26

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

alexmiller23:05:36

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

alexmiller23:05:59

The command line to try it is in the article

alexmiller23:05:41

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

alexmiller23:05:02

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