Fork me on GitHub
#clojure
<
2021-10-27
>
Ngoc Khuat04:10:19

Hi, I’ve been trying out the clojure.tools.trace lib and it’s pretty useful. But the data structure it prints look bad bc there are no indent or highlight. I wonder is there a way we can achieve this? I think that’ll make the tools trace much more useful.

dpsutton04:10:40

i have indentation in mine:

dpsutton04:10:45

perhaps a bit more indented example:

dpsutton04:10:45

(clojure.tools.trace/trace-ns metabase.mbql.normalize)
(doto 'metabase.mbql.normalize-test require in-ns)
(normalize/normalize {:database 4
                      :type     :query
                      :query    {"source_table" 1
                                 "Joins"        [{"source_table" 2
                                                  "alias"        :my/table
                                                  "strategy"     "left-join"
                                                  "fields"       "all"}]}})

Ngoc Khuat05:10:13

Hi @U11BV7MTK, The indentation works for me too, but I was trying to find a way to have indentation for the arguments. Something like pretty print for all arguments would be great. Btw, Your logs look nice but I think the highlight is supported by your editor right? I’m actually trying to read the trace from stdout

Ngoc Khuat05:10:12

Also, hi - as your future colleague 😁.

👋 1
Ngoc Khuat06:10:13

Is there a way I could re-define a function? (i.e: clojure.tools.trace/trace ) I tried alter-var-root but unable to make it work in repl.

dpsutton14:10:00

normalize-test=> (clojure.tools.trace/trace-ns clojure.set)
nil
normalize-test=> (clojure.set/union #{:a} #{:b})
TRACE t171478: (clojure.set/union #{:a} #{:b})
TRACE t171478: => #{:b :a}
#{:b :a}
normalize-test=> (alter-var-root #'clojure.tools.trace/tracer (constantly (fn [name value] (println "new function!"))))
#object[metabase.mbql.normalize_test$eval171494$fn__171495 0x359121ba "[email protected]"]
normalize-test=> (clojure.set/union #{:a} #{:b})
new function!
new function!
#{:b :a}

Ngoc Khuat15:10:18

Oh, I got it running now. I probably alter the wrong name or something this morning. Thanks! Btw that’s a nice way to introduce the constantly func 😄

GGfpc10:10:14

Is there anyway I can take a spec and create a new spec that is the original spec with one less key?

Lennart Buit11:10:45

Not sure, but you can do this the other way around:

(s/def ::loose (s/keys :req-un [::a]))

(s/def ::strict (s/merge ::loose (s/keys :req-un [::b]))

Alex Miller (Clojure team)16:10:02

no, spec is intentionally not supportive of restriction, only addition

seancorfield16:10:57

@U016XBH746B Even if your spec only lists, say, ::a you can still have arbitrary other keys in your hash map and it will satisfy the Spec. [caveat: if you have qualified keys in your hash map and they match other spec names, they will be checked, even if they are not mentioned in keys!]

GGfpc17:10:21

Thanks, but what I wanted was to make a required key optional without changing the underlying spec. This was a bit of a hack I understand, I fixed it like Lennart sugested

seancorfield17:10:57

dev=> (s/def :foo/bar int?)
:foo/bar
dev=> (s/def ::a int?)
:dev/a
dev=> (s/def ::thing (s/keys :req-un [::a]))
:dev/thing
dev=> (s/valid? ::thing {:a 1})
true
dev=> (s/valid? ::thing {:a "one"})
false
dev=> (s/valid? ::thing {:a 1 :foo/bar 2})
true
dev=> (s/valid? ::thing {:a 1 :foo/bar "two"})
false
☝️:skin-tone-2: This surprises some people.

GGfpc10:10:37

Aka dissoc for spec

Sam Ritchie13:10:04

hey all - does anyone have any pointers for a successful REPL experience with Clojure at http://repl.it?

Sam Ritchie13:10:34

I have a notebook set up now, but it doesn’t look like the editor has any shortcuts to send forms over to a REPL process. The template assumes you just want to run a full file and see the output.

chrisn15:10:13

http://Repl.it is on my shitlist for now. Radon is a friend of mine - he worked at ThinkTopic at the same time I did.

Sam Ritchie17:10:29

that saves me some time 🙂

gabriel15:10:47

Hi, A have a design question: when wrapping a Java lib, is it more idiomatic to expose the positional arguments like:

(defn foo
  ([obj])
  ([obj ^Instant instant])
  ([obj ^long timestamp ^TimeUnit unit]))
or instead provide a map of options:
(defn foo [obj {:keys [instant timestamp unit])

Joshua Suskalo16:10:32

The positional arguments in this particular case provide a nice documentation feature to specify that the timestamp and unit go together, while the instant is used on its own. I'd opt for the positional one in this case.

gabriel16:10:02

Yes indeed, good point. I could be tackled though by having a more nested opts shape: :timestamp {:value :unit}

thom16:10:55

I'd always prefer proper arg lists. Better documentation, better editor support etc. In this case though I'd consider whether it were possible to present a pure Clojure API (just because some people find Java interop and imports scary). For example, could you just have a function that takes a long representing milliseconds?

gabriel16:10:17

That’s a good point, thanks. Yes I could, that’s an option I will explore !

emccue17:10:42

you can use the Inst protocol

gabriel20:10:08

@U3JH98J4R can you elaborate ? I don’t understand how I would

emccue20:10:55

ah sorry meant to write more

emccue20:10:39

if you are gonna write a pure clojure api there is a built in protocol called Inst that exposes an inst-ms function that could be helpful - but thats it

emccue20:10:33

@U0CKD1VM4 I think in general though you shouldn’t wrap java apis

emccue20:10:22

there can be some utility or ergonomics gain sure, but its a lot of time and effort for the same end result

Alex Miller (Clojure team)21:10:51

To provide an alternate answer - maps are safely future extensible

Alex Miller (Clojure team)21:10:52

if you think it might be something that could change in the future

Alex Miller (Clojure team)21:10:19

kwargs are yet a 3rd option :instant ... or :instant ... :timestamp ... and with 1.11's upcoming trailing map support, kwargs supports both named positional and map forms

gabriel21:10:43

@U3JH98J4R @U064X3EF3 thank you for your feedback !

Apple21:10:46

How do you achieve what a normal java class/js object does like getter/setter and a few methods? Wrap record with protocol implementation in atom, or?

Alex Miller (Clojure team)21:10:31

why do you want to do that?

Alex Miller (Clojure team)21:10:10

doing this is generally an anti-pattern in Clojure

Apple21:10:27

I read this today https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_building_practice and try to convert it to cljs. The canvas api and object read/write is there a good way to do those?

seancorfield21:10:31

That's inherently stateful, imperative code. Idiomatic ClojureScript would look pretty different.

seancorfield21:10:56

At some level the equivalent cljs would still have to invoke stateful methods to on the canvas itself (I don't know if there's a nice, functional reactive wrapper for that sort of low-level drawing API?) but in Clojure/Script a ball does not "update itself".

Apple21:10:16

Yes and as far as my knowledge goes I come up with roughly this

(defprotocol Graphics
  (draw [_])
  (updateVel [_])
  (collisionDetect [_]))

(defrecord Ball [x y velX velY color size]
  Graphics
  ...)

(doseq [ball balls]
       (draw @ball)
       (swap! ball updateVel)
       (swap! ball collisionDetect))

seancorfield21:10:49

And in Clojure/Script we don't use getters -- because we have immutable data (and so there are no setters either).

seancorfield21:10:10

That approach is very OOP and not idiomatic Clojure/Script.

seancorfield21:10:26

(this is beginning to feel more like the sort of discussion that would be better suited to #beginners although you also have some #clojurescript specific aspects here since you'll need interop with JS... and I don't do cljs)

Apple21:10:32

You point it out above "inherently stateful, imperative code". What's the approach in clj? the interop is not important.

seancorfield21:10:28

Plain hash maps for the data. The transformation functions should be pure (data to data).

seancorfield21:10:18

The only stateful/mutating code you'd need would be (run! render-ball balls) at each step in the world evolving -- the world itself can also be plain data and it could be "reduced" over a stream of events (such as, say, a timecode).

phronmophobic21:10:37

I'm not sure these are the best examples, but here are a few: • http://www.quil.info/sketches/show/example_equilibrium (quil also supports clojurescript) • A very old example, but still interesting IMO, https://gist.github.com/michiakig/1093917

seancorfield21:10:26

The tick function at the bottom produces a new "world" from an existing "world" and it is called by the (stateful) Swing UI code to produce the data to draw on a timer (the event loop reducing the data over time).

seancorfield21:10:43

This is the stateful, imperative piece that paints the world on the screen, and transforms the data step-by-step https://github.com/sebastianbenz/clojure-game-of-life/blob/master/src/simulator/field.clj#L47-L59

seancorfield21:10:45

(it's unfortunate that it has to squirrel away the state in atoms because the rendering machinery -- Swing -- has no inherent notion of data traveling through the system)

seancorfield21:10:44

But that should provide some good insight into how to separate the bouncing ball problem into a pure data + transform section and a stateful render + update section.

phronmophobic21:10:53

> inherent notion of data traveling through the system I can't imagine any alternative to atoms (or some other reference type) except maybe loop/recur. Is there another option?

seancorfield21:10:13

That Quil/equilibrium example also does a pretty good job of separating those two (albeit in a single file -- but the pure data + transform stuff is all in the first half and the stateful render + update stuff is all in the second half).

seancorfield21:10:02

@U7RJTCH6J If the driver of the system is (mostly) functional, then it can either just reduce or loop/`recur`.

seancorfield21:10:02

(loop [world (set-up-world opts)]
  (draw world) ; stateful/imperative
  (recur (step world)))

phronmophobic22:10:28

Right, so either keep state in some reference type or loop/`recur` (since reduce would be implemented in terms of loop/`recur` ). Was just curious if there was something else. The reference type is useful if you want to process events in threads outside of the render thread (eg. networking, repl, etc).

Michael Mackenzie21:10:38

Hey guys! I've got a quick question about using ring.middleware.reload with component: I have used the technique that Stuart Sierra suggested for making components available to request handlers, i.e. closing the components over the handlers themselves. The problem I am having is that I want to use the ring reload middleware for development, but now my top-level handler takes an argument (the component that it requires), but in order to use wrap-reload, I need to use a var-quote or the var function:

(defn my-app-routes [component-dep]
  (ring/ring-handler (ring/router [(some-specific-routes component-dep)])))

(defn my-app [component-dep]
  ;; notice component-dep isn't being passed
  (wrap-reload #'my-app-routes))
My question is - how do I pass component-dep to my-app-routes in order to use the component library with ring.middleware.reload? Thank you!

Michael Mackenzie21:10:15

one potential sort of workaround i was thinking was, since the reload middleware is only used in the dev profile, use the already stored system var from com.stuartsierra.component.repl and reference it from inside the my-app-routes function to get the required dependency (but only in dev) to make it such that it takes no arguments

Michael Mackenzie21:10:47

but that feels really dirty and would require a separate code path for prod since the routes will have to take an argument (or i'd have to also store the system var globally in prod too)

isak22:10:25

I don't use the same libraries as you, but to accomplish the same thing with pedestal and integrant, here is what I did: 1. 'web' becomes a component that depends on various things, like db-pool, cache, etc 2. Those parameters are used when initializing the web system, adding interceptors (middleware in your case), etc, and there is one that injects that system variable into the request context so that it is available in every request handler

isak22:10:05

I also wrapped it like this, but that is just to prevent tools from exploding from bloated stringification if there is an exception or something: (reify IDeref (deref [_] system))

Michael Mackenzie22:10:49

i think i got it working

1
Michael Mackenzie22:10:11

(wrap-reload #(#'my-app-routes component-dep) %))

Michael Mackenzie22:10:43

TIL vars that reference functions can be called as functions

Michael Mackenzie22:10:05

probably an extremely shallow insight but news to me nonetheless

Michael Mackenzie22:10:53

it seems to at least partially work. it is recompiling certain namespaces but not others

lilactown22:10:21

I didn't realize how slow clojure.zip is - at least for my use case, it's an order of magnitude slower than a hand rolled recursive function

lilactown22:10:47

I kind of hoped it would be faster on account of not having to do all those recursive calls

phronmophobic22:10:44

I'm curious what a profile would point to spending so much time on

lilactown22:10:14

switching to fast-zip seems to help a ton - down to 2x as slow as the hand-rolled version

phronmophobic22:10:21

I hadn't heard of fast-zip. looks neat! :thumbsup:

lilactown22:10:54

this is a very small set of a data as an example, maybe the zipper comes out ahead of a deeper tree

phronmophobic22:10:22

If you're optimizing for performance, I would also see how the hand rolled version compares to a version using specter

lilactown22:10:56

I wouldn't consider taking on a dependency like specter. too heavy

👍 1
lilactown23:10:16

zippers seem (obviously in hindsight) very sensitive to the performance of branch? children and make-node fns you give to the constructor

lilactown23:10:46

changing branch? from

(or (map? x) (sequential? x))
to
(coll? x)
reduced it to 2/3 of the time. it also happened to be more correct for my case

lilactown23:10:49

also using zip/replace instead of zip/edit helped a bit because it gets rid of the (apply f ...) implicit in edit

hiredman23:10:17

With tree-seq I often use seq for both branch? And children

hiredman23:10:15

Hmm, that doesn't track, I must be misremembering, likely coll? and seq

lilactown23:10:13

you just made me realize I could replace part of what i'm doing with tree-seq 🙂

lilactown23:10:22

hmmm that's slower lol