Fork me on GitHub
#clojurescript
<
2023-02-09
>
hifumi12302:02:27

What websocket library do people prefer using? I’m currently looking at • sente • haslett • wscljs It looks like the first two need you to fully commit yourself into core.async, which I don’t really like, and the last one seems focused on being a thin wrapper library with minimal dependencies.

seancorfield03:02:13

Is there a reason not to just use interop and native JS for WS support?

hifumi12303:02:52

No reason in particular besides being largely unfamiliar with how to deal with websockets in JS. I’ve mostly used them from Clojure with Java’s built-in websocket client

seancorfield03:02:55

I recently had to add WS stuff into an existing app that is plain HTML on the frontend -- so I only had the option of JS and it was much simpler than I expected. The WS client stuff in Java is nastier, IMO. Our backend WS server support is via the sunng ring adapter on Jetty 11 and that's a really nice integration.

Rupert (Sevva/All Street)08:02:42

Are you wondering about Frontend (ClojureScript) or backend (Clojure) Websocket libraries? On the backend I think jetty and http-kit give you a basic ring compatible WebSocket API out of the box. On the fronend - haslett gives you a very simple API. core.async is more needed for websockets on the frontend side to avoid callback hell in ClojureScript. It's not so needed for websockets on the backend where threads/blocking/locks/semaphores etc are available.

Rupert (Sevva/All Street)09:02:12

You may need to wrap any websocket library that you use with extra features: • Serialisation/deserialisation • Request/Response • Multiplexing (so you don't use lots of websocket connections per page) • Retries • Detecting disconnections • Automatic Reconnections

p-himik10:02:26

I've been using Sente for years and am pretty happy with it. It doesn't require core.async and does everything of the above except for multiplexing, I think. Which should be trivial to implement on top. The only downside is that you have to use Sente on both the frontend and the backend. So unless you want to reimplement half of Sente, you have to use CLJ[S] on both the client and the server.

Daniel Tan14:02:59

what do people suggest for ws on the backend then

seancorfield16:02:11

WS server? As noted above, we're using Jetty 11 in production via the sunng Ring adapter, which has really elegant WS support.

seancorfield16:02:56

(our frontend is JS, not cljs)

hifumi12309:02:52

In the end I went with wscljs since it’s working for my needs and wraps around the pure JS interface to websockets. My question was intended for websockets on the frontend; I have little say in how the backend is implemented, but I do have to connect to a websocket endpoint it exposes

seancorfield16:02:14

@U01458A7CBX Yes, that's what we're using in production now for almost all our apps.

👍 2
Gerome05:02:10

Hello! I'm having trouble with requiring a JS module in CLJS. I would like to try it on the repl. The https://clojurescript.org/news/2017-07-12-clojurescript-is-not-an-island-integrating-node-modules tell me that it's possible but (require ["lib" :as lib]) fails with failed to parse ns require. What am I missing?

2
Sam Ritchie05:02:51

Can you paste your full namespace declaration? :require should be a keyword there

Gerome06:02:28

Darn, forgot the quote. The correct from is (require '["lib" :as lib])

Gerome06:02:03

@U017QJZ9M7W Thanks for chiming in. It's not a namespace declaration, though. I'm trying to require a namespace in the repl. https://clojure.org/guides/repl/navigating_namespaces#_how_things_can_go_wrong.

Jazzer05:02:50

Hi everyone. What would be the best way of getting a random element from a set? I can see I could turn it into a vector and use rand-nth, but this seems wasteful…

grav06:02:58

I mean, there's no order in a set, so by that definition I'd say the first element is any element

Jazzer06:02:03

I get what you mean and it may actually work in my current context. Would repeated calls of this give the same element each time?

grav06:02:58

It'd be up to the specific implementation of the set, but I would be surprised if it didn't return the same element

grav06:02:01

Here, they mention something about the order being determined by the hash of the elements: https://clojuredocs.org/clojure.core/set > The actual order will ultimately be determined by the specific hashing function underpinning the data structure. Which I think is still an implementation detail, but is probably true in praxis.

grav06:02:49

What's the use-case, though? I'd definitely go for (-> my-set vec rand-nth) unless it's a very big collection

grav06:02:37

Actually, vec on a set might even be constant-time, depending on how it's implemented

pithyless06:02:10

(frequencies (repeatedly 1000 #(first (random-sample 0.1 (cycle #{:a :b :c})))))
;;=> {:b 344, :c 343, :a 313}
😅

pithyless06:02:56

> but this seems wasteful… Did you measure it? If it's a problem, give us more insight into the the input / output and expected use-cases. Because improving performance is all about taking advantage of specific constraints of the problem (and don't generalize well).

Jazzer07:02:16

I did not measure it. It was more of a reaction that it seems (to my mind, without actually knowing the inner workings) a redundant operation to add ordering into something for which I don’t care about the ordering purely to allow me to use a function that I do know about but needs a sequence to operate on.

Jazzer07:02:21

Certainly (-> my-set vec rand-nth) will achieve the desired outcome and as I’m currently working with a set of 4 elements 😂 is unlikely to cause any issues…

Jazzer07:02:49

Thanks all for your prompt replies.

pithyless07:02:00

rand-nth and shuffle both seem reasonable (if I was reading the code and trying to understand the author's intention)

user=> (frequencies (repeatedly 1000 #(rand-nth (vec #{:a :b :c}))))
{:a 330, :b 351, :c 319}

user=> (frequencies (repeatedly 1000 #(first (shuffle #{:a :b :c}))))
{:c 333, :b 323, :a 344}

pithyless07:02:04

I'm guessing the shuffle would be more expensive, especially on CLJS.

Jazzer07:02:45

Yeah. I may put them into a separate function called rand-from-set or such. Would at least make the intention clearer

hifumi12307:02:24

Maybe (comp first shuffle) could work for your case? EDIT: Looks like someone already recommended shuffle , didn’t notice 🙂

hifumi12307:02:03

Using shuffle, you’ll pay the cost of allocating an ArrayList then invoking java.util.Collections::shuffle on the AL. I don’t expect this to be much more expensive than PRNGs that run something like xoshiro256 then modulo to get the value within some interval

Jazzer07:02:05

Hadn’t spotted that you can shuffle a set. It returns a vector, but at least doesn’t need to be made into one beforehand.

pithyless07:02:13

so convert to array, run Fisher-Yates in-place, and return vec.

pithyless07:02:38

(just to take first)

pithyless07:02:15

I can't imagine this would be faster than calling rand-int and nth on a set->vec.

pithyless07:02:49

(Just to be clear, this discussion is completely theoretical: with 4 elements in a set, the best version is the one that won't make the reader think. :))

😎 2
Jazzer07:02:58

At a high level, I’d agree. I don’t see it being quicker to shuffle the whole lot just to take the first element of that shuffled collection, than to take a single element at random

Jazzer08:02:16

Totally agreed that it’s a theoretical discussion. I would always rather think about the efficiency - it won’t make a difference here, but the next time I need to do similar it could make a difference. There is a big difference between thinking about efficiency and agonising over efficiency, though! With which in mind, I shall get back to writing some bugs code

Gerome08:02:59

I'm trying to use the node OpenAI API client package but if I try to resolve the promise returned by .createCompletion I get :repl/exception! [...] codex is not defined. It looks like ClojureScript is generating JS that tries to access the constant codex which is my namespace. However, for some reason, in that context, codex is not defined. I've taken the code that uses <p! to wait for promise resolution directly from the https://clojurescript.org/guides/promise-interop#using-promises-with-core-async. By the way, it also doesn't work if I wrap .createCompletion in a .then. Where is my mistake? And: Is there some way to recover from this error in the repl? As soon as this error is thrown, my repl is completely broken. Almost as if I had stepped into a namespace using in-ns without requiring it first. I constantly get weird erros saying that codex isn't defined. Is it better to wrap this in a try form?

thheller08:02:37

can you provide the exact error? and more importantly how you are running this? all form by form in the REPL? this really shouldn't be using (require ...)

Gerome08:02:34

I'm running this form by form in the REPL

Gerome08:02:42

Error is coming

thheller08:02:53

and don't ever use core.async for promise interop if that is your only use of core.async

thheller08:02:55

(ns codex
  (:require ["openai" :refer [OpenAIApi Configuration]]))

(def api-key "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
(def conf (Configuration. (clj->js {:api-key api-key})))
(def open-ai (OpenAIApi. conf))

(-> (.createCompletion open-ai (clj->js {:prompt "const fib ="}))
  (.then prn))

Gerome08:02:32

Yes, this is what I tried first. However, since had the same result, I decided to give core-async a try.

thheller08:02:36

or if you want something that looks more like js await use

(ns codex
  (:require ["openai" :refer [OpenAIApi Configuration]]
            [shadow.cljs.modern :refer (js-await)]))

(def api-key "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
(def conf (Configuration. (clj->js {:api-key api-key})))
(def open-ai (OpenAIApi. conf))

(js-await [result (.createCompletion open-ai (clj->js {:prompt "const fib ="}))]
  (prn result))

Gerome08:02:12

What the... I just tried to reproduce it and now it suddenly works... 😦

Gerome08:02:02

I'll try js-await.

Gerome08:02:24

I'll see if I can reproduce this again later. I need to go and do something else.

thheller08:02:25

working with async stuff at the REPL is not super fun

Gerome08:02:38

Hm, I understand. The repl is very immediate and async requests are not. I thought, <p! would halt execution though.

thheller08:02:48

it does not no

Gerome08:02:16

Ah, right there was a difference between CLJ and CLJS. Darn

thheller08:02:32

no different in CLJ with async code

thheller08:02:51

but you have the option to block and wait for the result, so that is missing indeed

Gerome08:02:44

That's what I meant. I feel like the added value of using this nodeJS library is getting slimmer and slimmer. I might as well write my own client. Especially since their client is only published as optimized code and I have no way to see what it does.

thheller08:02:56

your client won't be able to block either?

thheller08:02:09

or do you mean CLJ? there is a java API is there not?

Gerome08:02:33

They have a Python and a JavaScript client published afaik.

Benjamin14:02:28

https://github.com/benjamin-asdf/codex-play/blob/main/main.cljs if it is any help to you I have here an example using the node package with nbb

👍 2
Braden Shepherdson15:02:01

is it supposed to be possible to define and then use a macro in a CLJC file? I'm getting "Can't take value of macro" from shadow-cljs when I try. the namespace :require-macros on itself. minimal repro:

(ns repro.macros
  #?(:cljs (:require-macros [repro.macros])))

(defmacro my-let [bindings & body]
  `(let ~bindings ~@body))

(defn foo [y]
  (my-let [x 1]
          (+ x y)))
yields
------ WARNING #1 - :undeclared-var --------------------------------------------
 File: /Users/braden/clojure/kondo-playground/src/repro/macros.cljc:8:4
--------------------------------------------------------------------------------
   5 |   `(let ~bindings ~@body))
   6 |
   7 | (defn foo [y]
   8 |   (my-let [x 1]
----------^---------------------------------------------------------------------
 Can't take value of macro repro.macros/my-let
--------------------------------------------------------------------------------
   9 |           (+ x y)))
  10 |
--------------------------------------------------------------------------------

borkdude15:02:35

Does it help when you put a :refer [my-let] in there?

lsenjov15:02:44

(:require-macros [repro.macros :refer [my-let]])?

Braden Shepherdson15:02:15

ah, that sorts it.

🎉 2
Braden Shepherdson15:02:45

I didn't realize that's how it worked inside the same namespace.

borkdude15:02:07

@U017QJZ9M7W Here's the answer to your question recently ^

slk50015:02:26

reagent/hiccup: I want to create intermediate function mount-element And pass to it [component "John"] But it gives an error. I tried pass it as vector or even quote list. But no success. (defn component [name] [: [:h1 (str "Hello " name)] ]) ;; it works (rdom/render [component "John"] (gdom/getElement "app")) ;; error -> Uncaught Error: Key must be integer (defn mount-element [f id] (rdom/render [f] (gdom/getElement id))) (defn app-components [] (mount-element (component "John") "app"))

p-himik15:02:58

There's #C0620C0C8 and code blocks should be formatted as blocks and not as inline code. As to the actual question, just don't call components as functions. Lots of ways to do it. E.g. you can add # in front ot (component "John").

👍 2
slk50015:02:28

thank you, it works!

👍 2
pbranes17:02:44

I am trying write the drawScene method from the WebGL tutorial in Clojurescript. I am not sure what is return from the method and how to represent in Clojurescript? function drawScene(gl, programInfo, buffers) { // from https://github.com/mdn/dom-examples/blob/main/webgl-examples/tutorial/sample2/draw-scene.js .... Other code // Not sure what is being return and how to represent in Clojurescript? { const offset = 0; const vertexCount = 4; gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount); } }

p-himik17:02:36

You can easily experiment with that syntax using your browser's DevTools JS console.

p-himik17:02:03

But also, there's no return statement and it's not a lamda, so nothing is returned.

pbranes17:02:42

Ok, I will play some more with the DevTools ... right now I am just returning this (.drawArrays gl (.-TRIANGLE_STRIP gl) offset vertex-count)

p-himik17:02:24

I bet the result of drawScene is completely ignored. And most likely, drawArrays doesn't return anything either.

pbranes18:02:28

I wasn't sure what was going on with the javascript.. seems wierd that they made an object just to declare some const and call the gl.drawArrays(...) method. Thanks for the help

p-himik18:02:46

That {} is not an object, it's just a block.

👍 2
p-himik18:02:09

But you're right, even as a block it's completely pointless. Perhaps some copy-paste mistake.

👍 2
thheller19:02:01

its not entirely pointless in theory. blocks provider their own scope, so the const declared there is only available in that block. pointless here since there is no other count

thheller19:02:20

but for CLJS you can just ignore it since we have clear scoping rules anyways 😉

pbranes15:02:45

Thanks thheller... i had already started just bound the values in the block in a previous let.