Fork me on GitHub
#clojure
<
2022-05-13
>
Nom Nom Mousse05:05:26

Can a function like

(defn read-forms-in-file [file]
  (let [rdr (-> file io/file io/reader PushbackReader.)]
    (loop [forms []]
      (let [form (try (read rdr) (catch Exception e nil))]
        (if form
          (recur (conj forms form))
          forms)))))
Easily be made lazy? Sometimes I just want to read the first form to get the (ns ...) declaration.

seancorfield05:05:30

You could gave it test whether (and (sequential? form) (= 'ns (first form))) to stop the recursion. Or even just scan forms until you found such a thing and make that the result of the loop.

seancorfield05:05:01

Along as you don't recur after that, it won't read any further.

Nom Nom Mousse05:05:42

Thanks. Good ideas ^^ I could also add a second arity for the max number of forms to read that is counted down for each loop.

pinkfrog05:05:58

Is it possible to add some hook function to nrepl so that I can execute some functions when nrepl starts?

seancorfield05:05:08

Are you using lein or clj?

seancorfield05:05:27

The former allows :repl-options {:init <expression>} in project.clj

seancorfield05:05:06

I don't know enough about nREPL to say how to do that for clj but I'm sure it's possible...

pinkfrog05:05:58

I am using clj

seancorfield05:05:06

I guess you could just put code in user.clj which Clojure itself will run at startup.

seancorfield05:05:40

Best to put that in a dev folder and have a :dev alias or similar that adds :extra-paths ["dev"] in your deps.edn file.

seancorfield05:05:13

Presumably you're using :main-opts ["-m" "nrepl.cmdline"] to start nREPL?

pinkfrog05:05:20

> I guess you could just put code in user.clj which Clojure itself will run at startup. Only one user.clj can be sourced. My plan is to have some global dev.clj file that can be used in every project, meanwhile without impeding the possibility of each project define its own user.clj file.

pinkfrog05:05:27

> Presumably you’re using :main-opts ["-m" "nrepl.cmdline"] to start nREPL? Maybe I will endup using a :dev/repl as you do. https://github.dev/seancorfield/dot-clojure/blob/develop/dev.clj

seancorfield05:05:39

Yeah, that's kinda why I went that route -- global code to setup REPLs and customize them...

Nom Nom Mousse05:05:10

I have a defmacro that looks like:

(defmacro t [n d] `(def ~n ~d))

(t myname {:name "hi" :fn (fn [] (print "hi"))})

(print myname)
;; {:name "hi", :fn #function[user/fn--54026]}
I'd like to keep the :fn field un-evaled, but eval everything else. How do I do it?

Nom Nom Mousse05:05:55

Put quote around the fn, perhaps?

Nom Nom Mousse05:05:09

It seems like even not using ~ evals the fn:

(defmacro preserve-fn-code [m] m)
(preserve-fn-code {:a "b" :fn (defn hi [] "hiya")})
=> {:a "b", :fn #'user/hi}

Nom Nom Mousse06:05:57

Good enough for me:

(defmacro preserve-fn-code [m] 
  (let [my-fn (str (:fn m))]
    (assoc m :fn-str my-fn)))

{:a "b", :fn #'user/hi, :fn-str "(defn hi [] \"hiya\")"}

Nom Nom Mousse06:05:27

I can easily read that form as a list after the macro.

delaguardo06:05:29

(defmacro t [n {:keys [fn] :as d}]
  (let [d (dissoc d :fn)]
    `(def ~n (assoc ~d :fn '~fn))))

❤️ 1
Nom Nom Mousse13:05:03

@U04V4KLKC Do you need the dissoc for any particular case?

delaguardo13:05:14

Yes, without dissoc :fn value will be evaluated

😅 1
delaguardo13:05:16

So if it contains sidefectfull code - some unexpected things might happen

🙏 1
delaguardo13:05:17

(defmacro t [n {:keys [fn] :as d}]
  `(def ~n (assoc ~d :fn '~fn)))

(t foo {:fn (println "42")}) ;; => will print "42"

pinkfrog08:05:41

On the classpath, I have a ns named dev. Also, inside a a.clj, it requires b.clj as dev. How can I access the original dev ns inside a.clj? The conditions in the problem shall hold.

p-himik08:05:55

Require it with an alas. So b has alias dev and you can require dev with alias orig-dev.

pinkfrog08:05:48

> ou can require dev with alias orig-dev. I can not do that.

pinkfrog08:05:20

Sounds

((ns-resolve 'dev 'add-libs) {})
finds the correct ’dev namespace.

delaguardo10:05:43

why you need so much ceremony? you have a number of options: require b not as dev, explicitly require dev as another-dev etc.

delaguardo10:05:18

ns-resolve is a great function but it is highly unusual to see in the code so it might confuse other developers how might read this code in the future and also it makes refactoring unnecessary hard because tools like lsp will not see add-libs as a reference

Nom Nom Mousse14:05:07

Is it possible to get the code that belongs to a defined function?

(defn hi [] (println "hi"))
I'd like a function magic that gives me the code:
(magic 'hi)
;; (defn hi [] (println "hi"))

Alex Miller (Clojure team)14:05:14

well clojure.repl/source will do this within some parameters (it's a source file on the classpath)

Alex Miller (Clojure team)14:05:06

for the more general case, no - at the repl, clojure compiles the source to bytecode, and the source is not retained

Nom Nom Mousse14:05:32

Nooo, I want it for functions in files loaded with load-file XD

Nom Nom Mousse14:05:52

Yup, I see. Perhaps some macro magic can preserve the contents of defn? I'll think about it.

Alex Miller (Clojure team)14:05:03

I mean, you could make your own version of load-file

Alex Miller (Clojure team)14:05:37

it's just opening a file and then reading and evaling each form. you could use read+string instead of read and then put those strings in a map keyed by the ns/name, and then have a function for looking things up in that map

👍 1
Nom Nom Mousse14:05:19

Or have two passes: one that reads forms and stores the defns in a map and then call load-file.

Alex Miller (Clojure team)14:05:14

well if you like doing things twice, sure :)

Nom Nom Mousse14:05:34

Sounds like less code to me.

dgb2314:05:57

aside: clojure spec interestingly has the form which returns the spec expression. And if I remember correctly it can also be found somewhere in metadata (could be very wrong about this).

Ben Sless15:05:58

Alex, is it possible to hook into the reader to attach the &form as metadata to vars? It might even be nice as an official option which defaults to false, but it could be excellent for tool builders, etc

Ben Sless15:05:44

it would certainly bloat metadata but I care less about that during development

Alex Miller (Clojure team)15:05:02

I believe you can do it with tools.reader ?

Ben Sless15:05:44

I haven't checked to be honest

dgb2315:05:02

During dev time I sometimes would love to have this. Especially when learning new stuff. Amazing would be an editor utility that inline expands a function call so to speak.

👍 2
1
respatialized15:05:12

A canonical format for representing metadata about source code + vars at edit time would be super useful for both documentation and editor tooling; it seems to be reinvented slightly differently by each tool that takes on the task (including the ones I've written). IIRC wasn't there previously discussion about working up a proposal for something similar not too long ago? :thinking_face:

Ben Sless16:05:55

Not that I recall, but I'm wiling to sign a blood pact to vote it up on ask.clojure when one of us requests this feature

Ben Sless16:05:52

I'd argue that not having that slightly breaks the promise of a REPL, where source only works if there's a file attached to it. Where we're going we don't need files

👍 1
🕶️ 1
Alex Miller (Clojure team)16:05:03

the REPL only promises to read eval and print :)

zakkor15:05:06

why are variadic functions in the stdlib defined like this?

(defn complement
  "Takes a fn f and returns a fn that takes the same arguments as f,
  has the same effects, if any, and returns the opposite truth value."
  {:added "1.0"
   :static true}
  [f] 
  (fn 
    ([] (not (f)))
    ([x] (not (f x)))
    ([x y] (not (f x y)))
    ([x y & zs] (not (apply f x y zs)))))
Why have specific bodies for 1 and 2 params, and not just the variadic case?

ghadi15:05:46

varargs have a performance cost, so it's worthwhile to make the small arities explicit

ghadi15:05:54

as they're common

Alex Miller (Clojure team)15:05:31

usually, this is not something worth doing in your own code

Nom Nom Mousse16:05:04

I have a value v that prints to this: #<Fn@36d78d8f rules/add_a>. Is there a way to programmatically get the name add_a without ugly string manipulation? The string representation is "rules$add_a@389d7a69" so I could

(second (re-find #"\$(.*)@" "rules$add_a@389d7a69"))
;; add_a

Alex Miller (Clojure team)16:05:21

you can get the class name, then demunge

Alex Miller (Clojure team)16:05:29

(-> f class .getName clojure.repl/demunge) something like that

ghadi16:05:05

clojure functions don't print with that #<fnclass@....> syntax anymore, must be some tooling you're using. (You'll still have to demunge)

Nom Nom Mousse16:05:43

Ah, then it is hash-p (`#p`) that does it: https://github.com/weavejester/hashp

Nom Nom Mousse16:05:34

Is there a way to remove a namespace from a symbol without using string manipulation?

(-> func class .getName clojure.repl/demunge symbol) => rules/add-a
But I'd like just add-a returned

p-himik16:05:26

name

🙏 1
borkdude18:05:14

I notice (when trying to make plumatic/schema work with bb) that schema instantiates records like this:

user=> (defrecord Dude [x])
user=> (Dude/create {:x 1})
#user.Dude{:x 1}
I've never seen that before....

p-himik18:05:08

Seems like they decided to rely on an implementation detail, for some reason.

p-himik19:05:50

The relevant commit talks about performance improvement, among other things. Pure speculation, but maybe they thought that avoiding map->Record and going straight for Record/create (which map->Record uses) would improve performance.

zakkor19:05:37

Is there a faster method than slurp I could use for doing GET requests?

phronmophobic19:05:06

What do you mean by faster?

phronmophobic19:05:35

There's several http clients you can use. You can see a list of them under "HTTP Clients" at https://www.clojure-toolbox.com/. I usually reach for https://github.com/dakrone/clj-http

zakkor19:05:07

Cool, will check that out! I mean it's taking 700ms to do a GET request in Clojure, which seems a bit excessive. The same request takes 80ms to complete in Go, and while I'm not expecting the same sort of performance, I am expecting a bit better than that

seancorfield19:05:21

These days I recommend Hato since it has no transitive dependencies and it uses the built-in JDK HTTP client.

seancorfield19:05:32

We've stopped using clj-http altogether at work.

phronmophobic19:05:11

I'm surprised that using slurp or any of the alternatives would be 10x slower. How are you measuring the difference in timing?

phronmophobic19:05:47

Are you timing the http call in the REPL or via the command line?

zakkor19:05:41

I am doing (time (slurp)) in the REPL

phronmophobic19:05:06

Interesting. Is there a large response body? By default, some http clients won't read the full response body unless you specifically consume it whereas slurp will read the full response body. Could that be it?

zakkor19:05:47

is there a way to ignore the return value from clj-http client/get?

zakkor19:05:24

(asking generally how to ignore a return value in clojure) 😛 I am doing (doseq [x [1]] (time (client/get ""))) but that doesn't seem too great

zakkor19:05:09

I put that in a file, and am running it like doing clj -m time

p-himik19:05:46

Just make nil the last statement - the previous value will be ignored.

phronmophobic19:05:45

> I put that in a file, and am running it like doing clj -m time (edited) Oh. That would explain the timing difference. Most of the difference in timing is just how long it takes for clojure to startup.

phronmophobic19:05:05

For example, if you ran clj -m time with an empty file, it should take a similar amount of time.

phronmophobic19:05:19

For scripting, there's a great tool called https://github.com/babashka/babashka that you can use to run your scripts and avoid the startup delay.

zakkor19:05:41

Sorry, I meant, ignoring the JVM startup time, just going off of what (time) prints out - usually takes around 1200ms, for both slurp and clj-http

zakkor19:05:50

same results if I run in the REPL directly, 1200ms on average

zakkor19:05:06

I am running openJDK temurin, which I'm not sure what it is exactly, might that be slow?

zakkor19:05:43

Trying Oracle JDK now 😄

zakkor19:05:02

Going to also add Hato to the list of tests..

seancorfield19:05:02

FWIW, if I do time curl <that URL> I get

real	0m2.041s
user	0m0.018s
sys	0m0.028s

phronmophobic19:05:01

beat me to it. my results are:

real    0m1.772s
user    0m0.038s
sys     0m0.033s

seancorfield19:05:12

I just ran that a few times, redirecting output (so it writes to a file) and it ranges from 1.5s to 3.5s

seancorfield19:05:13

Even just curl -I on that URL takes 1-2s. So I'm not surprised that slurp also takes that amount of time and so does clj-http.

zakkor19:05:55

Oh LOL, sorry guys

phronmophobic19:05:57

We should extract the time dilation code from the go runtime. Seems like it might be useful.

1
zakkor19:05:25

In my Go benchmark the thing was cached already, that's why it was taking only 80ms...

👍 1
zakkor19:05:42

My bad! Thanks for the help

phronmophobic19:05:17

No problem! Generally, I would still prefer a regular http library for http requests over slurp.

zakkor19:05:44

Yeah, gonna give Hato a try now, so all in all helpful things have occurred 😄

💯 2
phronmophobic19:05:56

@U04V70XH6’s hato recommendation sounds interesting. I'll have to add it to my list of libraries to try.

Cora (she/her)19:05:30

this got me the fastest curl response

Cora (she/her)19:05:33

time curl --retry 0 --raw --sslv3 --tcp-nodelay --tcp-fastopen '' > /dev/null

Joshua Suskalo19:05:56

it's a good one

Cora (she/her)19:05:27

it gets down to a second, but without the options it's up around 2s

Cora (she/her)19:05:52

it's variable either way, for me

Cora (she/her)19:05:07

not really important to the thread, though 🙂

zakkor19:05:38

I get around 1100ms - 1200ms from all the clojure clients (but I am also probably closer in proximity to the server)

zakkor19:05:04

and yeah, I discovered today that curl is surprisingly slow :thinking_face:

seancorfield19:05:02

The owners of http://olx.ro are probably thinking "Wow, that's a popular page today! I wonder why?" 😆

😆 5
devn23:05:57

wait, curl is slow?

1