Fork me on GitHub
#beginners
<
2023-10-25
>
λ00:10:30

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?

λ00:10:56

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?

λ00:10:03

Is there a way to interact with this dangling object?

λ00:10:42

Is there a way to list all objects that have been created within the REPL?

hiredman01:10:13

The repl is not some different thing, the code that runs, is just all normal jvm stuff

hiredman01:10:07

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)

hiredman01:10:56

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

hiredman01:10:36

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)

Renan Oliveira11:10:14

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?

Ed11:10:55

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.

Ed11:10:13

ClojureScript and Babashka have different compilations models, though.

Renan Oliveira12:10:50

Thanks for explanation, I will read the docs. If you segment the compilation process into phases, what would the result be?

Ed12:10:41

Where are you coming from? What do think a compilation process looks like?

Renan Oliveira12:10:16

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.

Renan Oliveira12:10:32

But I worked with Java and JavaScript.

Ed12:10:40

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.

Ed12:10:47

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.

Ed12:10:29

make sense?

jpmonettas12:10:41

I replied this some days ago to mostly the same question : https://clojurians.slack.com/archives/C03S1KBA2/p1697455307674269?thread_ts=1697398141.175179&amp;cid=C03S1KBA2 But also take a look at the rest of the thread which contains more info

jpmonettas12:10:11

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

jpmonettas12:10:49

they are both pretty similar compilers, one written in Java while the other in Clojure

Renan Oliveira14:10:35

That's an excellent explanation, it's starting to make sense! @U0P0TMEFJ

👍 1
Renan Oliveira14:10:29

@U0739PUFQ I will read and thanks

Felix Dorner18:10:47

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?

Alex Miller (Clojure team)18:10:49

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

Alex Miller (Clojure team)18:10:08

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

Alex Miller (Clojure team)18:10:22

the top level special case means you (at the application level) always have the ability to choose what version you're using

Felix Dorner18:10:37

Ok, makes sense. But it must be a top level dependency, not just “higher in the tree”?

Alex Miller (Clojure team)18:10:27

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