Fork me on GitHub
#clojure
<
2023-10-17
>
Casey11:10:31

I have a tight loop/recur doing graphics rendering... It seems like this loop blocks evals from my editor (emacs/cider). When the loop exits, the evals go through. What's the proper way to workaround this so I can eval functions called in the loop?

oyakushev11:10:05

You have to start your loop in another thread. The easiest way is to do (future (my-background-code ...))

oyakushev11:10:03

Wait, I'm not sure I understood this part > so I can eval functions called in the loop You want to be able to interact with CIDER while your rendering code runs in the background, right?

Casey12:10:43

Here's a simple example

(def state (atom true))

(defn do-stuff [a b ]
  (+ a b))

#_(defn do-stuff [a b ]
    (prn "doing stuff"))

(defn test-loop []
  (prn "started")
  (while @state
    (do-stuff 1 2))
  (prn "stopped"))

(comment
  (future (test-loop)) ; to start the loop
  (reset! state false) ; to stop the loop
  ;;
  )
I added the (future...) as you recommended. I want to be able to swap the do-stuff function and have it reflected in the loop. But with the code above if I 1) start the loop, 2) comment out the first do-stuff and uncomment the second 3) eval the new do-stuff , I don't see any "doing stuff" printed

oyakushev12:10:59

I literally repeated what you did in the REPL and I see "doing stuff" being printed.

Casey12:10:14

(of course I'm not printing things in a loop in the real code)

Casey12:10:32

huh... I literally just ran the same 😕 but it didn't work

oyakushev12:10:58

Try repeating this outside of CIDER, in a regular clj REPL

oyakushev12:10:19

Maybe it's the printlns from the other threads that are being swallowed by CIDER, however I don't think it should happen

oyakushev12:10:02

Did this in CIDER, works for me

(def state (atom true))

(defn do-stuff [a b]
  (+ a b))

(defn test-loop []
  (prn "started")
  (while @state
    (Thread/sleep 1000)
    (do-stuff 1 2))
  (prn "stopped"))

(future (test-loop))

(defn do-stuff [a b]
  (prn "doing stuff" a b)
  (+ a b))

Casey12:10:14

When I do exactly that, the eval of the second do-stuff works and If i eval (do-stuff 10 10) I see the "doing stuff 10 10" output, but not the output that the loop should be generating

oyakushev12:10:12

This is on regular Clojure, right? Not babashka or something like that?

oyakushev12:10:41

Try putting a println into the initial do-stuff, would you see the output?

Casey12:10:26

i made a minimal deps.edn and tried it there, and it's working.. so something is wrong with my projects env I suppose

Casey12:10:39

(it is regular clojure)

oyakushev12:10:50

I bet that the output streams got messed up. That is the easier thing to break than the live recompilation functionality

oyakushev12:10:03

You can replace printlns with another atom (say, a counter) that you would start incrementing in a redefined do-stuff . Just to reassure yourself

Casey12:10:21

thanks for helping me trouble shoot this @U06PNK4HG I should have enough now to narrow down what the issue is.

👍 1
Casey13:10:17

Aha I was running the prod profile not the dev profile and prod has direct linking enabled.. that would do it :)

oyakushev13:10:17

Hmmm, direct linking popped into my mind but I didn't voice it aloud. Good that you've found the culprit!

practicalli-johnny16:10:17

cider-debug can be a very useful tool to see what an iterative piece of code is doing. It automatically adds breakpoints and shows intermediate values as it evaluates each line of code.

otwieracz17:10:28

Hello. Let's say I've got transducer (map inc). Is this possible to somehow wrap around that full transducer, so it will have effect like (map (something inc))? For example, I've got (map (fn [{:keys [args]}] {:args (inc args)})) . Is this possible to somehow wrap it the other way around, so it will be (map inc) ? Basically, I've got a vector of transducers [(map something) (map something)] - a clojure.core.async pipeline and I'd like to automatically wrap every one of these so I can carry something extra between (a OpenTracing span created in the first step of the pipeline) without having to modify each of the inner functions. In other words, I'd like to add onion layer to carry the span around channels. Hope it is clear enough 🙂 Thanks for any advice!

Bob B17:10:28

I'm not sure if I'm understanding the question correctly, but you can compose transducers, so if you wanted to square each numbers before inc'ing it, for example:

(let [inc-xf (map inc)
      square-xf (map #(* % %))]
  (transduce (comp square-xf inc-xf) conj [1 2 3]))
=> [2 5 10]

dpsutton17:10:38

i think it’s changing the shape of what’s going through. the classic refactor from a single arg to a map so you can pass more things through

dpsutton17:10:59

i read it as starting here

(let [x1 (map inc)
      x2 (map inc)]
  (transduce (comp x1 x2) conj [] (range 3)))
[2 3 4]
and wanting to adjust x1 and x2 to handle a new map shape like
(let [x1 (map inc)
      x2 (map inc)]
         (transduce (comp x1 x2) conj [] [{:x 1} {:x 2} {:x 3}]))

otwieracz18:10:38

OK, let's have a look at this:

;; basic transducers
  (def transducers-1
    [(map #(+ % 1))
     (map #(- % 1))])

  (into []
        (apply comp transducers-1)
        [1 2 3])

  (def add-span
    (map (fn [x]
           {:span "some-span"
            :args x})))

  (def init-span (map (fn [x] {:span "some-span" :args x})))
  (def close-span (map (fn [x] (:args x))))

  ;; explicit case
  (defn wrap-span [f]
    (fn [{:keys [span args]}]
      (log/info args)
      {:span span
       :args (f args)}))

  (def transducers-2
    [(map (wrap-span #(+ % 1)))
     (map (wrap-span #(- % 1)))])

  (into []
        (apply comp (concat [init-span]
                            transducers-2
                            [close-span]))
        [1 2 3])

  ;; What I'm looking for is function `wrap-with-spans` to :
  (into []
        (apply comp (concat [init-span]
                            (wrap-with-spans transducers-1)
                            [close-span])))

otwieracz18:10:17

I'm looking if wrap-with-spans function is possible that unwraps arguments for transducer from map, applies it to transducer and packs it back.

otwieracz18:10:55

But actually that's only part of the story, since that's for https://github.com/alvinfrancis/opentracing-clj

Bob B18:10:19

so, I think (not a transducer expert) that it'd be necessary to turn wrap-span into a transducer so it can be comped onto another transducer

otwieracz18:10:22

So it's more like

(defn wrap-span [f]
    (fn [{:keys [span args]}]
      (log/info args)
      (tracing/with-span [_ {:from span}]
        {:span span
         :args (f args)})))

Joshua Suskalo18:10:45

so each transducer does not have a way of inspecting transducers which occur before it. Once you combine transducers with comp they become fully opaque. That said, it would be entirely possible to make a function which takes a transducer and applies it only to a wrapped element in a map.

otwieracz18:10:27

I think it's not possible, especially with tracing/with-span.

Joshua Suskalo18:10:12

(defn under-key
  [k xf]
  (let [inner-rf (xf #(assoc %1 k %2))]
    (fn [rf]
      (completing
       (fn [acc elt]
         (rf acc (inner-rf elt (get elt k))))))))

(sequence
 (comp (map #(-> {:hello %})) 
       (map #(assoc % :buz :quux))
       (under-key :hello (comp (map inc)
                               (map -)))
       (map #(assoc % :bar :baz)))
 (range 3))
;; => ({:hello -1, :buz :quux, :bar :baz}
       {:hello -2, :buz :quux, :bar :baz}
       {:hello -3, :buz :quux, :bar :baz})

Joshua Suskalo18:10:27

For your example case of wrapping a span, you could also add logging or similar to the under-key transducer, although if you wanted logging for each individual step you would need to use it multiple times, as again, you can't peek into what's happened once comp is called. You could introduce your own comp function that would add in special things around each transducer though, much like what I have listed here.

Joshua Suskalo18:10:41

With my under-key from above you could implement your wrap something like this:

(defn wrapping-comp
  [span-name & funs]
  (comp (map #(-> {:args % :span span-name}))
        (reduce comp (map (partial under-key :args) funs))
        (map #(-> % :args))))

Joshua Suskalo18:10:28

you'd want to apply some function over funs before the under-key in order to add your logging and such

Joshua Suskalo18:10:29

but this would do it

otwieracz18:10:49

Hmm, let me have a look at this. Thanks! 🙂

borkdude18:10:26

I noticed a library that clj-kondo is confused about since the lib has a var that ends with ., so you call it as (py. ...) - clj-kondo thinks this is a constructor rather than a var and therefore something doesn't work as expected. Is this even a valid var name in Clojure? I don't mean "does it work" but "is it supported"

hiredman18:10:22

the macro expander unconditionally rewrites (py. ...) to (new py ...)

hiredman18:10:38

same for any symbol that ends with .

borkdude18:10:23

Ah indeed:

bar=> (def py.)
#'bar/py.
bar=> (macroexpand '(bar/py.))
(new py)

hiredman18:10:05

and getting around that is very challenging

hiredman18:10:26

user=> (let [f. (fn [x] x)] (apply f. [1]))
Syntax error (ClassFormatError) compiling fn* at (REPL:1:1).
Illegal field name "f." in class user$eval149
user=> (def f. (fn [x] x))
#'user/f.
user=> (apply f. [1])
Syntax error (ClassNotFoundException) compiling at (REPL:1:1).
f.
user=>

hiredman18:10:02

so you can indeed def a function with a name like f. but you cannot let bind such a name, and calling the function is tricky

hiredman18:10:48

Ah, fascinating

hiredman18:10:57

it is itself a macro

hiredman18:10:17

life finds a way

oyakushev18:10:58

And they say Lisps have no syntax:)

😆 2
Alex Miller (Clojure team)18:10:22

This is a some code undergoing further change in 1.12 with qualified instance member symbols, although I’m not sure that makes it worse

borkdude18:10:35

hm indeed:

bar=> (defmacro py. [])
#'bar/py.
bar=> (macroexpand '(bar/py.))
nil

borkdude18:10:59

still, don't know if clj-kondo should support (encourage) this

borkdude18:10:39

I guess "Symbols beginning or ending with '.' are reserved by Clojure." from the Reader spec is enough to say: just don't do it

Alex Miller (Clojure team)18:10:29

The reader page says “Symbols beginning or ending with '.' are reserved by Clojure.”

borkdude18:10:21

I guess clj-kondo could lint that ;)

Alex Miller (Clojure team)18:10:25

And indeed we are expanding both of those cases in 1.12 :)

oyakushev18:10:35

Looks like something that the Clojure compiler can itself warn about since leading and trailing . are unequivocally part of Clojure's syntax.

borkdude18:10:32

that would be too convenient

oyakushev18:10:52

A man can dream 😄

oyakushev18:10:07

For example, the compiler warns about non-dynamic earmuffed vars, so warning about the var name in def is not even new categorically.

👍 2