Fork me on GitHub
#beginners
<
2022-08-06
>
Omar Bassam16:08:06

I have this piece of code that compile to an error "Can only recur from tail poistion" (loop [in (read-line)] (let [choice (read-string in)] ;; Check if the input is not number (if (not (int? choice)) (let [] (println "You must Enter a Number :") (recur (read-line))) nil))) I don't want to just return the recur in the if condition, I want to print to the user to re-enter the number.

daniel.flexiana17:08:59

Maybe try the inverse... If int choice? Return the choice. Else (do (println...) (recur...))

hiredman17:08:25

I am on my phone, but that recur looks like it is in the tail position, so I would take a step back

hiredman17:08:50

Verify the error points to that code, verify that version of the code is being run, etc

seancorfield17:08:15

That code works fine in a REPL for me so I think the error is coming from somewhere else in your code.

daniel.flexiana17:08:38

I'm on my phone too but I guess the nil is the tail position here

Omar Bassam18:08:48

It was a tail position thing. Thank you all @U04V70XH6 @U037W3C7KTR @U0NCTKEV8

👍 1
1
Omar Bassam17:08:35

Yes, I just fixed it now. The issue was the (let [choice ....) part was not the only part in my loop. Currently this is the version I got it to work:

(loop [in (read-line)]
    (let [choice (read-string in)]
      (cond
        (not (int? choice)) (recur (read-line))
        (not (<= 1 choice numCols))
        (let []
          (println "The number you entered is not a valid column")
          (recur (read-line)))
				;; TODO check if Col is filled
        :else (do
                (println "Your Choice is " in)
                (update-board  (- (dec numRows) (get @colFilledIndex (dec choice)))
                               (dec choice)
                               "P")))))

seancorfield17:08:40

If you use triple backticks around code That looks much better!

seancorfield17:08:04

Glad you got it working.

Omar Bassam17:08:05

Sorry, I'm new to Slack as well.

Omar Bassam17:08:34

I'm glad it works after weveral hours. However, I'm not sure I understand why 😄

seancorfield17:08:43

Figuring out what is actually the "tail position" in a nested chunk of code can be quite tricky. It can help to refactor the code into smaller functions. For example, write a read-column function that does the (read-string (read-line)) and returns the value if it is an int in the valid range, else nil. Then you can do this:

(loop [in (read-column num-cols)]
  (if in
    (do
      (println "Your Choice...")
      (update-board ..))
    (do 
      (println "The number you entered is not a valid column")
      (recur (read-column num-cols)))))

❤️ 1
seancorfield17:08:41

(`num-cols` and num-rows would be more idiomatic names in Clojure than numCols and numRows -- we like "kebab-case"... we don't use "headlessCamelCase")

1
❤️ 1
seancorfield17:08:00

And read-column could look something like:

(defn- read-column [num-cols]
  (let [choice (read-string (read-line))]
    (and (int? choice)
         (<= 1 choice num-cols)
         choice)))
so it'll return false for an invalid input and a number for valid one so you can use it with if.

❤️ 1
seancorfield17:08:40

Or you might prefer:

(when (and (int? choice)
                (<= 1 choice num-cols))
      choice)

seancorfield17:08:00

That would return nil for an invalid input or the number for a valid choice.

seancorfield17:08:47

(punning nil and false is quite common since Clojure relies on truthy and falsey a lot for conditions rather than strictly true / false -- but it takes a bit of getting used to)

Omar Bassam17:08:20

WOW! Thank you so Much. One question though. I had tried to use recur inside the (do) but it I guess it didn't work because do always return nil I guess. Or may be there's something I'm missing here.

seancorfield17:08:05

If the do was nested inside other code, it might not be in the "tail position" itself. Tail position is essentially defined recursively: the entire expression containing the recur must also be in "tail position", as well as recur being in the tail position of its own expression.

seancorfield17:08:11

Does that make sense?

seancorfield17:08:23

(recursive definitions can be tricky)

Omar Bassam18:08:18

Yeah I get it now, I thought that tail and return in other languages are the same. But I guess now I get the difference. Thank You so much for your help and explanation.

1
Omar Bassam19:08:32

I adjusted the read-column method to prevent the user from not entering an input that would result in "EOF while reading"

(defn read-column []
  (println "Please Choose a Column (1-7) :")
  (let [in (second (read+string))]
    (if (string/blank? in)
      (recur)
      (read-string in))))

1
seancorfield19:08:38

For safety/security reasons, you probably want to switch from clojure.core/read-string to clojure.edn/read-string -- you'll need to add [clojure.edn :as edn] to your :require in your ns form, and then use edn/read-string in read-column.

seancorfield19:08:54

user=> (doc read-string)
-------------------------
clojure.core/read-string
([s] [opts s])
  Reads one object from the string s. Optionally include reader
  options, as specified in read.

  Note that read-string can execute code (controlled by *read-eval*),
  and as such should be used only with trusted sources.

  For data structure interop use clojure.edn/read-string
nil
See that Note about executing code.

Omar Bassam20:08:57

Oh Thanks, That's very interesting, I wouldn't have thought of that. Thank you so much.

Ben Lieberman17:08:33

can I use an .edn file as storage for config (ie db configs etc)? That seems like an obvious application but I just want to be sure that's not terribly unidiomatic before I get in the habit of doing it

seancorfield17:08:39

Yup, we have a slew of config in .edn files at work. You'll see it a lot in libraries as well, with config files named for the library.

💯 1
didibus17:08:29

Yes it's awesome for configuration.

Alex Miller (Clojure team)17:08:28

just keep in mind you'll usually want to use the clojure.edn/read[-string], not clojure.core/read[-string] when you read those

👀 1
rads20:08:55

Related: https://github.com/juxt/aero This is a library designed to help with loading configs from EDN files, though clojure.edn is often enough on its own

Narendra Bharathi C V21:08:17

Hi, I need help with a script I'm working on. The task at hand is compute the power set of a set of attributes for a population (338) and determine the frequency distribution of each combination across the population. I wrote a function to compute this using reduce and that blew the heap; I rewrote the function to use loop/recur but that is exhausting the heap as well. reduce blew up right away, and loop/recur took about an hour before throwing an error. I need help to understand where/how heap is getting used up.

Narendra Bharathi C V21:08:18

(def distill-data-2
  (fn [acc xs]
    (loop [m    acc
           yays xs]
      (let [yay        (first yays)
            rest       (rest yays)
            patient-id (:Patient_Deidentifier (into {} yay))
            set        (->> (dissoc yay :Patient_Deidentifier)
                            (keys)
                            (calculate-powerset)
                            (filter #(not= #{} %))
                            (seq)
                            (map #(-> %
                                      (str)
                                      (str/replace #" :" "+")
                                      (str/replace #"}" "")
                                      (str/replace #"#\{:" "")
                                      (keyword))))
            n          (loop [colx m
                              ys   set]
                         (let [combi  (first ys)
                               rcombi (next ys)
                               o      (if (contains? colx combi)
                                        (-> (update-in colx [combi :count] inc)
                                            (update-in      [combi :patient-ids] #(conj % patient-id)))
                                        (assoc colx combi {:count 1 :patient-ids (sorted-set patient-id)}))]
                           (if rcombi (recur o rcombi) o)))]
        (if (not= rest '()) (recur n rest) n)))))

Narendra Bharathi C V21:08:19

(def test1 '({:Patient_Deidentifier "1.1.1" :BMFL_Bprecursors "Y"}
             {:Patient_Deidentifier "1.1.2" :CA_CMML "Y"}))

(distill-data-2 {} test1)
;; => {:BMFL_Bprecursors {:count 1, :patient-ids #{"1.1.1"}},
 :CA_CMML {:count 1, :patient-ids #{"1.1.2"}}}

(def test2 '({:Patient_Deidentifier "1.1.1" :CA_CMML "Y" :BMFL_Bprecursors "Y"} :Patient_Deidentifier "1.1.2" :BMFL_Bprecursors "Y" :CA_CMML "Y"}))

(distill-data-2 {} test2)
;; => {:CA_CMML {:count 2, :patient-ids #{"1.1.1" "1.1.2"}},
 :BMFL_Bprecursors {:count 2, :patient-ids #{"1.1.1" "1.1.2"}},
 :CA_CMML+BMFL_Bprecursors {:count 2, :patient-ids #{"1.1.1" "1.1.2"}}}

ahungry22:08:30

O(2 ^ n) - how big is your n? ive done power set stuff away from clojure and it gets awfully big/slow awfully fast

ahungry22:08:28

https://github.com/ahungry/ahungry-powerset its php, sorry (lol) - but i had to avoid recursion accumulation and swap to basic looping/accumulating, maybe your code could be tuned similar

ahungry22:08:47

if your n is 338, you may want to try a new approach

Narendra Bharathi C V01:08:35

@U96DD8U80 Thank you for looking! Aha, I did not think powerset generation is the issue; the n varies by the person and goes up to 28. Is there a way to confirm? This is what I have to compute power set.

(defn calculate-powerset [coll]
  (reduce (fn [a x]
            (into a (map #(conj % x)) a))
          #{#{}} coll))

David22:08:08

Hey all, wondering if someone can help me understand recur a bit better. First, see the code below, which computes prime factors for any given number:

(defn prime-factors-of
  ([n] (prime-factors-of 2 n))
  ([f n] ; f = divisor, n = value of number were trying to factor
   (if (> n 1)
     (if (= 0 (mod n f))
       (cons f (prime-factors-of f (/ n f)))
       (recur (inc f) n))
     [])))
I can follow what's happening up until recur; specifically what function is refer calling? Also, how does it know what function to call if it's not included in the same block?

ahungry22:08:06

recur is the fn it is defined in, prime-factors-of

ahungry22:08:42

its used to support tco and avoid blowing your stack

David22:08:37

Thank you! I read the docs, I was confused because all the examples use it inside a loop construct. So just realized it works by replacing recur with original function name. So basically you just use recur if you're trying to not blow your stack up with large calls?

seancorfield23:08:38

@U03T9NK8UBS Correct. recur is a "manual optimization" if you like: you can only use it in the "tail position" -- the Clojure compiler enforces that -- so anywhere that you see recur used instead of a direct recursive call, you can be sure that it truly is a "Tail Call Optimization", using a loop instead of a recursive call and avoiding stack expansion.

👍 1
skylize19:08:46

> was confused because all the examples use it inside a loop construct. loop is basically an anonymous function with explicit declaration of your intent to recur to it

👍 2