Fork me on GitHub
#clojure
<
2022-03-04
>
borkdude11:03:48

Granted that this is a low level operation and you should not normally do this - just trying to understand:

(def ^:dynamic *foo* 1)

(push-thread-bindings {#'*foo* 2})

(prn *foo*) ;; 1
Why am I not seeing the pushed binding for *foo* ?

Jimmy Miller16:03:09

Not sure why, but you can make it work by wrapping things in a let.

(let [] 
  (push-thread-bindings {#'*foo* 2}) 
  (prn *foo*))

;; => 
2

hiredman16:03:00

What repl are you using?

hiredman17:03:52

I believe nrepl potentially evaluates things on different threads

hiredman17:03:15

(nope, I see it does that in a clojure.main/repl)

borkdude17:03:36

No nREPL, just a plain clojure REPL

hiredman17:03:17

it must be a pop inside the repl or inside the compiler, that is paired with a push, but by running a push without a pop, the pop in the repl or compiler is popping the most recent frame (the push of foo), and leaving whatever frame it is supposed to pop in place

borkdude17:03:03

it also happens when I use load-file or just execute the file

hiredman17:03:33

well, the compiler also has pairs of pushes and pops in a few places

hiredman17:03:08

you may get different behavior when loading an aot'ed namespace

borkdude17:03:29

I figured as much. I tried to support dealing with "native" dynamic vars in SCI but this behavior makes it not behave like it should.

user=> (def ^:dynamic *foo* 1)
#'user/*foo*
user=> (sci/eval-string "(binding [*foo* 2] *foo*)" {:namespaces {'user {'*foo* #'*foo*}}})
1
where push-thread-bindings in the interpreter defers to Clojure's push-thread-bindings when it encounters a var from the host (and similar for pop-thread-bindings)

borkdude17:03:42

SCI has its own dynamic var type (based on the same thread-local stuff) but it would be nice if I could re-use dynamic vars from the host since that prevents me from wrapping and rebinding so the bound values line up

hiredman17:03:21

if you did something like ((eval (fn [] (whatever)))) then whatever would be invoked outside of any bindings setting up inside eval

pinkfrog13:03:17

Hi guys, I’d like to know how you perform http request inside go block without tying up the underlying thread?

p-himik14:03:02

By using clojure.core.async/thread.

mpenet14:03:25

by using a non blocking client, otherwise you're bound to use async/thread, or just not use async/go in the first place. A go block doesn't really spin a thread for you to use, it's just for coordination/supervision

p-himik14:03:01

> A go block doesn't really spin a thread for you to use It does, but from a limited pool that by default has only 8 threads.

Ferdinand Beyer14:03:46

Popular http client libs such as clj-http and http-kit have APIs for async requests, taking a callback function that will receive the response. You can call those in a go block and pass a callback that will put! the result on a channel. Then you can take from that channel in the go block to park until you have the result. A similar example is in the core.async guide here: https://clojure.org/guides/core_async_go#_general_advice

Ferdinand Beyer14:03:03

(go
  (let [ch (chan 1)]
    (org.http-kit.client/get url #(put! ch %))
    (<! ch) ; will receive the response
  ))

pinkfrog15:03:46

@U031CHTGX1T The returning a channel approach is what disappoints me. It complicates the result handling, like error handling, and makes the code structure contrived. I kinda feel the way goroutine implemented on JVM (or the way clojure.async implemented on JVM) is not a true CSP as compared to golang where I have no need to concern about if my goroutine will tying up all the underlying OS threads.

p-himik15:03:53

Implementation details and pragmatic platform limitations have nothing to do with CSP. Same with your particular HTTP client library not providing an API you'd prefer, which you can easily circumvent by providing a few line wrapper.

p-himik15:03:51

But I'm curious - how do you handle cases in Go where the target library does not support async out of the box, or at least not in the way it's typically done in Go?

Ferdinand Beyer15:03:37

Valid criticism, although possibly unfair as golang has CSP built-in natively and as the only way of doing concurrency, while for Clojure this is a library. In general I think many people don’t seem to realise that core.async is pretty low-level for Clojure standards, and you might want to add a layer of abstraction on top of it.

p-himik15:03:17

Interesting, that's the first time I hear about core.async being low-level. Do you have an example of such abstractions that still remain flexible and don't just do some one thing?

Ferdinand Beyer15:03:17

@U2FRKM4TW — watch Timothy Baldridge’s talk “Core.Async in Use” to know what I mean. Go blocks are imperative, centered around I/O, stuff you usually don’t want to deal with in your nice pure Clojure code.

👍 1
pinkfrog15:03:35

> But I’m curious - how do you handle cases in Go where the target library does not support async out of the box, or at least not in the way it’s typically done in Go? You just directly use synchronous calls.

pinkfrog15:03:31

@U031CHTGX1T

(go
  (let [ch (chan 1)]
    (org.http-kit.client/get url #(put! ch %))
    (<! ch) ; will receive the response
  ))
So for the result of (<! ch), we normally check if the result is an exception or a normal return value and proceed ?

Ferdinand Beyer15:03:12

It depends of course on what you want. For http-kit, the response will have an :error key that you would need to check, yes.

Ferdinand Beyer15:03:47

Please do realise that code like this pulls you deeper and deeper into the imperative mess:

(go
  (let [ch (chan 1)]
    (http/get url #(put! ch %))
    (when-let [response (<! ch)]
      (if-let [err (:error response)]
        (handle-error ...)
        (handle-success ...)))))

pinkfrog15:03:53

yeah. If some imaginary <! can automatically throw the exception if any, or return the normal result, then much boilerplate can be omitted.

p-himik15:03:28

@UGC0NEP4Y I see, thanks. Perhaps Go's goroutines are lightweight enough to allow for their amount to be unbounded. That's not yet the case for JVM threads, but it might potentially change in some future. People often mention project Loom in this context.

Ferdinand Beyer15:03:40

I was in a situation like that before and decided to go a different route. I used a dedicated channel for errors, and let go blocks park forever on error. Then you can use alt! and friends to fetch a successful result OR and error.

Ferdinand Beyer15:03:13

E.g.

(def err (chan))

(defn http-get [url]
  (let [ch (chan 1)
    (http/get url (fn [resp]
                    (if-let [error (:error resp)]
                      (put! err error)
                      (put! ch resp))))
    ch))

,,,

(go
  (let [resp (<! (http-get url))] ;; Never unparks on error
    ,,,))
Now you can either have a dedicated go-loop to handle errors, or use alt! to wait for the result of your go block, an error, or timeout. And your go block is relatively clean and does not need to care about error checking.

☝️ 1
Ferdinand Beyer15:03:13

In other words: core.async is a great tool, but you probably want to write some helper code on top of it :)

👍 2
mpenet16:03:02

> It does, but from a limited pool that by default has only 8 threads. yeah but it's not a thread you "own"

pinkfrog16:03:36

Hi @U031CHTGX1T, one more question on this. What’s your criteria on when to use`(chan 1)` and (chan) ? For example, in your above snippet.

Ferdinand Beyer16:03:59

In general, you should always think about your use case and choose the right buffer for channels. Without a buffer, go blocks need to “rendez-vous”, e.g. a put will block until a take is available (and vise versa). Here, I know that I will put exactly one item on the channel. By using the buffer we allow put! to succeed immediately, instead of having to block and wait for the go block to take. Not sure if this is optimization is worth it 🤷

Ferdinand Beyer16:03:00

(`put!` does not block, but it will put the value on the channel asynchronously, so some other thread / go-block will need to block/park)

Carlo15:03:40

Question: I'm searching references to understand what happens when I'm using both a clj and a cljs repl (in my case, this is specific to hacking on portal). What happens when I invoke, for example cider-connect-clj&cljs ?

p-himik16:03:33

Sounds like it would be better asked in #cider.

Carlo16:03:50

But I think this is not a cider-specific question, I mean, Calva will probably have a jack-in-clj&cljs analogue. I mainly want to know what happens in principle. Like, is it the same nrepl server that's talking to both the JVM and js runtime? I'd also appreciate some articles on using different repls in different projects

Carlo16:03:17

basically, I'm versed in using just one repl (clj or cljs) for projects, and I'd like to know what lies behind that.

👍 1
solf16:03:33

Yep it’s the same nrepl

solf16:03:05

What it does is just connecting twice to the same nrepl hostname, one in clj mode and one in cljs mode

solf16:03:18

So the question is really, how does a clojurescript repl work?

🙌 1
solf16:03:48

You can check this link to see a bit about how nrepl (what is used by cider and calva) does it: https://github.com/nrepl/piggieback#usage

🙌 1
solf16:03:00

Or this one for the official clojurescript repl: https://clojurescript.org/guides/quick-start

solf16:03:22

So for cider-connect-clj&cljs in particular: • it opens two nrepl connections, both talk to the same clojure (java) process • one of the two nrepl will then start a clojurescript nrepl, by calling (cider.piggieback/cljs-repl (cljs.repl.node/repl-env))

solf16:03:20

At that point in time, when you write something in the clojurescript repl it will: • be sent to the clojure (java) process • that will compile it into javascript • then send it to the js runtime you’re using (browser, nodejs, etc) • get the result back from the js runtime • return the result to you

🙌 2
solf16:03:05

If you want details about how the js runtime is created, you can check for example the browser one: https://github.com/clojure/clojurescript/blob/master/src/main/clojure/cljs/repl/browser.clj#L473

Carlo16:03:08

perfect, thank you @U7S5E44DB, the whole process is much clearer now!

Carlo16:03:44

I'll continue reading the sources you linked but at least at a higher level I understand what's going on 🥳

dpsutton18:03:43

Does anyone have a good way to turn a uuid into a relatively unique string of six characters or so? I understand its going to lose lots of uniqueness but it would be acceptable

dpsutton18:03:35

strategies like hash, mod by a large prime , etc

noisesmith19:03:53

silly overly literal version of this

(cmd)user=> (->> (java.util.UUID/randomUUID)
                 (str)
                 (remove #{\-})
                 (partition 2)
                 (map (comp #(Long/parseLong % 16)
                            (partial apply str)))
                 (filter pos?)
                 (take 6)
                 (map char)
                 (apply str))
"sÓ_xv"

Cora (she/her)19:03:44

crc32 can produce 8 hex characters

👀 1
Cora (she/her)19:03:10

you could always use md5/sha1/sha256 on it and grab the leading or trailing characters, too

Cora (she/her)19:03:38

and if you wanted to push things further there are a lot of non-cryptographic hashes out there like murmur3 and similar

Cora (she/her)19:03:40

oh, interesting, clojure itself contains an implementation of murmur3 https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Murmur3.java

👀 1
Cora (she/her)19:03:05

user=> (format "%x" (clojure.lang.Murmur3/hashUnencodedChars "asdf"))
"610afaf8"

ghadi23:03:22

private implementation detail, I wouldn't use that