Fork me on GitHub
#clojure
<
2022-01-24
>
dgr01:01:19

Anybody have any strong opinions about the “best” library for manipulating URIs/URLs? I need something that will easily add query parameters, handling coding transparently, break things apart, etc. I’d like something that’s a bit more useful than java.net.URL or http://java.net.URI. Exploding Fish seems interesting (https://github.com/wtetzner/exploding-fish). Anybody have any strong opinions about it or others that you particularly like/hate?

dorab02:01:48

I've found lambdaisland/uri useful for my (rather simple) needs. I like the fact that it works on Clojure and Clojurescript.

👍 2
Joshua Suskalo15:01:29

Not quite connected, but please don't use java.net.URLs in Clojure. In Clojure you want everything to just be inert data and easy to use, but the hash function of java.net.URL depends on DNS servers and actually makes network calls, so if you have one as a map key it could behave incredibly unexpectedly and very slowly. always prefer URIs if you can.

🙀 1
👍 1
java 1
Nom Nom Mousse05:01:13

I want to be able to send commands to and set the config options of my running (web) app from the command line (without having to enter a REPL). Is the best way to do this to use curl to send JSON POST requests and add a JSON handler? Or is there another way?

p-himik08:01:35

If the config options you want to set are rather large then JSON or any other human-writable and machine-readable format that can easily represent your data would be a good fit. If options are small and plain key-value pairs, then you can get by with query parameters, like in ?a=1&b=2.

p-himik08:01:24

Another way would be to have a separate config file and make the server re-read it when it receives some signal, usually SIGHUP (although it doesn't have to be a POSIX signal - it can be a request to some URL).

Nom Nom Mousse08:01:52

I actually use the latter already. I have hawk watch my config file for changes. I guess sending commands through babashka/curl is the best option then.

p-himik09:01:19

Wait, initially you mentioned CLI but now you mention babashka/curl. If you gonna write your own tool to communicate to your own server then it doesn't have to be JSON - it can be any serializable format. And if you need to be able to change the config only from the machine where the server is running, then you can do it via programmable REPL - it would simply call the right function with the new config and exit, without you having to type anything.

Nom Nom Mousse09:01:49

Agreed. But I might want to run the program on a different server and communicate with it remotely.

p-himik09:01:24

Actually, my comment above is not entirely correct - REPL can be accessible from anywhere, not just the same server. But doing so is definitely more dangerous than exposing only the config endpoint. Although the latter is still dangerous by itself, of course, so all the necessary precautions must be taken.

Nom Nom Mousse10:01:04

I actually try to use CSRF tokens but I can't get them to work: https://clojurians.slack.com/archives/C053AK3F9/p1643011299188500 when sending POST requests with curl.

danm14:01:48

I'm trying to call Java's Path.of class method, which has a single-arity version (takes a URI) and a variable-arity version (takes strings). I know with variable arity you're supposed to use into-array, but (Path/of (into-array ["path" "parts"])) errors saying it can't cast java.lang.String to java.net.URI, so presumably Clojure is thinking it should call the single-arity form because there's only a single argument on the Clojure side. How do I get around that?

ghadi14:01:14

it takes a String, then an array of String

ghadi14:01:55

(java.nio.file.Path/of "your-stuff" (make-array String 0))

danm14:01:57

Sorted. Ta!

dpsutton18:01:06

For testing purposes, we will use a dynamic var, bind an atom, and then test some code that will produce side effects. Things like emails, snowplow events, etc. Wondering about what is best to put in the (def ^:dynamic emails-sent nil) bit of code.

dpsutton18:01:23

I know we could structurally rearrange stuff but that is not happening today

dpsutton18:01:14

But in the test file, is it better to have some reify’d atom that will warn “you need a binding” type of thing rather than nil? And if so, is the following the best way to accomplish that?

(reify
  clojure.lang.IAtom
  (swap [_ f] (throw (ex-info "test instance. must add binding" {})))
  (swap [_ f a] (throw (ex-info "test instance. must add binding" {})))
  (swap [_ f a b] (throw (ex-info "test instance. must add binding" {})))
  (swap [_ f a b c] (throw (ex-info "test instance. must add binding" {})))
  (compareAndSet [_ old new] (throw (ex-info "test instance. must add binding" {})))
  (reset [_ v] (throw (ex-info "test instance. must add binding" {})))
  clojure.lang.IAtom2
  (swapVals [_ f] (throw (ex-info "test instance. must add binding" {})))
  (swapVals [_ f a] (throw (ex-info "test instance. must add binding" {})))
  (swapVals [_ f a b] (throw (ex-info "test instance. must add binding" {})))
  (swapVals [_ f a b c] (throw (ex-info "test instance. must add binding" {})))
  (resetVals [_ v] (throw (ex-info "test instance. must add binding" {}))))

jkxyz18:01:32

Using declare would at least give you an exception about the var being unbound. That’s what I normally go with

jkxyz18:01:25

(declare foo)
(assoc foo :bar true)
;;=> class clojure.lang.Var$Unbound cannot be cast to class clojure.lang.Associative

dpsutton18:01:33

Hmm. I'm not sure that's better than just defining it nil. But that's interesting

jkxyz19:01:10

It's better in the general case because most functions will throw with the unbound var where they might accept nil. But if you care about the error message, I think you'd need to mock out all the methods as in your example.

Carlo19:01:50

Hey! I have a question on macros: consider this calls that happen in normal code (using the quil library):

(q/defsketch sketch-01-001-0002
  :title "sketch-01-001-0002"
  :settings #(q/smooth 2)
  :setup setup
  :size [(:width sketch) (:height sketch)]
  :key-pressed #(case (q/key-as-keyword)
                  :s (q/save-frame "art/sketch-01-001-0002-####.tiff")
                  :r (setup)
                  nil))
Now, I want to write this macro that wraps it:
(defmacro ssketch [name & instructions]
  `(q/defsketch ~name
     :title (str ~name)
     :settings #(q/smooth 2)
     :setup (do ~@instructions)
     :size [(:width sketch) (:height sketch)]
     :key-pressed #(case (q/key-as-keyword)
                     :s (q/save-frame "art/sketch-01-001-0002-####.tiff")
                     :r (do ~@instructions)
                     nil)))
I'll continue with the question in-thread!

Carlo20:01:56

Now, when I call macroexpand-1

(macroexpand-1 '(ssketch 'sketch-01-001-0002
                         (q/color-mode :hsb 360 100 100 1.0)
                         (q/background 200 50 50 1)
                         (dotimes [_ 50] (draw-circle))))
I get:
(quil.core/defsketch
 'sketch-01-001-0002
 :title
 (clojure.core/str 'sketch-01-001-0002)
 :settings
 (fn* [] (quil.core/smooth 2))
 :setup
 (do
  (q/color-mode :hsb 360 100 100 1.0)
  (q/background 200 50 50 1)
  (dotimes [_ 50] (draw-circle)))
 :size
 [(:width sketch-01-001-0002/sketch) (:height sketch-01-001-0002/sketch)]
 :key-pressed
 (fn*
  []
  (clojure.core/case
   (quil.core/key-as-keyword)
   :s
   (quil.core/save-frame "art/sketch-01-001-0002-####.tiff")
   :r
   (do
    (q/color-mode :hsb 360 100 100 1.0)
    (q/background 200 50 50 1)
    (dotimes [_ 50] (draw-circle)))
   nil)))

Carlo20:01:49

And you can see that the first argument of defsketch, 'skettch-01-001-0002 is quoted. I would like that not to be quoted. How can I do that?

dpsutton20:01:49

don’t quote it?

'(ssketch 'sketch-01-001-0002

Carlo20:01:40

Thank you! But I can't call it like this:

(ssketch sketch-01-001-0002
         (q/color-mode :hsb 360 100 100 1.0)
         (q/background 200 50 50 1)
         (dotimes [_ 50] (draw-circle)))

Carlo20:01:00

outside of the contest of macroexpand-1

Sam Ritchie20:01:30

At a glance I think you need to quote it in your str call

Sam Ritchie20:01:07

(str ‘~name)

Sam Ritchie20:01:34

Sorry for wonky quote symbol on phone

dpsutton20:01:09

I’m confused why you think you can call

(q/defsketch sketch-01-001-0002 ...)
but you must quote the name to your own macro
(ssketch 'sketch-01-001-0002 ...)
`

Sam Ritchie20:01:38

@U11BV7MTK I bet the str call is throwing with “symbol not found” or something

Carlo20:01:33

Ok I solved the problem I was having: quil was expecting a function in the :setup key, so I should have written :setup (fn [] (do ~@instructions)) in:

(defmacro ssketch [name & instructions]
  `(q/defsketch ~name
     :title (str '~name)
     :settings #(q/smooth 2)
     :setup (fn [] (do ~@instructions))
     :size [(:width sketch) (:height sketch)]
     :key-pressed #(case (q/key-as-keyword)
                     :s (q/save-frame "art/sketch-01-001-0002-####.tiff")
                     :r (do ~@instructions)
                     nil)))
And the other error is that I thought I had to quote the symbol I was passing to defmacro. I worried that there was no meaning in an unquoted version. But I guess defmacro is taking as parameter the raw syntax, so that's not necessary!

Joshua Suskalo20:01:00

you should probably prefer ~(str name)

Joshua Suskalo20:01:10

Although I guess it doesn't much matter here

Carlo21:01:53

Thanks @U5NCUG8NR, that's more semantically in line with what I wanted!

👍 2
Carlo22:01:49

general question: when I create a namespace and :require something in it, if the required file evaluated instantly (I suspect this is the case but would like some confirmation and a pointer to where this is discussed)

Alex Miller (Clojure team)22:01:56

when a namespace is loaded, any required namespaces that are not yet loaded are loaded at that point

Alex Miller (Clojure team)22:01:53

just as a general thing, when a Clojure namespace is loaded, top level forms are read and evaluated top-down just like they would be if you typed them in at the repl

Alex Miller (Clojure team)22:01:40

the ns macro expands a series of calls but relevant here, a call to require which initiates a load if the namespace is not yet loaded (tracked in *loaded-libs* dynamic var)

🙌 1