This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-10-25
Channels
- # announcements (22)
- # babashka (9)
- # beginners (33)
- # biff (12)
- # calva (17)
- # cider (64)
- # cljdoc (3)
- # cljfx (16)
- # clojure (125)
- # clojure-bay-area (14)
- # clojure-europe (15)
- # clojure-norway (64)
- # clojure-uk (2)
- # clojurescript (7)
- # conjure (1)
- # core-async (4)
- # cursive (6)
- # data-science (14)
- # datahike (8)
- # datomic (6)
- # defnpodcast (4)
- # emacs (5)
- # events (1)
- # hyperfiddle (15)
- # leiningen (17)
- # lsp (8)
- # membrane (27)
- # off-topic (25)
- # podcasts-discuss (4)
- # polylith (6)
- # portal (21)
- # reagent (11)
- # releases (1)
- # shadow-cljs (36)
- # slack-help (2)
- # sql (1)
- # squint (131)
- # testing (12)
- # xtdb (7)
So I did an evaluation of the following code in the repl
(doto (Thread. #((while true
(Thread/sleep 60000)
(println "Hello"))))
How do I terminate this (running) thread in the REPL. Is there a way to see processes/threads started by the REPL easily?I had a similar question about objects that are created without symbols. How do I manually clean those up? For example (new Socket)
is this immediately reclaimed by the garbage collector?
The repl is not some different thing, the code that runs, is just all normal jvm stuff
And there is no single repl either, a given clojure runtime can run multiple repl just fine and is easy to do (clojure has a built in socket repl that you can connect to with something like netcat), you can even run multiple clojure runtimes in a single jvm (but that is much more annoying to do)
That is to say, the repl is not some special debug mode thing, or privileged single thread thing, or whatever, where it would make sense for it to track everything
The jvm doesn't provide a way for running programs to capture and inspect allocations (there is a debugger interface and it is a whole thing, but it is a multi process thing, and it is a whole thing)
Hey everyone! I'm looking to gain a better understanding of how Clojure compiles and interprets code. Can you recommend any helpful resources on this topic?
Clojure compiles pretty much everything, almost nothing is interpreted. In general, everything that you eval will get complied to jvm byte code before executing. When you load a file, for example by requiring a name space, it is read, macro-expanded, compiled and eval'd one top level form at a time. So each def
/`defn` can only depend on ones that appear earlier in the file. This is explained much better here: https://clojure.org/reference/evaluation. There is also a concept of ahead of time compiling that more matches the java model of compilation which serialises the results of the compile step to disk which can help with startup time. You can read about that here: https://clojure.org/reference/compilation.
Thanks for explanation, I will read the docs. If you segment the compilation process into phases, what would the result be?
Now, I’m only working with clojure right now, sometimes I have confuses because clojure has a reader that read the code and after compile them, because the idea of data as a code, and I’m try to understand better this.
But I worked with Java and JavaScript.
So, I think the best thing to do is to forget about the word "compile". Clojure is built around a REPL. This is built out of 3 phases, a read
, an eval
and a print
(and a loop
😉). There is a running environment that you are interacting with every time you eval
something. This environment is in RAM in the running JVM process you are interacting with. It is made up of namespaces which are mappings of symbols to vars. When you call read
you are turning a sequence of bytes (for example a string from a terminal input) into a clojure data structure, like symbols and vectors and so on. Then you tend to eval
the result of that, which follows the rules mentioned in that link I posted earlier, the result is printed and we go round again. Compilation is a sub-step of eval
, as is macro-expansion.
So when you eval (def x 3)
it will change the current namespace to have a new symbol x
pointing to the value 3
. And when then eval (defn plus-x [y] (+ x y))
it will define a new symbol in the same namespace call plus-x
that points to the function that adds x
to it's argument. All code loading is built on top of this. So when you require a file, it just loads each top level form in the file, one at a time, until the end of the file.
I replied this some days ago to mostly the same question : https://clojurians.slack.com/archives/C03S1KBA2/p1697455307674269?thread_ts=1697398141.175179&cid=C03S1KBA2 But also take a look at the rest of the thread which contains more info
And if you are also interested on the internals of the ClojureScript compilers I also wrote this post a couple of days ago https://jpmonettas.github.io/my-blog/public/compilers-with-flow-storm.html
they are both pretty similar compilers, one written in Java while the other in Clojure
@U0739PUFQ I will read and thanks
When I print deps tree with clj -X:deps tree
some entries are marked with :older-version
which seems clear, but some are marked with :use-top
, what does it mean?
if a version of a lib is specified as a top level dependency, it's version is used regardless of what exists in the transitive tree
so when you see that, it means the version of that lib was not considered deep in the tree, and the top version was used
more gory details at https://clojure.org/reference/dep_expansion if needed
the top level special case means you (at the application level) always have the ability to choose what version you're using
Ok, makes sense. But it must be a top level dependency, not just “higher in the tree”?
Thanks Alex!
if it's not a top level dependency, generally the newest version found in the entire tree is used (this differs from Maven / Leiningen), but there are special cases which are covered in that link above