Fork me on GitHub
#clojure
<
2024-01-29
>
Jack Arrington15:01:59

Hey all, I have a lot of CLJS experience but am setting up a backend app in JVM Clojure for the first time using compojure and http-kit (roughly followed https://learnxinyminutes.com/docs/compojure/). Could someone point me to a good resource for how my REPL workflow is supposed to go? Restarting with lein run every time feels very clunky, guessing that's not what most are doing

Janet A. Carr15:01:40

What do you use for your editor/IDE ?

Ed15:01:16

I think most people using lein will probably start an nREPL server (maybe with lein repl) and connect their editor to that. This is a video of Sean Corfield doing something similar with VS Code and the clojure cli tools https://www.youtube.com/watch?v=gIoadGfm5T8

πŸ’― 1
βž• 1
Jack Arrington16:01:48

@U1Z5X06NP using Calva mostly, occasionally NeoVim+Conjure

Jack Arrington16:01:01

@U0P0TMEFJ Sorry, I should have been more clear. I know how to start and connect to the REPL from my editor. Looking for what my workflow for starting and stopping the web server from the REPL should look like. Like when I update the code in a route, etc

Ed16:01:02

I think Sean says in that video that he uses Calva with VS Code. He shows how he evaluates forms as he's editing the file, which will update the running server. I use emacs and have that sort of workflow. I often load the whole file too, and write the code so that's not a destructive operation. (putting stateful things behind a defonce - for example).

Ed16:01:13

I'll tend to write some functions that start the web server that I can call from a (comment ,,,) block.

πŸ‘ 1
Ed16:01:34

and then run them when I've connected the emacs up.

Ed16:01:46

Does that make sense?

Jack Arrington16:01:06

Yep, it does! Thanks.

Ed16:01:42

I think a lot of people like things like component and the like to mange restarting things like db connections and web servers in their local process. But I've never found it to be all that useful. See: https://cognitect.com/blog/2013/06/04/clojure-workflow-reloaded

Janet A. Carr16:01:44

I think Calva uses the cider-nrepl, so that probably means using the cider-nrepl plugin for leiningen. I'm not sure if httpkit does this, but with ring you can just start the jetty adapter and have :join? false as an option. You don't need a rich comment to start the web server. It'll start with your main function, and then you can evaluate buffers to change the state of the process. I know ring has wrap-reload for this.

πŸ‘ 2
awesome 1
Jack Arrington16:01:58

wrap-reload looks perfect. Thanks!

😁 1
practicalli-johnny17:01:23

Wrap reload is what I used to use when first starting. Although it's more common to use the reader quote on the function passed when starting the web server https://practical.li/clojure-web-services/app-servers/create-server/#code-a-basic-web-server

Jack Arrington18:01:08

If I use the reader quote method, can I just re-eval my main handler function and then everything works, or do I have to call run-server again as well?

Ed18:01:30

no - you can just update the function and the var will be derefed on each request.

πŸ‘ 1
πŸ‘Œ 1
Ed18:01:50

I tend to put something like this in a dev file

(defonce web-server (atom nil))

(defn boot []
  (swap! web-server
         (fn [s]
           (when s (.stop s))
           (run-jetty #'app {:join? false :port 8080}))))

(comment

  (boot)

  )
and when I start the repl and connect my editor, I can just eval (boot) to start the sever. But because you're passing a var quoted thing (`#'app`) to run-server it won't capture the value of the var during the compilation process, so when you replace the contents of the var with a new function, the new function will be used for the next request. Does that make sense? I think the behaviour of vars is one of the differences between clojurescript and clojure

πŸ‘ 1
βž• 3
Jack Arrington17:01:54

Side note, but Ring and its ecosystem are really reminding me what I love about Clojure. It's just simple, powerful concepts all the way down...no magic, nothing fancy, just functions. When I have a doubt, I just read the source code. Definitely a welcome reprieve from the likes of Spring.

πŸ‘ 1
ryan echternacht16:02:45

thanks @U0P0TMEFJ I use a worse version of this, and will likely switch to yours!

πŸ‘ 1
vemv20:01:38

Isn't it a bit odd that one can't (read-string (pr-str (ex-info "a" {:a 2})))? ("No reader function for tag error") pr 's doc says: By default, pr and prn print in a way that objects can be read by the reader. I get it that exceptions don't have value semantics, but the chosen pr representation seems quite different for what I'd expect for a pr-str ed value (most notably it has newlines, which suggests this is human oriented, while pr 's doc hints a machine-oriented direction)

vemv20:01:07

This is all to say, I sense that I can't consistently choose between one of str or pr-str for "output that looks machine-oriented" vs. "output that looks human-oriented" . Perhaps there's a third function that will not try to present newlines?

hiredman20:01:57

why are you trying to avoid newlines?

vemv21:01:13

I can solve a specific problem such as avoiding newlines for a particular code path. I'm more interested in the why and, hopefully, if there are more functions to pick from

hiredman21:01:17

like, the reader can read:

{:foo 1 :bar 2}
and
{:foo 1
 :bar 2}
just as easily, so I am not sure newlines or not is some indication of intended for machines or not

hiredman21:01:29

so it seems like what you really want is "something with no newlines" but are couching it as "this doesn't seem machine readable"

hiredman21:01:52

or "this doesn't seem primarily intended for machines"

hiredman21:01:07

the big difference between pr on say maps and pr on an exception is the method for printing exceptions attempts to pretty print

vemv21:01:42

Then again, why? (pr-str (vec (range 10000)))does not make an attempt at prettification. Why would one thing deserve to be pretty for humans and the other not? It's an inconsistency that is at the very least surprising

Noah Bogart21:01:09

are the newlines the issue or lacking a reader for #error?

hiredman21:01:25

sure, the pretting observation was an attempt to better describe the difference, not to explain why it exists

πŸ‘ 1
Alex Miller (Clojure team)21:01:41

the reason it doesn't round-trip is that #error is not a known tag. that is NOT at odds with "can be read by the reader" - it can be read by the reader (if you bind a reader for #error tag)

Alex Miller (Clojure team)21:01:43

so answering the original question: no, it is not odd

Noah Bogart21:01:45

is there a particular reason that there's a clojure-originating reader tag that isn't defined by clojure itself?

Alex Miller (Clojure team)21:01:02

we use #object when printing arbitrary objects too

Alex Miller (Clojure team)21:01:11

user=> (pr-str (java.awt.Point. 1 2))
"#object[java.awt.Point 0x14f3c6fc \"java.awt.Point[x=1,y=2]\"]"

πŸ‘ 1
Alex Miller (Clojure team)21:01:14

I think those may be the only unbound tags, but not sure

Alex Miller (Clojure team)21:01:39

you can always use tagged-literal as the default-data-reader-fn to catch unknown tags in a container object

Alex Miller (Clojure team)21:01:01

(binding [*default-data-reader-fn* tagged-literal] (read-string (pr-str (ex-info "a" {:a 2}))))

Alex Miller (Clojure team)21:01:14

that just puts them in a TaggedLiteral object with "tag" and "value" that prints back to the original

vemv21:01:32

Odd or not, in practice not knowing what pr-str will print (pretty / not pretty) is not only occasionally inconvenient, but in my view, breaks abstraction (I have to be aware of all implementations to have the full picture) Ideally, the "pretty" and the "machine-readable" parts would be composable, at will.

hiredman21:01:47

but the pretty is also machine-readable

hiredman21:01:40

which is why I suggested reframing this to be about the specific issue you are having (newlines), the distinction you are drawing there between pretty and machine readable doesn't exist

Alex Miller (Clojure team)21:01:46

printing is inherently complicated - there are at least 3 flavors now (print, pr, and pretty-print) and those are somewhat intertwined. the key differentiator is that you are printing for some purpose and even these do not capture all the cases. providing more localized control over printing is on the short list for future possible work (in particular around edn printing).

πŸ‘ 1
hiredman21:01:10

like, my guess is you are trying to deal with edn data without a full fledged edn parser, and most of your data is printed without newlines so you can mostly get away with a line based approach, but that is breaking for exceptions

vemv21:01:52

> is on the short list for future possible work Very nice to hear!

hiredman21:01:17

but edn as a data format ignores whitespace, so like anything could set its print-method to produce whitespace

Alex Miller (Clojure team)21:01:57

there are lots of things you might want to customize when printing edn, right now you can only do that by changing printing at a global level for the runtime. it would be nice to have more localized control that you can safely change without affecting global config.

πŸ‘ 1
vemv21:01:02

> like, my guess is you are trying to deal with edn data without a full fledged edn parser, and most of your data is printed without newlines so you can mostly get away with a line based approach, but that is breaking for exceptions If you are very curious: https://github.com/clojure-emacs/cider/issues/3612#issuecomment-1915528196

Alex Miller (Clojure team)22:01:01

it seems generally like a mistake to think any edn parser would work with a line based approach

vemv22:01:43

From my side I don't have one, I have something that uses pr-str, that's all

hiredman22:01:41

(apply format "%s %s" ((juxt (comp (partial str "#") :tag) (comp pr-str :form)) (binding [*default-data-reader-fn* tagged-literal] (read-string (pr-str (Exception.))))))

πŸ’‘ 1
Alex Miller (Clojure team)23:01:31

but TaggedLiteral's printer already does that .... so, why?

hiredman23:01:20

Oh! of course, because tagged literal of course has the data and just prints it because it isn't the type that gets pretty printed anymore

Alex Miller (Clojure team)23:01:32

same as (pr-str (binding [*default-data-reader-fn* tagged-literal] (read-string (pr-str (Exception.)))))

Alex Miller (Clojure team)23:01:13

the whole point of TaggedLiteral is to be tagged value conveyor

Alex Miller (Clojure team)23:01:10

the underlying idea is that tagged literals can be conveyed across systems, over edn, without being semantically "understood" until it gets somewhere that knows how to read it

hiredman23:01:17

sure, which can lead you to think of it has "preserving" things, where in this case it removes the pretty formatting

hiredman23:01:16

(it doesn't remove it, but dealing with the data as generic data means it gets printed by the regular map printer instead of the custom printer that throwables use)

Alex Miller (Clojure team)23:01:35

but it is just generic data at that point, not a Throwable

phronmophobic18:01:31

fwiw, being able to print new line delimited edn is useful (sometimes referred to with the file suffix ednl). it’s a better alternative to jsonl in some contexts.

Alex Miller (Clojure team)18:01:39

:face_vomiting: to ednl, it's just edn

Alex Miller (Clojure team)18:01:16

do you mean a file with repeated edn objects?

phronmophobic18:01:29

yes, a file with lots of edn objects.

Alex Miller (Clojure team)18:01:47

well, it's still edn, but I'll take back 50% of my reaction

πŸ˜‚ 1
phronmophobic18:01:04

unfortunately, new line delimited formats work better with existing tooling in many cases

phronmophobic18:01:31

Do you know of any good alternatives for storing thousands of edn objects into a file (~1GB)? I'm not super excited about ednl, but I was less excited by alternatives. Most of the existing data-sciency formats expect "square, unnested data". Databases are great for internal use, but not great for sharing public datasets.