This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-08-06
Channels
- # babashka (101)
- # beginners (47)
- # biff (7)
- # calva (36)
- # clj-kondo (19)
- # clojure (11)
- # clojure-europe (1)
- # clojurescript (4)
- # conjure (4)
- # core-typed (3)
- # cursive (24)
- # emacs (22)
- # events (4)
- # gratitude (1)
- # introduce-yourself (1)
- # malli (27)
- # meander (5)
- # off-topic (101)
- # portal (5)
- # shadow-cljs (26)
- # tools-build (4)
- # tools-deps (3)
- # vim (8)
- # xtdb (13)
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.
Maybe try the inverse... If int choice? Return the choice. Else (do (println...) (recur...))
I am on my phone, but that recur looks like it is in the tail position, so I would take a step back
Verify the error points to that code, verify that version of the code is being run, etc
That code works fine in a REPL for me so I think the error is coming from somewhere else in your code.
I'm on my phone too but I guess the nil is the tail position here
It was a tail position thing. Thank you all @U04V70XH6 @U037W3C7KTR @U0NCTKEV8
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")))))
If you use triple backticks around code
That looks much better!
Glad you got it working.
Sorry, I'm new to Slack as well.
NP. Welcome!
I'm glad it works after weveral hours. However, I'm not sure I understand why 😄
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)))))
(`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")
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
.Or you might prefer:
(when (and (int? choice)
(<= 1 choice num-cols))
choice)
That would return nil
for an invalid input or the number for a valid choice.
(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)
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.
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.
Does that make sense?
(recursive definitions can be tricky)
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.
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))))
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
.
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.Oh Thanks, That's very interesting, I wouldn't have thought of that. Thank you so much.
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
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.
just keep in mind you'll usually want to use the clojure.edn/read[-string], not clojure.core/read[-string] when you read those
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
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.
(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)))))
(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"}}}
O(2 ^ n) - how big is your n? ive done power set stuff away from clojure and it gets awfully big/slow awfully fast
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
@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))
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?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?
@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.