This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-03-04
Channels
- # announcements (19)
- # babashka (11)
- # babashka-sci-dev (9)
- # beginners (71)
- # calva (25)
- # cider (1)
- # clara (36)
- # clj-kondo (47)
- # clojure (65)
- # clojure-dev (64)
- # clojure-europe (9)
- # clojure-nl (2)
- # clojure-seattle (1)
- # clojure-uk (2)
- # clojured (8)
- # clojurescript (17)
- # cursive (9)
- # data-science (36)
- # datahike (11)
- # emacs (10)
- # figwheel-main (19)
- # fulcro (15)
- # graalvm (12)
- # humbleui (5)
- # introduce-yourself (3)
- # jobs (10)
- # leiningen (4)
- # lsp (24)
- # malli (7)
- # nextjournal (23)
- # off-topic (1)
- # pedestal (2)
- # polylith (6)
- # portal (1)
- # re-frame (3)
- # reitit (2)
- # releases (2)
- # remote-jobs (1)
- # reveal (9)
- # shadow-cljs (13)
- # spacemacs (6)
- # xtdb (3)
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*
?Not sure why, but you can make it work by wrapping things in a let.
(let []
(push-thread-bindings {#'*foo* 2})
(prn *foo*))
;; =>
2
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
like maybe this binding https://github.com/clojure/clojure/blob/master/src/clj/clojure/main.clj#L437
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)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
if you did something like ((eval (fn [] (whatever))))
then whatever would be invoked outside of any bindings setting up inside eval
Hi guys, I’d like to know how you perform http request inside go block without tying up the underlying thread?
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
> 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.
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
(go
(let [ch (chan 1)]
(org.http-kit.client/get url #(put! ch %))
(<! ch) ; will receive the response
))
@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.
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.
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?
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.
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?
@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.
> 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.
(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 ?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.
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 ...)))))
yeah. If some imaginary <!
can automatically throw the exception if any, or return the normal result, then much boilerplate can be omitted.
@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.
@UGC0NEP4Y — you can create macros to do that for you, check out: http://swannodette.github.io/2013/08/31/asynchronous-error-handling/
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.
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.In other words: core.async
is a great tool, but you probably want to write some helper code on top of it :)
> It does, but from a limited pool that by default has only 8 threads. yeah but it's not a thread you "own"
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.
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 🤷
(`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)
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
?
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
basically, I'm versed in using just one repl (clj or cljs) for projects, and I'd like to know what lies behind that.
What it does is just connecting twice to the same nrepl hostname, one in clj mode and one in cljs mode
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
Or this one for the official clojurescript repl: https://clojurescript.org/guides/quick-start
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))
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
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
perfect, thank you @U7S5E44DB, the whole process is much clearer now!
I'll continue reading the sources you linked but at least at a higher level I understand what's going on 🥳
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
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"
you could always use md5/sha1/sha256 on it and grab the leading or trailing characters, too
and if you wanted to push things further there are a lot of non-cryptographic hashes out there like murmur3 and similar
oh, interesting, clojure itself contains an implementation of murmur3 https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Murmur3.java
user=> (format "%x" (clojure.lang.Murmur3/hashUnencodedChars "asdf"))
"610afaf8"