Fork me on GitHub
#beginners
<
2023-05-27
>
leifericf07:05:13

Is it possible to view the data structures produced by the Reader before the Compiler turns it into bytecode? By having the Reader dump its data structures to a file or maybe it's possible to views the Reader’s output for a given expression directly in one’s editor? Sort of like how some IDEs support viewing CIL for C# code. I'm curious to see what it looks like.

hiredman07:05:16

The reader is a deserializer

hiredman07:05:41

When you write clojure you are writing serialized data structures

hiredman07:05:29

The read creates a list when it reads () a map when it reads {} etc

leifericf07:05:35

So the Reader basically “expands” Clojure source code, which itself is data structures, to more of the same data structures on a more granular level?

hiredman07:05:52

No it reads in the serialized text form and produces the data structures described by the text from the text

leifericf07:05:57

I don't get it 😅 But thanks for trying to explain it to me! I think it would be helpful to see a few Clojure expressions before and after being passed through the reader.

hiredman07:05:14

The reader produces the data structures you write, and that is what is passed on to the compiler, like try (read-string "{}") at the repl

👍 2
hiredman07:05:57

The reader reads the string "{}" produces a hash map from it, then the repl prints the hash map back out as {}

leifericf07:05:38

I'm on my phone with a sleeping baby on my chest right now 😅 I will try it when I'm back at my computer!

hiredman07:05:46

Like when you run a json parser on [] and it get some kind of array or list and then json encode it and get [] again

leifericf07:05:51

But if the Reader doesn't do anything to the Clojure source code, then why is it needed? In that case, why can't the Clojure source code itself (which is data structures) be passed “raw” to the Compiler which produces bytecode? :thinking_face:

2
hiredman07:05:29

Because macros operate on clojure data structures not serialized data stuctures

💡 2
hiredman07:05:39

Something would have to turn the linear sequence of text into a tree representation at some point because things like lexical scope depend on the tree structure

leifericf07:05:09

Oh, I see. It's slowly dawning on me what it means when people say “Clojure is data.” But I can't say I fully “get it” yet.

hiredman07:05:43

Like, when you write "{}" it is a string, a linear sequence of characters, there is no structure to it

hiredman07:05:19

Parsing is taking a linear sequence of characters and turning it into a tree

leifericf08:05:58

Would it be accurate to say that the Reader is sort of like one massive function that takes some data structures (Clojure S-expressions from files or the REPL) and turns them into a different data structure required for compilation to bytecode?

hiredman08:05:00

The reader operates on a a linear series of characters

hiredman08:05:21

No data structures going in, data structures going out

hiredman08:05:28

Like any parser

hiredman08:05:06

The serialized parse tree of clojure code is the clojure code you wrote that was parsed

💡 2
leifericf08:05:27

Thank you for your patience and for taking the time to explain

jpmonettas11:05:19

So, when you type (+ 1 2) at the repl, the LispReader is going to take the string of characters and transform it into this objects

jpmonettas11:05:33

So the Compiler.java will have as an input a PersistentList with Symbol, Long. etc, that it can walk over, not a java.lang.String, that is the purpose of the LispReader

jpmonettas11:05:16

I find the easiest to do if you want to understand this things from a compiler perspective is to run clojure.main, add a breakpoint to eval, evaluate something at the repl and step over

💡 2
🙌 2
jpmonettas11:05:26

then analyze(...) will run over that and convert it into a ObjExpr (which will be a tree with Methods, Body, InvokeExpr, etc (maybe this tree is what you where interested in originally?)

👍 2
🙌 2
jpmonettas11:05:13

on that ObjExpr the compiler will call .emit(), which will convert that tree into bytecode

Bob B14:05:56

an option that's a small lift, but not awful, is to run a repl in a repl but override :eval to identity, which sort of turns that repl into a 'rpl':

user=> (clojure.main/repl :eval identity :prompt #(print "read=>"))
read=>@(list #(println %1 '(1 2 3)))
(clojure.core/deref (list (fn* [p1__22#] (println p1__22# (quote (1 2 3))))))
note that the form I entered there is sort of nonsense, but it hits a couple of the reader 'transformations'

💡 2
Jack Gee15:05:32

Hey Clojurians - I'm working through the maths of primality and factoring in Clojure as a project, and was wondering if I could get some feedback! Was wondering if there's any improvements of my sieve of Eratosthenes? My current implementation is:

(ns factoring.eratosthenes)

(defn bool-array 
    "A vector of true values representing integers 2 -> `n`"
    [n] 
    (into [] (take (dec n) (repeat true))))

(defn index 
    "The index of a number in the array defined in [[bool-array]]"
    [p] (- p 2))

(defn replace-value 
    "Replace the `np`-th in `b` value with false (if not already)"
    [b np]
    (assoc b (index np) false))

(defn replace-values
    "Using [[replace-value]], replace all multiples of `p` in `b` with false"
    [b p]
    (if (not (nth b (index p))) 
        b 
        (loop [b' b
               np (+ p p)]
            (if (> (index np) (count b'))
                b'
                (recur (replace-value b' np) (+ np p))))))

(defn isqrt
    "The integer square root of `n`"
    [n]
    (loop [a 1]
        (if (> (* a a)  n)
            (dec a)
            (recur (inc a)))))

(defn sieve
    "Returns a boolean array of primes in range 2 -> `n`"
    [n]
    (let [b (bool-array n)
          ub (isqrt n)]
        
        (loop [b' b
               p  2]
            (if (> p ub)
                b'
                (recur (replace-values b' p) (inc p)))))) 

(defn sieve-integers
    "Returns an array of integer of primes in range 2 -> `n`"
    [n]
    (let [b (sieve n)]
        (map #(first %) (filter #(second %) (map vector (range 2 n) b)))))

dpsutton16:05:33

before even getting to the logic, a couple pointers (into [] (take (dec n) (repeat true)) is way easier as (vec (repeat (dec n) true)) In your sieve-integers function, you use map vector but then when you map first and filter second you wrap them in functions #(first %) and #(second %). Just use the functions first and second. No need to pass a function that calls the function.

❤️ 2
dpsutton16:05:28

and an alternative approach

(let [n 25
      root (-> n math/sqrt math/floor long)
      multiple-of? (fn [n] (fn [a] (and (not= n a) (zero? (mod a n)))))]
  (->> (range 2 (inc n))
       (remove (apply some-fn (map multiple-of? (range 2 (inc root)))))))
(2 3 5 7 11 13 17 19 23)
gets the root with the useful clojure.math namespace. makes a helper multiple-of? that takes a number n and returns a function that is true when it’s argument is a multiple of n but not equal to n itself. Then get the list of numbers from 2..n , and then remove all of the multiples of the numbers between 2…sqrt(n)

❤️ 2
dpsutton16:05:20

if a vector of bools is what you want, there are simple ways to turn this sequence of indexes into a set and use that as a function of whether the bool should be set.

Jack Gee16:05:39

@U11BV7MTK thank you so much for your feedback. These all make sense to me

quoll21:05:52

Incidentally, your vector can be used to only represent odd numbers, starting at 3. For any number being tested, you do (dec (int (/ n 2))) Then you walk up the vector by n. Which is to say, for any position in the vector, call it x, you know that it represents n where: n= (inc (* 2 (inc x)) Then walk up the vector by n step each time. For instance, if you look at the vector at position 2, you’ll see its true. For position 2 that means n=1+(2*(1+2)) meaning n=7. So now step up in 7s: 2, 9, 16, 23, 30, 37… This is stepping up to every second multiple of 7, since every other multiple is an even number and we know they’re not prime. It’s trickier, but it speeds up the loop and uses half as much memory

❤️ 2
Jack Gee12:05:50

@U051N6TTC I like the optimization, I think I'll make some different versions for different optimizations that can be used and add this in!